/** * External Identity model for Stack.Gallery Registry * Links users to external authentication provider accounts */ import * as plugins from '../plugins.ts'; import type { IExternalIdentity } from '../interfaces/auth.interfaces.ts'; import { db } from './db.ts'; @plugins.smartdata.Collection(() => db) export class ExternalIdentity extends plugins.smartdata.SmartDataDbDoc implements IExternalIdentity { @plugins.smartdata.unI() public id: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public userId: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public providerId: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public externalId: string = ''; // ID from the external provider @plugins.smartdata.svDb() public externalEmail?: string; @plugins.smartdata.svDb() public externalUsername?: string; @plugins.smartdata.svDb() public rawAttributes?: Record; @plugins.smartdata.svDb() public lastLoginAt?: Date; @plugins.smartdata.svDb() @plugins.smartdata.index() public createdAt: Date = new Date(); /** * Find by ID */ public static async findById(id: string): Promise { return await ExternalIdentity.getInstance({ id }); } /** * Find by provider and external ID (unique combination) */ public static async findByExternalId( providerId: string, externalId: string ): Promise { return await ExternalIdentity.getInstance({ providerId, externalId }); } /** * Find all identities for a user */ public static async findByUserId(userId: string): Promise { return await ExternalIdentity.getInstances({ userId }); } /** * Find identity by user and provider */ public static async findByUserAndProvider( userId: string, providerId: string ): Promise { return await ExternalIdentity.getInstance({ userId, providerId }); } /** * Create a new external identity link */ public static async createIdentity(data: { userId: string; providerId: string; externalId: string; externalEmail?: string; externalUsername?: string; rawAttributes?: Record; }): Promise { // Check if this external ID is already linked const existing = await ExternalIdentity.findByExternalId(data.providerId, data.externalId); if (existing) { throw new Error('This external account is already linked to a user'); } const identity = new ExternalIdentity(); identity.id = await ExternalIdentity.getNewId(); identity.userId = data.userId; identity.providerId = data.providerId; identity.externalId = data.externalId; identity.externalEmail = data.externalEmail; identity.externalUsername = data.externalUsername; identity.rawAttributes = data.rawAttributes; identity.lastLoginAt = new Date(); identity.createdAt = new Date(); await identity.save(); return identity; } /** * Update last login time */ public async updateLastLogin(): Promise { this.lastLoginAt = new Date(); await this.save(); } /** * Update attributes from provider */ public async updateAttributes(data: { externalEmail?: string; externalUsername?: string; rawAttributes?: Record; }): Promise { if (data.externalEmail !== undefined) this.externalEmail = data.externalEmail; if (data.externalUsername !== undefined) this.externalUsername = data.externalUsername; if (data.rawAttributes !== undefined) this.rawAttributes = data.rawAttributes; this.lastLoginAt = new Date(); await this.save(); } /** * Lifecycle hook: Generate ID before save */ public async beforeSave(): Promise { if (!this.id) { this.id = await ExternalIdentity.getNewId(); } } }