/** * AuthService – Registration, Login, Session Management */ import bcrypt from 'bcrypt'; import { randomUUID } from 'crypto'; import type { DatabaseInterface, DBUser, DBSession } from '../database/DatabaseInterface.js'; const SALT_ROUNDS = 10; const SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours export interface Session { token: string; userId: string; expiresAt: number; } export interface AuthResult { user: Omit; session: Session; } export class AuthService { constructor(private db: DatabaseInterface) {} async register(email: string, password: string, name: string, role: string = 'planer'): Promise { const existing = this.db.getUserByEmail(email); if (existing) { throw new Error('Email already registered'); } const password_hash = await bcrypt.hash(password, SALT_ROUNDS); const user = this.db.createUser({ email, password_hash, name, role }); const session = this.createSession(user.id); return { user: this.sanitizeUser(user), session }; } async login(email: string, password: string): Promise { const user = this.db.getUserByEmail(email); if (!user) { throw new Error('Invalid credentials'); } const valid = await bcrypt.compare(password, user.password_hash); if (!valid) { throw new Error('Invalid credentials'); } const session = this.createSession(user.id); return { user: this.sanitizeUser(user), session }; } async changePassword(userId: string, oldPassword: string, newPassword: string): Promise { const user = this.db.getUser(userId); if (!user) throw new Error('User not found'); const valid = await bcrypt.compare(oldPassword, user.password_hash); if (!valid) throw new Error('Invalid current password'); const password_hash = await bcrypt.hash(newPassword, SALT_ROUNDS); this.db.updateUser(userId, { password_hash }); } createSession(userId: string): Session { const token = randomUUID(); const expiresAt = Date.now() + SESSION_TTL_MS; this.db.createSession({ token, user_id: userId, expires_at: expiresAt }); return { token, userId, expiresAt }; } validateSession(token: string): Session | null { const dbSession = this.db.getSession(token); if (!dbSession) return null; if (Date.now() > dbSession.expires_at) { this.db.deleteSession(token); return null; } return { token: dbSession.token, userId: dbSession.user_id, expiresAt: dbSession.expires_at, }; } destroySession(token: string): void { this.db.deleteSession(token); } getUserFromSession(token: string): DBUser | null { const session = this.validateSession(token); if (!session) return null; return this.db.getUser(session.userId); } private sanitizeUser(user: DBUser): Omit { const { password_hash, ...safe } = user; return safe; } }