import * as plugins from '../../plugins.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import { AuthService } from '../../services/auth.service.ts'; import { User } from '../../models/user.ts'; import { AuthProvider, PlatformSettings } from '../../models/index.ts'; export class AuthHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); private authService: AuthService; constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.authService = new AuthService(); } /** * Initialize auth handler - must be called after construction */ public async initialize(): Promise { this.registerHandlers(); } private registerHandlers(): void { // Login this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'login', async (dataArg) => { try { const { email, password } = dataArg; if (!email || !password) { throw new plugins.typedrequest.TypedResponseError('Email and password are required'); } const result = await this.authService.login(email, password); if (!result.success || !result.user || !result.accessToken || !result.refreshToken) { return { errorCode: result.errorCode, errorMessage: result.errorMessage, }; } const user = result.user; const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes const identity: interfaces.data.IIdentity = { jwt: result.accessToken, refreshJwt: result.refreshToken, userId: user.id, email: user.email, username: user.username, displayName: user.displayName, isSystemAdmin: user.isSystemAdmin, expiresAt, sessionId: result.sessionId!, }; return { identity, user: { id: user.id, email: user.email, username: user.username, displayName: user.displayName, avatarUrl: user.avatarUrl, isSystemAdmin: user.isSystemAdmin, isActive: user.isActive, createdAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : String(user.createdAt), lastLoginAt: user.lastLoginAt instanceof Date ? user.lastLoginAt.toISOString() : user.lastLoginAt ? String(user.lastLoginAt) : undefined, }, }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Login failed'); } }, ), ); // Refresh Token this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'refreshToken', async (dataArg) => { try { if (!dataArg.identity?.refreshJwt) { throw new plugins.typedrequest.TypedResponseError('Refresh token is required'); } const result = await this.authService.refresh(dataArg.identity.refreshJwt); if (!result.success || !result.user || !result.accessToken) { throw new plugins.typedrequest.TypedResponseError( result.errorMessage || 'Token refresh failed', ); } const user = result.user; const expiresAt = Date.now() + 15 * 60 * 1000; return { identity: { jwt: result.accessToken, refreshJwt: dataArg.identity.refreshJwt, userId: user.id, email: user.email, username: user.username, displayName: user.displayName, isSystemAdmin: user.isSystemAdmin, expiresAt, sessionId: result.sessionId || dataArg.identity.sessionId, }, }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Token refresh failed'); } }, ), ); // Logout this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'logout', async (dataArg) => { try { if (!dataArg.identity?.jwt) { throw new plugins.typedrequest.TypedResponseError('Identity required'); } if (dataArg.all) { const count = await this.authService.logoutAll(dataArg.identity.userId); return { message: `Logged out from ${count} sessions` }; } const sessionId = dataArg.sessionId || dataArg.identity.sessionId; if (sessionId) { await this.authService.logout(sessionId, { userId: dataArg.identity.userId, }); } return { message: 'Logged out successfully' }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Logout failed'); } }, ), ); // Get Me this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getMe', async (dataArg) => { try { if (!dataArg.identity?.jwt) { throw new plugins.typedrequest.TypedResponseError('Identity required'); } const validated = await this.authService.validateAccessToken(dataArg.identity.jwt); if (!validated) { throw new plugins.typedrequest.TypedResponseError('Invalid or expired token'); } const user = validated.user; return { user: { id: user.id, email: user.email, username: user.username, displayName: user.displayName, avatarUrl: user.avatarUrl, isSystemAdmin: user.isSystemAdmin, isActive: user.isActive, createdAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : String(user.createdAt), lastLoginAt: user.lastLoginAt instanceof Date ? user.lastLoginAt.toISOString() : user.lastLoginAt ? String(user.lastLoginAt) : undefined, }, }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to get user info'); } }, ), ); // Get Auth Providers (public) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getAuthProviders', async (_dataArg) => { try { const settings = await PlatformSettings.get(); const providers = await AuthProvider.getActiveProviders(); return { providers: providers.map((p) => p.toPublicInfo()), localAuthEnabled: settings.auth.localAuthEnabled, defaultProviderId: settings.auth.defaultProviderId, }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to get auth providers'); } }, ), ); } // Guard for valid identity - validates JWT via AuthService public validIdentityGuard = new plugins.smartguard.Guard<{ identity: interfaces.data.IIdentity; }>( async (dataArg) => { if (!dataArg.identity?.jwt) return false; try { const validated = await this.authService.validateAccessToken(dataArg.identity.jwt); if (!validated) return false; // Verify the userId matches the identity claim if (dataArg.identity.userId !== validated.user.id) return false; return true; } catch { return false; } }, { failedHint: 'identity is not valid', name: 'validIdentityGuard' }, ); // Guard for admin identity - validates JWT + checks isSystemAdmin public adminIdentityGuard = new plugins.smartguard.Guard<{ identity: interfaces.data.IIdentity; }>( async (dataArg) => { const isValid = await this.validIdentityGuard.exec(dataArg); if (!isValid) return false; // Check isSystemAdmin claim from the identity if (!dataArg.identity.isSystemAdmin) return false; // Double-check from database const user = await User.findById(dataArg.identity.userId); if (!user || !user.isSystemAdmin) return false; return true; }, { failedHint: 'user is not admin', name: 'adminIdentityGuard' }, ); }