import { z } from "zod"; /** * Authentication Abstraction Interfaces * * These interfaces define the contracts for authentication-related operations. * Implementations can use different databases (PostgreSQL, MongoDB, etc.) to * store user, role, and token data. */ /** * User data structure */ export interface UserData { id: string; email: string; passwordHash?: string | null; displayName?: string | null; photoUrl?: string | null; emailVerified: boolean; emailVerificationToken?: string | null; emailVerificationSentAt?: Date | null; isAnonymous?: boolean; metadata?: Record; createdAt: Date; updatedAt: Date; } /** * Data for creating a new user */ export interface CreateUserData { email: string; passwordHash?: string; displayName?: string; photoUrl?: string; emailVerified?: boolean; isAnonymous?: boolean; metadata?: Record; } /** * User Identity Data (OAuth accounts linked to user) */ export interface UserIdentityData { id: string; userId: string; provider: string; providerId: string; profileData?: Record | null; createdAt: Date; updatedAt: Date; } /** * Standardized profile data returned by an OAuth provider verification payload */ export interface OAuthProviderProfile { providerId: string; email: string; displayName?: string | null; photoUrl?: string | null; /** Whether the OAuth provider has verified the user's email address */ emailVerified?: boolean; } /** * Pluggable OAuth Provider integration strategy */ export interface OAuthProvider { /** The identifier of the provider (e.g. "github", "google") */ id: string; /** Zod schema validating the expected request payload (e.g. { code: string }) */ schema: z.ZodSchema; /** * Verify external tokens/codes and return a standardized user profile. * * NOTE: Declared as method syntax (not arrow property) intentionally. * This makes `OAuthProvider` bivariant in `T`, which is correct because * each provider is self-contained — `schema` validates the request body * and `verify` consumes the same `T`. Bivariance lets heterogeneous * providers (`OAuthProvider`) coexist in a single * `OAuthProvider[]` array without resorting to `any`. */ verify(payload: T): Promise; } /** * Role data structure */ export interface RoleData { id: string; name: string; isAdmin: boolean; defaultPermissions: { read?: boolean; create?: boolean; edit?: boolean; delete?: boolean; } | null; collectionPermissions: Record | null; } /** * Data for creating a new role */ export interface CreateRoleData { id: string; name: string; isAdmin?: boolean; defaultPermissions?: RoleData["defaultPermissions"]; collectionPermissions?: RoleData["collectionPermissions"]; } /** * Refresh token info */ export interface RefreshTokenInfo { id: string; userId: string; tokenHash: string; expiresAt: Date; createdAt: Date; userAgent?: string | null; ipAddress?: string | null; } /** * Password reset token info */ export interface PasswordResetTokenInfo { userId: string; expiresAt: Date; } /** * Magic link token info */ export interface MagicLinkTokenInfo { userId: string; expiresAt: Date; } /** * Options for paginated user listing */ export interface ListUsersOptions { /** Max results per page (default 25) */ limit?: number; /** Number of results to skip (default 0) */ offset?: number; /** Search term — matches against email and displayName (case-insensitive) */ search?: string; /** Field to sort by (default "createdAt") */ orderBy?: string; /** Sort direction (default "desc") */ orderDir?: "asc" | "desc"; /** Filter by role ID */ roleId?: string; } /** * Result of a paginated user listing */ export interface PaginatedUsersResult { users: UserData[]; /** Total number of users matching the filters (ignoring limit/offset) */ total: number; limit: number; offset: number; } /** * Abstract user repository interface. * Handles all user-related database operations. */ export interface UserRepository { /** * Create a new user */ createUser(data: CreateUserData): Promise; /** * Get a user by ID */ getUserById(id: string): Promise; /** * Get a user by email */ getUserByEmail(email: string): Promise; /** * Get a user by an OAuth identity */ getUserByIdentity(provider: string, providerId: string): Promise; /** * Get all identities linked to a user */ getUserIdentities(userId: string): Promise; /** * Link a new OAuth identity to a user */ linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record): Promise; /** * Update a user */ updateUser(id: string, data: Partial>): Promise; /** * Delete a user */ deleteUser(id: string): Promise; /** * List all users (unbounded — use listUsersPaginated for large datasets) */ listUsers(): Promise; /** * List users with server-side pagination, search, and sorting. */ listUsersPaginated(options?: ListUsersOptions): Promise; /** * Update user's password hash */ updatePassword(id: string, passwordHash: string): Promise; /** * Set email verification status */ setEmailVerified(id: string, verified: boolean): Promise; /** * Set email verification token */ setVerificationToken(id: string, token: string | null): Promise; /** * Find user by email verification token */ getUserByVerificationToken(token: string): Promise; /** * Get roles for a user */ getUserRoles(userId: string): Promise; /** * Get role IDs for a user */ getUserRoleIds(userId: string): Promise; /** * Set roles for a user (replaces existing roles) */ setUserRoles(userId: string, roleIds: string[]): Promise; /** * Assign a specific role to a new user */ assignDefaultRole(userId: string, roleId: string): Promise; /** * Get user with their roles */ getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[]; } | null>; } /** * Abstract role repository interface. * Handles all role-related database operations. */ export interface RoleRepository { /** * Get a role by ID */ getRoleById(id: string): Promise; /** * List all roles */ listRoles(): Promise; /** * Create a new role */ createRole(data: CreateRoleData): Promise; /** * Update a role */ updateRole(id: string, data: Partial>): Promise; /** * Delete a role */ deleteRole(id: string): Promise; } /** * Abstract token repository interface. * Handles refresh tokens and password reset tokens. */ export interface TokenRepository { /** * Create a new refresh token */ createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise; /** * Find a refresh token by hash */ findRefreshTokenByHash(tokenHash: string): Promise; /** * Delete a refresh token by hash */ deleteRefreshToken(tokenHash: string): Promise; /** * Delete all refresh tokens for a user */ deleteAllRefreshTokensForUser(userId: string): Promise; /** * List all refresh tokens for a user */ listRefreshTokensForUser(userId: string): Promise; /** * Delete a specific refresh token by its primary key ID */ deleteRefreshTokenById(id: string, userId: string): Promise; /** * Create a password reset token */ createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise; /** * Find a valid (not expired, not used) password reset token by hash */ findValidPasswordResetToken(tokenHash: string): Promise; /** * Mark a password reset token as used */ markPasswordResetTokenUsed(tokenHash: string): Promise; /** * Delete all password reset tokens for a user */ deleteAllPasswordResetTokensForUser(userId: string): Promise; /** * Clean up expired tokens */ deleteExpiredTokens(): Promise; /** * Create a magic link token */ createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise; /** * Find a valid (not expired, not used) magic link token by hash */ findValidMagicLinkToken(tokenHash: string): Promise; /** * Mark a magic link token as used */ markMagicLinkTokenUsed(tokenHash: string): Promise; } /** * MFA factor data structure */ export interface MfaFactor { id: string; userId: string; factorType: "totp"; friendlyName?: string; verified: boolean; createdAt: Date; updatedAt: Date; } /** * MFA challenge information */ export interface MfaChallengeInfo { id: string; factorId: string; createdAt: Date; verifiedAt?: Date; ipAddress?: string; } /** * Recovery code data structure */ export interface RecoveryCode { id: string; userId: string; usedAt?: Date; } /** * Abstract MFA repository interface. * Handles all MFA-related database operations. */ export interface MfaRepository { /** * Create a new MFA factor for a user */ createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise; /** * Get all MFA factors for a user */ getMfaFactors(userId: string): Promise; /** * Get a specific MFA factor by ID */ getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string; }) | null>; /** * Mark an MFA factor as verified */ verifyMfaFactor(factorId: string): Promise; /** * Delete an MFA factor */ deleteMfaFactor(factorId: string, userId: string): Promise; /** * Create an MFA challenge */ createMfaChallenge(factorId: string, ipAddress?: string): Promise; /** * Get an MFA challenge by ID */ getMfaChallengeById(challengeId: string): Promise; /** * Mark an MFA challenge as verified */ verifyMfaChallenge(challengeId: string): Promise; /** * Create recovery codes for a user */ createRecoveryCodes(userId: string, codeHashes: string[]): Promise; /** * Use a recovery code (mark as used) */ useRecoveryCode(userId: string, codeHash: string): Promise; /** * Get unused recovery code count for a user */ getUnusedRecoveryCodeCount(userId: string): Promise; /** * Delete all recovery codes for a user */ deleteAllRecoveryCodes(userId: string): Promise; /** * Check if a user has any verified MFA factors */ hasVerifiedMfaFactors(userId: string): Promise; } /** * Combined auth repository interface for convenience */ export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository, MfaRepository { }