mikroauth
Version:
Dead-simple magic link authentication that is useful, lightweight, and uncluttered.
553 lines (540 loc) • 15.8 kB
text/typescript
import { PikoDB } from 'pikodb';
/**
* @description Storage interface for MikroAuth.
*/
interface StorageProvider {
get(key: string): Promise<string | null>;
set(key: string, value: string, expirySeconds?: number): Promise<void>;
delete(key: string): Promise<void>;
addToCollection(collectionKey: string, item: string, expirySeconds?: number): Promise<void>;
removeFromCollection(collectionKey: string, item: string): Promise<void>;
getCollection(collectionKey: string): Promise<string[]>;
getCollectionSize(collectionKey: string): Promise<number>;
removeOldestFromCollection(collectionKey: string): Promise<string | null>;
findKeys(pattern: string): Promise<string[]>;
}
interface EmailMessage {
from: string;
to: string | string[];
cc?: string | string[];
bcc?: string | string[];
subject: string;
text: string;
html: string;
}
interface EmailProvider {
sendMail(message: EmailMessage): Promise<void>;
}
interface CreateTokenRequest {
email: string;
username?: string;
role?: string;
ip?: string;
}
interface JwtPayload {
sub: string;
jti: string;
iat?: number;
exp?: number;
lastLogin: number;
metadata?: {
ip?: string;
};
}
interface JwtPayload {
sub: string;
username?: string;
email?: string;
role?: string;
jti: string;
iat?: number;
exp?: number;
lastLogin: number;
metadata?: {
ip?: string;
};
}
interface RequestContext {
body?: Record<string, any>;
query?: Record<string, any>;
headers?: Record<string, any>;
ip?: string;
log?: {
error: (message: string, error: Error) => void;
info?: (message: string, ...args: any[]) => void;
};
user?: {
email: string;
};
}
interface RequestContext {
body?: Record<string, any>;
query?: Record<string, any>;
headers?: Record<string, any>;
ip?: string;
log?: {
error: (message: string, error: Error) => void;
info?: (message: string, ...args: any[]) => void;
};
user?: {
email: string;
};
}
interface MagicLinkRequest {
email: string;
ip?: string;
metadata?: Record<string, any>;
appUrl?: string;
subject?: string;
}
interface MagicLinkRequest {
email: string;
ip?: string;
metadata?: Record<string, any>;
appUrl?: string;
subject?: string;
}
interface VerifyTokenRequest {
token: string;
email: string;
}
interface VerifyTokenRequest {
token: string;
email: string;
}
interface SessionInfo {
id: string;
createdAt: number;
lastLogin: number;
lastUsed?: number;
metadata?: {
ip?: string;
};
isCurrentSession?: boolean;
}
interface SessionInfo {
id: string;
createdAt: number;
lastLogin: number;
lastUsed?: number;
metadata?: {
ip?: string;
};
isCurrentSession?: boolean;
}
interface TokenResponse {
accessToken: string;
refreshToken: string;
exp: number;
tokenType: string;
}
interface TokenResponse {
accessToken: string;
refreshToken: string;
exp: number;
tokenType: string;
}
type UserIdentity = {
id: string;
email: string;
username: string;
role: string;
};
type AuthConfiguration = {
/**
* The JSON Web Token secret to use.
*/
jwtSecret: string;
/**
* How many seconds until a magic link expires?
*/
magicLinkExpirySeconds: number;
/**
* How many seconds until the JSON Web Token expires?
*/
jwtExpirySeconds: number;
/**
* How many seconds until the refresh token expires?
*/
refreshTokenExpirySeconds: number;
/**
* How many sessions can be active?
*/
maxActiveSessions: number;
/**
* The URL to the application we are authenticating towards.
*/
appUrl: string;
/**
* Custom email templates.
*/
templates: EmailTemplateConfiguration | null | undefined;
/**
* Use debug mode?
*/
debug: boolean;
};
/**
* @description Options for configuring MikroAuth.
*/
type AuthOptions = Partial<AuthConfiguration>;
type EmailConfiguration = {
emailSubject: string;
user: string;
host: string;
password: string;
port: number;
secure: boolean;
maxRetries: number;
debug: boolean;
};
type EmailOptions = Partial<EmailConfiguration>;
/**
* @description Configuration for magic link email templates.
* Defines the structure for text and HTML versions of authentication emails.
*/
type EmailTemplateConfiguration = {
textVersion: MagicLinkTemplate;
htmlVersion: MagicLinkTemplate;
};
/**
* @description Function that generates the text and HTML version of the email.
* @param magicLink - The authentication link to include in the email.
* @param expiryMinutes - The number of minutes until the link expires.
* @param metadata - Optional metadata object that can be used to customize the email content.
* @returns The formatted text or HTML content for the email.
*/
type MagicLinkTemplate = (magicLink: string, expiryMinutes: number, metadata?: Record<string, any>) => string;
/**
* @description MikroAuth is a dead-simple "Magic Link"
* authentication service that works with your storage and email.
*/
declare class MikroAuth {
private readonly config;
private readonly email;
private readonly storage;
private readonly jwtService;
private readonly templates;
constructor(options: {
auth: AuthOptions;
email: EmailOptions;
}, emailProvider?: EmailProvider, storageProvider?: StorageProvider);
/**
* @description Verify that we are not using defaults.
*/
private checkIfUsingDefaultCredentialsInProduction;
/**
* @description Generates a secure token.
*/
generateToken(email: string): string;
/**
* @description Generate a JWT for an authenticated user.
*/
generateJsonWebToken(user: UserIdentity): string;
/**
* @description Generates a refresh token.
*/
private generateRefreshToken;
/**
* @description Tracks a user session in storage.
*/
private trackSession;
/**
* @description Creates the actual magic link URL, using the token and email.
*/
private generateMagicLinkUrl;
/**
* @description Creates and sends a magic link to the user.
*/
createMagicLink(params: MagicLinkRequest): Promise<{
message: string;
}>;
/**
* @description Creates credentials/tokens directly without sending an email.
* This method is useful for programmatic use cases such as SSO integrations,
* where you need to authenticate a user and obtain tokens directly without
* going through the magic link flow.
*/
createToken(params: CreateTokenRequest): Promise<TokenResponse>;
/**
* @description Verifies a magic link token and creates session tokens.
*/
verifyToken(params: VerifyTokenRequest): Promise<TokenResponse>;
/**
* @description Refreshes an access token using a refresh token.
*/
refreshAccessToken(refreshToken: string): Promise<TokenResponse>;
/**
* @description Verifies a JWT token.
*/
verify(token: string): JwtPayload;
/**
* @description Logs out a user by revoking their session token.
*/
logout(refreshToken: string): Promise<{
message: string;
}>;
/**
* @description Gets all active sessions for a user.
*/
getSessions(request: RequestContext): Promise<{
sessions: SessionInfo[];
}>;
/**
* @description Revokes all active sessions for a user.
*/
revokeSessions(request: RequestContext): Promise<{
message: string;
}>;
/**
* @description Middleware to authenticate requests with JWT tokens.
*/
authenticate(request: any, next: (error?: Error) => void): void;
}
/**
* @description PikoDB implementation of the StorageProvider interface.
* Provides lightweight, reliable key-value storage with optional encryption.
*/
declare class PikoDBProvider implements StorageProvider {
private readonly db;
private readonly encryption?;
private readonly PREFIX_KV;
private readonly PREFIX_COLLECTION;
private readonly TABLE_NAME;
constructor(pikoDB: PikoDB, encryptionKey?: string);
/**
* @description Start the PikoDB instance.
*/
start(): Promise<void>;
/**
* @description Close the database connection and clean up resources.
*/
close(): Promise<void>;
/**
* @description Set a value with optional expiry.
*/
set(key: string, value: string, expirySeconds?: number): Promise<void>;
/**
* @description Get a value by key.
*/
get(key: string): Promise<string | null>;
/**
* @description Delete a key.
*/
delete(key: string): Promise<void>;
/**
* @description Add an item to a collection.
*/
addToCollection(collectionKey: string, item: string, expirySeconds?: number): Promise<void>;
/**
* @description Remove an item from a collection.
*/
removeFromCollection(collectionKey: string, item: string): Promise<void>;
/**
* @description Get all items in a collection.
*/
getCollection(collectionKey: string): Promise<string[]>;
/**
* @description Get the number of items in a collection.
*/
getCollectionSize(collectionKey: string): Promise<number>;
/**
* @description Remove and return the oldest item from a collection.
*/
removeOldestFromCollection(collectionKey: string): Promise<string | null>;
/**
* @description Find keys matching a pattern.
* Supports wildcards: * (any characters) and ? (single character).
*/
findKeys(pattern: string): Promise<string[]>;
}
/**
* @description Use MikroMail as the email provider.
* @see https://github.com/mikaelvesavuori/mikromail
*/
declare class MikroMailProvider implements EmailProvider {
private readonly email;
private readonly sender;
constructor(config: Record<string, any>);
/**
* @description Send an email using the MikroMail provider.
*/
sendMail(message: EmailMessage): Promise<void>;
}
/**
* @description Mock implementation of EmailProvider
* for development and testing purposes.
*/
declare class InMemoryEmailProvider implements EmailProvider {
private options?;
private sentEmails;
constructor(options?: {
logToConsole?: boolean;
onSend?: (message: EmailMessage) => void;
} | undefined);
/**
* @description Send an email with the in-memory provider.
*/
sendMail(message: EmailMessage): Promise<void>;
/**
* @description Get all sent emails (for testing purposes).
*/
getSentEmails(): EmailMessage[];
/**
* @description Clear sent emails history (for testing purposes).
*/
clearSentEmails(): void;
}
/**
* @description In-memory implementation of the StorageProvider interface.
*/
declare class InMemoryStorageProvider implements StorageProvider {
private data;
private collections;
private expiryEmitter;
private expiryCheckInterval;
constructor(checkIntervalMs?: number);
/**
* @description Clean up resources.
*/
destroy(): void;
/**
* @description Check for and remove expired items.
*/
private checkExpiredItems;
/**
* @description Set a value with optional expiry.
*/
set(key: string, value: string, expirySeconds?: number): Promise<void>;
/**
* @description Get a value by key.
*/
get(key: string): Promise<string | null>;
/**
* @description Delete a key.
*/
delete(key: string): Promise<void>;
/**
* @description Add an item to a collection.
*/
addToCollection(collectionKey: string, item: string, expirySeconds?: number): Promise<void>;
/**
* @description Remove an item from a collection.
*/
removeFromCollection(collectionKey: string, item: string): Promise<void>;
/**
* @description Get all items in a collection.
*/
getCollection(collectionKey: string): Promise<string[]>;
/**
* @description Get the number of items in a collection.
*/
getCollectionSize(collectionKey: string): Promise<number>;
/**
* @description Remove and return the oldest item from a collection.
*/
removeOldestFromCollection(collectionKey: string): Promise<string | null>;
/**
* @description Find keys matching a pattern (simple wildcard support).
*/
findKeys(pattern: string): Promise<string[]>;
}
/**
* @description Resend email provider using their HTTP API.
* @see https://resend.com/docs/api-reference/emails/send-email
*/
declare class ResendProvider implements EmailProvider {
private readonly apiKey;
private readonly debug;
constructor(config: {
apiKey: string;
debug?: boolean;
});
/**
* @description Send an email using the Resend API.
*/
sendMail(message: EmailMessage): Promise<void>;
}
/**
* @description Brevo (formerly Sendinblue) email provider using their HTTP API.
* @see https://developers.brevo.com/docs/send-a-transactional-email
*/
declare class BrevoProvider implements EmailProvider {
private readonly apiKey;
private readonly debug;
constructor(config: {
apiKey: string;
debug?: boolean;
});
/**
* @description Send an email using the Brevo API.
*/
sendMail(message: EmailMessage): Promise<void>;
}
/**
* @description Postmark email provider using their HTTP API.
* @see https://postmarkapp.com/developer/api/email-api
*/
declare class PostmarkProvider implements EmailProvider {
private readonly serverToken;
private readonly messageStream;
private readonly debug;
constructor(config: {
serverToken: string;
messageStream?: string;
debug?: boolean;
});
/**
* @description Send an email using the Postmark API.
*/
sendMail(message: EmailMessage): Promise<void>;
}
/**
* @description SendGrid email provider using their v3 HTTP API.
* @see https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send
*/
declare class SendGridProvider implements EmailProvider {
private readonly apiKey;
private readonly debug;
constructor(config: {
apiKey: string;
debug?: boolean;
});
/**
* @description Send an email using the SendGrid v3 API.
*/
sendMail(message: EmailMessage): Promise<void>;
}
/**
* @description AWS SES email provider using the SES v2 HTTP API.
* @see https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html
*/
declare class AWSESProvider implements EmailProvider {
private readonly accessKeyId;
private readonly secretAccessKey;
private readonly region;
private readonly debug;
constructor(config: {
accessKeyId: string;
secretAccessKey: string;
region: string;
debug?: boolean;
});
/**
* @description Send an email using the AWS SES v2 API.
*/
sendMail(message: EmailMessage): Promise<void>;
/**
* @description Calculate SHA256 hash.
*/
private sha256;
/**
* @description Calculate HMAC SHA256.
*/
private hmacSha256;
/**
* @description Get signing key for AWS Signature V4.
*/
private getSignatureKey;
}
export { AWSESProvider, BrevoProvider, type CreateTokenRequest, type EmailProvider, InMemoryEmailProvider, InMemoryStorageProvider, MikroAuth, MikroMailProvider, PikoDBProvider, PostmarkProvider, ResendProvider, SendGridProvider, type StorageProvider };