/** * IntegrationKey Domain Entity * * Represents an API key for tenant authentication. * Keys are always visible — full plaintext stored persistently. * * @layer Domain */ export { IntegrationKeyStatus } from '@archer/domain'; export { KeyEnvironment } from '@archer/domain'; import { IntegrationKeyStatus } from '@archer/domain'; import { KeyEnvironment } from '@archer/domain'; interface IntegrationKeyProps { id: string; tenantId: string; key: string; keyPrefix: string; environment: KeyEnvironment; status: IntegrationKeyStatus; usageCount: number; expiresAt?: Date; lastUsedAt?: Date; createdAt: Date; updatedAt: Date; deletedAt?: Date; } /** * IntegrationKey Entity * * Encapsulates integration key business logic and security rules. * Use factory methods (create, fromPersistence) to construct instances. */ export class IntegrationKey { private constructor(private readonly props: IntegrationKeyProps) { this.validate(); } /** * Creates a new IntegrationKey instance */ static create(data: { id: string; tenantId: string; key: string; keyPrefix: string; environment: KeyEnvironment; expiresAt?: Date; }): IntegrationKey { const now = new Date(); return new IntegrationKey({ id: data.id, tenantId: data.tenantId, key: data.key, keyPrefix: data.keyPrefix, environment: data.environment, status: IntegrationKeyStatus.ACTIVE, usageCount: 0, expiresAt: data.expiresAt, createdAt: now, updatedAt: now, }); } /** * Reconstructs IntegrationKey from persistence layer */ static fromPersistence(data: { id: string; tenantId: string; key: string; keyPrefix: string; environment: KeyEnvironment; status: IntegrationKeyStatus; usageCount: number; expiresAt?: string | Date; lastUsedAt?: string | Date; createdAt: string | Date; updatedAt: string | Date; deletedAt?: string | Date; }): IntegrationKey { return new IntegrationKey({ id: data.id, tenantId: data.tenantId, key: data.key, keyPrefix: data.keyPrefix, environment: data.environment, status: data.status, usageCount: data.usageCount, expiresAt: data.expiresAt ? typeof data.expiresAt === 'string' ? new Date(data.expiresAt) : data.expiresAt : undefined, lastUsedAt: data.lastUsedAt ? typeof data.lastUsedAt === 'string' ? new Date(data.lastUsedAt) : data.lastUsedAt : undefined, createdAt: typeof data.createdAt === 'string' ? new Date(data.createdAt) : data.createdAt, updatedAt: typeof data.updatedAt === 'string' ? new Date(data.updatedAt) : data.updatedAt, deletedAt: data.deletedAt ? typeof data.deletedAt === 'string' ? new Date(data.deletedAt) : data.deletedAt : undefined, }); } private validate(): void { if (!this.props.tenantId) { throw new Error('Integration key must belong to a tenant'); } if (!this.props.key || this.props.key.length === 0) { throw new Error('Integration key cannot be empty'); } if (!this.props.keyPrefix || this.props.keyPrefix.length === 0) { throw new Error('Integration key prefix cannot be empty'); } } // Getters get id(): string { return this.props.id; } get tenantId(): string { return this.props.tenantId; } get key(): string { return this.props.key; } get keyPrefix(): string { return this.props.keyPrefix; } get environment(): KeyEnvironment { return this.props.environment; } get status(): IntegrationKeyStatus { return this.props.status; } get usageCount(): number { return this.props.usageCount; } get expiresAt(): Date | undefined { return this.props.expiresAt; } get lastUsedAt(): Date | undefined { return this.props.lastUsedAt; } get createdAt(): Date { return this.props.createdAt; } get updatedAt(): Date { return this.props.updatedAt; } get deletedAt(): Date | undefined { return this.props.deletedAt; } isExpired(): boolean { if (!this.props.expiresAt) { return false; } return this.props.expiresAt < new Date(); } isRevoked(): boolean { return this.props.status === IntegrationKeyStatus.REVOKED; } isActive(): boolean { return this.props.status === IntegrationKeyStatus.ACTIVE; } isValid(): boolean { return this.isActive() && !this.isExpired() && !this.props.deletedAt; } getDaysUntilExpiration(): number | null { if (!this.props.expiresAt) { return null; } const now = new Date(); const diff = this.props.expiresAt.getTime() - now.getTime(); return Math.ceil(diff / (1000 * 60 * 60 * 24)); } isExpiringSoon(): boolean { const days = this.getDaysUntilExpiration(); return days !== null && days > 0 && days <= 30; } revoke(): IntegrationKey { if (this.isRevoked()) { throw new Error('Integration key is already revoked'); } return new IntegrationKey({ ...this.props, status: IntegrationKeyStatus.REVOKED, updatedAt: new Date(), }); } markAsUsed(): IntegrationKey { return new IntegrationKey({ ...this.props, lastUsedAt: new Date(), updatedAt: new Date(), }); } update(updates: { status?: IntegrationKeyStatus; expiresAt?: Date; lastUsedAt?: Date; }): IntegrationKey { return new IntegrationKey({ ...this.props, ...updates, updatedAt: new Date(), }); } delete(): IntegrationKey { return new IntegrationKey({ ...this.props, status: IntegrationKeyStatus.REVOKED, deletedAt: new Date(), updatedAt: new Date(), }); } toPersistence(): { id: string; tenantId: string; key: string; keyPrefix: string; environment: KeyEnvironment; status: IntegrationKeyStatus; usageCount: number; expiresAt?: string; lastUsedAt?: string; createdAt: string; updatedAt: string; deletedAt?: string; } { return { id: this.props.id, tenantId: this.props.tenantId, key: this.props.key, keyPrefix: this.props.keyPrefix, environment: this.props.environment, status: this.props.status, usageCount: this.props.usageCount, expiresAt: this.props.expiresAt?.toISOString(), lastUsedAt: this.props.lastUsedAt?.toISOString(), createdAt: this.props.createdAt.toISOString(), updatedAt: this.props.updatedAt.toISOString(), deletedAt: this.props.deletedAt?.toISOString(), }; } }