143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
|
|
/**
|
||
|
|
* 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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|