import { AuthenticatedSession, SuperRole } from '../types'; export class TenantAuthService { private static SESSION_KEY = 'examforge_auth_session'; private static TOKEN_KEY = 'local_user_token'; /** * Safe getter for current active session */ static getSession(): AuthenticatedSession | null { try { const val = localStorage.getItem(this.SESSION_KEY); if (!val) return null; const parsed = JSON.parse(val) as AuthenticatedSession; // Revoke if expired if (Date.now() > parsed.expiresAt) { this.clearSession(); return null; } return parsed; } catch { return null; } } /** * Save active credentials locally */ static saveSession(session: AuthenticatedSession) { try { localStorage.setItem(this.SESSION_KEY, JSON.stringify(session)); localStorage.setItem(this.TOKEN_KEY, session.token); } catch (e) { console.warn("Storage failed to update session:", e); } } /** * Clear active trace credentials on logout */ static clearSession() { localStorage.removeItem(this.SESSION_KEY); localStorage.removeItem(this.TOKEN_KEY); localStorage.removeItem('local_user_profile'); } /** * Execute authentication using Tenant boundaries */ static async loginTenant(payload: { idOrEmail: string; pinOrPass: string; schoolId: string; }): Promise { const isEmail = payload.idOrEmail.includes('@'); const endpoint = isEmail ? '/api/auth/login-email' : '/api/auth/login'; const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Tenant-School-ID': payload.schoolId, // Strict school boundary header }, body: JSON.stringify({ id: payload.idOrEmail, email: payload.idOrEmail, pin: payload.pinOrPass, password: payload.pinOrPass, schoolId: payload.schoolId }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Verification failed.' })); throw new Error(errorData.error || 'Failed to establish tenant session.'); } const { user, customToken } = await response.json(); const formedSession: AuthenticatedSession = { uid: user.id || user.uid, role: (user.role as SuperRole) || 'student', schoolId: payload.schoolId || user.schoolId || 'pilot-default-school', email: user.email, fullName: user.fullName || 'Anonymous Candidate', token: customToken || localStorage.getItem(this.TOKEN_KEY) || '', issuedAt: Date.now(), expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours }; this.saveSession(formedSession); return formedSession; } /** * Enforces role access verification dynamically in React routers or hooks */ static authorizeRole(requiredRoles: SuperRole[]): boolean { const s = this.getSession(); if (!s) return false; return requiredRoles.includes(s.role); } }