import * as plugins from '../../plugins.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; export interface IJwtData { userId: string; role: 'admin'; status: 'loggedIn' | 'loggedOut'; expiresAt: number; } export class AdminHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); public smartjwtInstance!: plugins.smartjwt.SmartJwt; private revokedTokens = new Set(); private failedLoginAttempts = new Map(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); } public async initialize(): Promise { this.smartjwtInstance = new plugins.smartjwt.SmartJwt(); await this.smartjwtInstance.init(); await this.smartjwtInstance.createNewKeyPair(); this.registerHandlers(); } private registerHandlers(): void { // Login this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'adminLoginWithUsernameAndPassword', async (dataArg) => { this.assertLoginNotRateLimited(dataArg.username); const adminPassword = this.opsServerRef.objectStorageRef.config.adminPassword; if (dataArg.username !== 'admin' || dataArg.password !== adminPassword) { this.recordFailedLogin(dataArg.username); await this.opsServerRef.objectStorageRef.auditLogger.log({ actorUserId: dataArg.username || 'anonymous', action: 'admin.login', targetType: 'adminSession', success: false, message: 'Invalid credentials', }); throw new plugins.typedrequest.TypedResponseError('Invalid credentials'); } this.failedLoginAttempts.delete(dataArg.username); const expiresAt = Date.now() + 24 * 3600 * 1000; const userId = 'admin'; const jwt = await this.smartjwtInstance.createJWT({ userId, role: 'admin', status: 'loggedIn', expiresAt, }); await this.opsServerRef.objectStorageRef.auditLogger.log({ actorUserId: userId, action: 'admin.login', targetType: 'adminSession', success: true, }); console.log('Admin user logged in'); return { identity: { jwt, userId, username: 'admin', expiresAt, role: 'admin' as const, }, }; }, ), ); // Logout this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'adminLogout', async (dataArg) => { if (dataArg.identity?.jwt) { this.revokedTokens.add(dataArg.identity.jwt); await this.opsServerRef.objectStorageRef.auditLogger.log({ actorUserId: dataArg.identity.userId, action: 'admin.logout', targetType: 'adminSession', success: true, }); } return { ok: true }; }, ), ); // Verify Identity this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'verifyIdentity', async (dataArg) => { if (!dataArg.identity?.jwt) { return { valid: false }; } if (this.revokedTokens.has(dataArg.identity.jwt)) return { valid: false }; try { const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt); if (jwtData.expiresAt < Date.now()) return { valid: false }; if (jwtData.status !== 'loggedIn') return { valid: false }; if (jwtData.role !== 'admin') return { valid: false }; return { valid: true, identity: { jwt: dataArg.identity.jwt, userId: jwtData.userId, username: jwtData.userId, expiresAt: jwtData.expiresAt, role: jwtData.role, }, }; } catch { return { valid: false }; } }, ), ); } // Guard for valid identity public validIdentityGuard = new plugins.smartguard.Guard<{ identity: interfaces.data.IIdentity; }>( async (dataArg) => { if (!dataArg.identity?.jwt) return false; if (this.revokedTokens.has(dataArg.identity.jwt)) return false; try { const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt); if (jwtData.expiresAt < Date.now()) return false; if (jwtData.status !== 'loggedIn') return false; if (jwtData.role !== 'admin') return false; if (dataArg.identity.expiresAt !== jwtData.expiresAt) return false; if (dataArg.identity.userId !== jwtData.userId) return false; if (dataArg.identity.role !== jwtData.role) return false; return true; } catch { return false; } }, { failedHint: 'identity is not valid', name: 'validIdentityGuard' }, ); // Guard for admin identity public adminIdentityGuard = new plugins.smartguard.Guard<{ identity: interfaces.data.IIdentity; }>( async (dataArg) => { return await this.validIdentityGuard.exec(dataArg); }, { failedHint: 'user is not admin', name: 'adminIdentityGuard' }, ); private assertLoginNotRateLimited(username: string): void { const attempt = this.failedLoginAttempts.get(username); if (!attempt) return; const windowMs = 60 * 1000; if (Date.now() - attempt.firstAttemptAt > windowMs) { this.failedLoginAttempts.delete(username); return; } if (attempt.count >= 5) { throw new plugins.typedrequest.TypedResponseError('Too many failed login attempts'); } } private recordFailedLogin(username: string): void { const now = Date.now(); const attempt = this.failedLoginAttempts.get(username); if (!attempt || now - attempt.firstAttemptAt > 60 * 1000) { this.failedLoginAttempts.set(username, { count: 1, firstAttemptAt: now }); return; } attempt.count++; } }