import * as plugins from '../../plugins.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import { requireValidIdentity } from '../helpers/guards.ts'; export class RegistryHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get registry tags this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRegistryTags', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const tags = await this.opsServerRef.oneboxRef.registry.getImageTags(dataArg.serviceName); return { tags }; }, ), ); // Get registry tokens this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRegistryTokens', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const rawTokens = this.opsServerRef.oneboxRef.database.getAllRegistryTokens(); const now = Date.now(); const tokens = rawTokens.map((token: any) => { const isExpired = token.expiresAt !== null && token.expiresAt < now; let scopeDisplay: string; if (token.scope === 'all') { scopeDisplay = 'All services'; } else if (Array.isArray(token.scope)) { scopeDisplay = token.scope.length === 1 ? token.scope[0] : `${token.scope.length} services`; } else { scopeDisplay = 'Unknown'; } return { id: token.id!, name: token.name, type: token.type, scope: token.scope, scopeDisplay, expiresAt: token.expiresAt, createdAt: token.createdAt, lastUsedAt: token.lastUsedAt, createdBy: token.createdBy, isExpired, }; }); return { tokens }; }, ), ); // Create registry token this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createRegistryToken', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const config = dataArg.tokenConfig; // Calculate expiration const now = Date.now(); let expiresAt: number | null = null; if (config.expiresIn !== 'never') { const daysMap: Record = { '30d': 30, '90d': 90, '365d': 365 }; const days = daysMap[config.expiresIn]; if (days) expiresAt = now + days * 24 * 60 * 60 * 1000; } // Generate token const plainToken = crypto.randomUUID() + crypto.randomUUID(); const encoder = new TextEncoder(); const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(plainToken)); const hashArray = Array.from(new Uint8Array(hashBuffer)); const tokenHash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); const token = this.opsServerRef.oneboxRef.database.createRegistryToken({ name: config.name, tokenHash, type: config.type, scope: config.scope, expiresAt, createdAt: now, lastUsedAt: null, createdBy: dataArg.identity.username, }); let scopeDisplay: string; if (token.scope === 'all') { scopeDisplay = 'All services'; } else if (Array.isArray(token.scope)) { scopeDisplay = token.scope.length === 1 ? token.scope[0] : `${token.scope.length} services`; } else { scopeDisplay = 'Unknown'; } return { result: { token: { id: token.id!, name: token.name, type: token.type, scope: token.scope, scopeDisplay, expiresAt: token.expiresAt, createdAt: token.createdAt, lastUsedAt: token.lastUsedAt, createdBy: token.createdBy, isExpired: false, }, plainToken, }, }; }, ), ); // Delete registry token this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteRegistryToken', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const token = this.opsServerRef.oneboxRef.database.getRegistryTokenById(dataArg.tokenId); if (!token) { throw new plugins.typedrequest.TypedResponseError('Token not found'); } this.opsServerRef.oneboxRef.database.deleteRegistryToken(dataArg.tokenId); return { ok: true }; }, ), ); } }