import * as plugins from '../../plugins.ts'; import { logger } from '../../logging.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; export interface IJwtData { userId: string; status: 'loggedIn' | 'loggedOut'; expiresAt: number; } export class AdminHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); public smartjwtInstance!: plugins.smartjwt.SmartJwt; 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( 'adminLogin', async (dataArg) => { const expectedUsername = Deno.env.get('GITOPS_ADMIN_USERNAME') || 'admin'; const expectedPassword = Deno.env.get('GITOPS_ADMIN_PASSWORD') || 'admin'; if (dataArg.username !== expectedUsername || dataArg.password !== expectedPassword) { throw new plugins.typedrequest.TypedResponseError('Invalid credentials'); } const expiresAt = Date.now() + 24 * 3600 * 1000; const userId = 'admin'; const jwt = await this.smartjwtInstance.createJWT({ userId, status: 'loggedIn', expiresAt, }); logger.info(`User logged in: ${dataArg.username}`); return { identity: { jwt, userId, username: dataArg.username, expiresAt, role: 'admin' as const, }, }; }, ), ); // Logout this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'adminLogout', async (_dataArg) => { return { ok: true }; }, ), ); // Verify Identity this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'verifyIdentity', async (dataArg) => { if (!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 }; return { valid: true, identity: { jwt: dataArg.identity.jwt, userId: jwtData.userId, username: dataArg.identity.username, expiresAt: jwtData.expiresAt, role: dataArg.identity.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; try { const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt); if (jwtData.expiresAt < Date.now()) return false; if (jwtData.status !== 'loggedIn') return false; if (dataArg.identity.expiresAt !== jwtData.expiresAt) return false; if (dataArg.identity.userId !== jwtData.userId) return false; return true; } catch { return false; } }, { failedHint: 'identity is not valid', name: 'validIdentityGuard' }, ); }