import * as privacyKit from "privacy-kit"; import { log } from "@/utils/log"; interface TokenCacheEntry { userId: string; extras?: any; cachedAt: number; } interface AuthTokens { generator: Awaited>; verifier: Awaited>; } class AuthModule { private tokenCache = new Map(); private tokens: AuthTokens | null = null; async init(): Promise { if (this.tokens) { return; // Already initialized } log({ module: 'auth' }, 'Initializing auth module...'); const generator = await privacyKit.createPersistentTokenGenerator({ service: 'handy', seed: process.env.HANDY_MASTER_SECRET! }); const verifier = await privacyKit.createPersistentTokenVerifier({ service: 'handy', publicKey: generator.publicKey }); this.tokens = { generator, verifier }; log({ module: 'auth' }, 'Auth module initialized'); } async createToken(userId: string, extras?: any): Promise { if (!this.tokens) { throw new Error('Auth module not initialized'); } const payload: any = { user: userId }; if (extras) { payload.extras = extras; } const token = await this.tokens.generator.new(payload); // Cache the token immediately this.tokenCache.set(token, { userId, extras, cachedAt: Date.now() }); return token; } async verifyToken(token: string): Promise<{ userId: string; extras?: any } | null> { // Check cache first const cached = this.tokenCache.get(token); if (cached) { return { userId: cached.userId, extras: cached.extras }; } // Cache miss - verify token if (!this.tokens) { throw new Error('Auth module not initialized'); } try { const verified = await this.tokens.verifier.verify(token); if (!verified) { return null; } const userId = verified.user as string; const extras = verified.extras; // Cache the result permanently this.tokenCache.set(token, { userId, extras, cachedAt: Date.now() }); return { userId, extras }; } catch (error) { log({ module: 'auth', level: 'error' }, `Token verification failed: ${error}`); return null; } } invalidateUserTokens(userId: string): void { // Remove all tokens for a specific user // This is expensive but rarely needed for (const [token, entry] of this.tokenCache.entries()) { if (entry.userId === userId) { this.tokenCache.delete(token); } } log({ module: 'auth' }, `Invalidated tokens for user: ${userId}`); } invalidateToken(token: string): void { this.tokenCache.delete(token); } getCacheStats(): { size: number; oldestEntry: number | null } { if (this.tokenCache.size === 0) { return { size: 0, oldestEntry: null }; } let oldest = Date.now(); for (const entry of this.tokenCache.values()) { if (entry.cachedAt < oldest) { oldest = entry.cachedAt; } } return { size: this.tokenCache.size, oldestEntry: oldest }; } // Cleanup old entries (optional - can be called periodically) cleanup(): void { // Note: Since tokens are cached "forever" as requested, // we don't do automatic cleanup. This method exists if needed later. const stats = this.getCacheStats(); log({ module: 'auth' }, `Token cache size: ${stats.size} entries`); } } // Global instance export const auth = new AuthModule();