/** * ApiToken model for Stack.Gallery Registry */ import * as plugins from '../plugins.ts'; import type { IApiToken, ITokenScope, TRegistryProtocol } from '../interfaces/auth.interfaces.ts'; import { db } from './db.ts'; @plugins.smartdata.Collection(() => db) export class ApiToken extends plugins.smartdata.SmartDataDbDoc implements IApiToken { @plugins.smartdata.unI() public id: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public userId: string = ''; @plugins.smartdata.svDb() public name: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index({ unique: true }) public tokenHash: string = ''; @plugins.smartdata.svDb() public tokenPrefix: string = ''; @plugins.smartdata.svDb() public protocols: TRegistryProtocol[] = []; @plugins.smartdata.svDb() public scopes: ITokenScope[] = []; @plugins.smartdata.svDb() @plugins.smartdata.index() public expiresAt?: Date; @plugins.smartdata.svDb() public lastUsedAt?: Date; @plugins.smartdata.svDb() public lastUsedIp?: string; @plugins.smartdata.svDb() public usageCount: number = 0; @plugins.smartdata.svDb() @plugins.smartdata.index() public isRevoked: boolean = false; @plugins.smartdata.svDb() public revokedAt?: Date; @plugins.smartdata.svDb() public revokedReason?: string; @plugins.smartdata.svDb() @plugins.smartdata.index() public createdAt: Date = new Date(); @plugins.smartdata.svDb() public createdIp?: string; /** * Find token by hash */ public static async findByHash(tokenHash: string): Promise { return await ApiToken.getInstance({ tokenHash, isRevoked: false, }); } /** * Find token by prefix (for listing) */ public static async findByPrefix(tokenPrefix: string): Promise { return await ApiToken.getInstance({ tokenPrefix, }); } /** * Get all tokens for a user */ public static async getUserTokens(userId: string): Promise { return await ApiToken.getInstances({ userId, isRevoked: false, }); } /** * Check if token is valid (not expired, not revoked) */ public isValid(): boolean { if (this.isRevoked) return false; if (this.expiresAt && this.expiresAt < new Date()) return false; return true; } /** * Record token usage */ public async recordUsage(ip?: string): Promise { this.lastUsedAt = new Date(); this.lastUsedIp = ip; this.usageCount += 1; await this.save(); } /** * Revoke token */ public async revoke(reason?: string): Promise { this.isRevoked = true; this.revokedAt = new Date(); this.revokedReason = reason; await this.save(); } /** * Check if token has permission for protocol */ public hasProtocol(protocol: TRegistryProtocol): boolean { return this.protocols.includes(protocol) || this.protocols.includes('*' as TRegistryProtocol); } /** * Check if token has permission for action on resource */ public hasScope( protocol: TRegistryProtocol, organizationId?: string, repositoryId?: string, action?: string ): boolean { for (const scope of this.scopes) { // Check protocol if (scope.protocol !== '*' && scope.protocol !== protocol) continue; // Check organization if (scope.organizationId && scope.organizationId !== organizationId) continue; // Check repository if (scope.repositoryId && scope.repositoryId !== repositoryId) continue; // Check action if (action && !scope.actions.includes('*') && !scope.actions.includes(action as never)) continue; return true; } return false; } /** * Lifecycle hook */ public async beforeSave(): Promise { if (!this.id) { this.id = await ApiToken.getNewId(); } } }