feat(auth): Add external authentication (OAuth/OIDC & LDAP) with admin management, UI, and encryption support
This commit is contained in:
142
ts/models/external.identity.ts
Normal file
142
ts/models/external.identity.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user