Files
registry/ts/models/external.identity.ts

143 lines
3.9 KiB
TypeScript
Raw Normal View History

/**
* 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<ExternalIdentity, ExternalIdentity>
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<string, unknown>;
@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<ExternalIdentity | null> {
return await ExternalIdentity.getInstance({ id });
}
/**
* Find by provider and external ID (unique combination)
*/
public static async findByExternalId(
providerId: string,
externalId: string
): Promise<ExternalIdentity | null> {
return await ExternalIdentity.getInstance({ providerId, externalId });
}
/**
* Find all identities for a user
*/
public static async findByUserId(userId: string): Promise<ExternalIdentity[]> {
return await ExternalIdentity.getInstances({ userId });
}
/**
* Find identity by user and provider
*/
public static async findByUserAndProvider(
userId: string,
providerId: string
): Promise<ExternalIdentity | null> {
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<string, unknown>;
}): Promise<ExternalIdentity> {
// 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<void> {
this.lastLoginAt = new Date();
await this.save();
}
/**
* Update attributes from provider
*/
public async updateAttributes(data: {
externalEmail?: string;
externalUsername?: string;
rawAttributes?: Record<string, unknown>;
}): Promise<void> {
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<void> {
if (!this.id) {
this.id = await ExternalIdentity.getNewId();
}
}
}