import * as plugins from '../../plugins.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import { requireValidIdentity } from '../helpers/guards.ts'; import { ApiToken } from '../../models/index.ts'; import { TokenService } from '../../services/token.service.ts'; import { PermissionService } from '../../services/permission.service.ts'; export class TokenHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); private tokenService = new TokenService(); private permissionService = new PermissionService(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get Tokens this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getTokens', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { let tokens; if (dataArg.organizationId) { // Check if user can manage org const canManage = await this.permissionService.canManageOrganization( dataArg.identity.userId, dataArg.organizationId, ); if (!canManage) { throw new plugins.typedrequest.TypedResponseError( 'Not authorized to view organization tokens', ); } tokens = await this.tokenService.getOrgTokens(dataArg.organizationId); } else { tokens = await this.tokenService.getUserTokens(dataArg.identity.userId); } return { tokens: tokens.map((t) => ({ id: t.id, name: t.name, tokenPrefix: t.tokenPrefix, protocols: t.protocols as interfaces.data.TRegistryProtocol[], scopes: t.scopes as interfaces.data.ITokenScope[], organizationId: t.organizationId, createdById: t.createdById, expiresAt: t.expiresAt instanceof Date ? t.expiresAt.toISOString() : t.expiresAt ? String(t.expiresAt) : undefined, lastUsedAt: t.lastUsedAt instanceof Date ? t.lastUsedAt.toISOString() : t.lastUsedAt ? String(t.lastUsedAt) : undefined, usageCount: t.usageCount, createdAt: t.createdAt instanceof Date ? t.createdAt.toISOString() : String(t.createdAt), })), }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to list tokens'); } }, ), ); // Create Token this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createToken', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { name, organizationId, protocols, scopes, expiresInDays } = dataArg; if (!name) { throw new plugins.typedrequest.TypedResponseError('Token name is required'); } if (!protocols || protocols.length === 0) { throw new plugins.typedrequest.TypedResponseError('At least one protocol is required'); } if (!scopes || scopes.length === 0) { throw new plugins.typedrequest.TypedResponseError('At least one scope is required'); } // Validate protocols const validProtocols = ['npm', 'oci', 'maven', 'cargo', 'composer', 'pypi', 'rubygems', '*']; for (const protocol of protocols) { if (!validProtocols.includes(protocol)) { throw new plugins.typedrequest.TypedResponseError(`Invalid protocol: ${protocol}`); } } // Validate scopes for (const scope of scopes) { if (!scope.protocol || !scope.actions || scope.actions.length === 0) { throw new plugins.typedrequest.TypedResponseError('Invalid scope configuration'); } } // If creating org token, verify permission if (organizationId) { const canManage = await this.permissionService.canManageOrganization( dataArg.identity.userId, organizationId, ); if (!canManage) { throw new plugins.typedrequest.TypedResponseError( 'Not authorized to create organization tokens', ); } } const result = await this.tokenService.createToken({ userId: dataArg.identity.userId, organizationId, createdById: dataArg.identity.userId, name, protocols: protocols as any[], scopes: scopes as any[], expiresInDays, }); return { token: { id: result.token.id, name: result.token.name, token: result.rawToken, tokenPrefix: result.token.tokenPrefix, protocols: result.token.protocols as interfaces.data.TRegistryProtocol[], scopes: result.token.scopes as interfaces.data.ITokenScope[], organizationId: result.token.organizationId, createdById: result.token.createdById, expiresAt: result.token.expiresAt instanceof Date ? result.token.expiresAt.toISOString() : result.token.expiresAt ? String(result.token.expiresAt) : undefined, usageCount: result.token.usageCount, createdAt: result.token.createdAt instanceof Date ? result.token.createdAt.toISOString() : String(result.token.createdAt), warning: 'Store this token securely. It will not be shown again.', }, }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to create token'); } }, ), ); // Revoke Token this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'revokeToken', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { tokenId } = dataArg; // Check if it's a personal token const userTokens = await this.tokenService.getUserTokens(dataArg.identity.userId); let token = userTokens.find((t) => t.id === tokenId); if (!token) { // Check if it's an org token the user can manage const anyToken = await ApiToken.getInstance({ id: tokenId, isRevoked: false }); if (anyToken?.organizationId) { const canManage = await this.permissionService.canManageOrganization( dataArg.identity.userId, anyToken.organizationId, ); if (canManage) { token = anyToken; } } } if (!token) { throw new plugins.typedrequest.TypedResponseError('Token not found'); } const success = await this.tokenService.revokeToken(tokenId, 'user_revoked'); if (!success) { throw new plugins.typedrequest.TypedResponseError('Failed to revoke token'); } return { message: 'Token revoked successfully' }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to revoke token'); } }, ), ); } }