// store.ts // // Authentication storage interface and in-memory implementation. // The interface allows easy migration to PostgreSQL later. import { User, type UserId } from "../user"; import { generateToken, hashToken } from "./token"; import type { AuthMethod, SessionData, TokenId, TokenType } from "./types"; // Data for creating a new session (tokenId generated internally) export type CreateSessionData = { userId: string; tokenType: TokenType; authMethod: AuthMethod; expiresAt: Date; userAgent?: string; ipAddress?: string; }; // Data for creating a new user export type CreateUserData = { email: string; passwordHash: string; displayName?: string; }; // Abstract interface for auth storage - implement for PostgreSQL later export interface AuthStore { // Session operations createSession( data: CreateSessionData, ): Promise<{ token: string; session: SessionData }>; getSession(tokenId: TokenId): Promise; updateLastUsed(tokenId: TokenId): Promise; deleteSession(tokenId: TokenId): Promise; deleteUserSessions(userId: UserId): Promise; // User operations getUserByEmail(email: string): Promise; getUserById(userId: UserId): Promise; createUser(data: CreateUserData): Promise; getUserPasswordHash(userId: UserId): Promise; setUserPassword(userId: UserId, passwordHash: string): Promise; updateUserEmailVerified(userId: UserId): Promise; } // In-memory implementation for development export class InMemoryAuthStore implements AuthStore { private sessions: Map = new Map(); private users: Map = new Map(); private usersByEmail: Map = new Map(); private passwordHashes: Map = new Map(); private emailVerified: Map = new Map(); async createSession( data: CreateSessionData, ): Promise<{ token: string; session: SessionData }> { const token = generateToken(); const tokenId = hashToken(token); const session: SessionData = { tokenId, userId: data.userId, tokenType: data.tokenType, authMethod: data.authMethod, createdAt: new Date(), expiresAt: data.expiresAt, userAgent: data.userAgent, ipAddress: data.ipAddress, }; this.sessions.set(tokenId, session); return { token, session }; } async getSession(tokenId: TokenId): Promise { const session = this.sessions.get(tokenId); if (!session) return null; // Check expiration if (new Date() > session.expiresAt) { this.sessions.delete(tokenId); return null; } return session; } async updateLastUsed(tokenId: TokenId): Promise { const session = this.sessions.get(tokenId); if (session) { session.lastUsedAt = new Date(); } } async deleteSession(tokenId: TokenId): Promise { this.sessions.delete(tokenId); } async deleteUserSessions(userId: UserId): Promise { let count = 0; for (const [tokenId, session] of this.sessions) { if (session.userId === userId) { this.sessions.delete(tokenId); count++; } } return count; } async getUserByEmail(email: string): Promise { const userId = this.usersByEmail.get(email.toLowerCase()); if (!userId) return null; return this.users.get(userId) ?? null; } async getUserById(userId: UserId): Promise { return this.users.get(userId) ?? null; } async createUser(data: CreateUserData): Promise { const user = User.create(data.email, { displayName: data.displayName, status: "pending", // Pending until email verified }); this.users.set(user.id, user); this.usersByEmail.set(data.email.toLowerCase(), user.id); this.passwordHashes.set(user.id, data.passwordHash); this.emailVerified.set(user.id, false); return user; } async getUserPasswordHash(userId: UserId): Promise { return this.passwordHashes.get(userId) ?? null; } async setUserPassword(userId: UserId, passwordHash: string): Promise { this.passwordHashes.set(userId, passwordHash); } async updateUserEmailVerified(userId: UserId): Promise { this.emailVerified.set(userId, true); // Update user status to active const user = this.users.get(userId); if (user) { // Create new user with active status const updatedUser = User.create(user.email, { id: user.id, displayName: user.displayName, status: "active", roles: [...user.roles], permissions: [...user.permissions], }); this.users.set(userId, updatedUser); } } }