/** * Authentication Provider model for Stack.Gallery Registry * Stores OAuth/OIDC and LDAP provider configurations */ import * as plugins from '../plugins.ts'; import type { IAuthProvider, TAuthProviderType, TAuthProviderStatus, IOAuthConfig, ILdapConfig, IAttributeMapping, IProvisioningSettings, } from '../interfaces/auth.interfaces.ts'; import { db } from './db.ts'; const DEFAULT_ATTRIBUTE_MAPPING: IAttributeMapping = { email: 'email', username: 'preferred_username', displayName: 'name', }; const DEFAULT_PROVISIONING: IProvisioningSettings = { jitEnabled: true, autoLinkByEmail: true, }; @plugins.smartdata.Collection(() => db) export class AuthProvider extends plugins.smartdata.SmartDataDbDoc implements IAuthProvider { @plugins.smartdata.unI() public id: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index({ unique: true }) public name: string = ''; // URL-safe slug identifier @plugins.smartdata.svDb() @plugins.smartdata.searchable() public displayName: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public type: TAuthProviderType = 'oidc'; @plugins.smartdata.svDb() @plugins.smartdata.index() public status: TAuthProviderStatus = 'disabled'; @plugins.smartdata.svDb() public priority: number = 100; // Lower = shown first in UI // Type-specific config (only one should be populated based on type) @plugins.smartdata.svDb() public oauthConfig?: IOAuthConfig; @plugins.smartdata.svDb() public ldapConfig?: ILdapConfig; @plugins.smartdata.svDb() public attributeMapping: IAttributeMapping = DEFAULT_ATTRIBUTE_MAPPING; @plugins.smartdata.svDb() public provisioning: IProvisioningSettings = DEFAULT_PROVISIONING; @plugins.smartdata.svDb() @plugins.smartdata.index() public createdAt: Date = new Date(); @plugins.smartdata.svDb() public updatedAt: Date = new Date(); @plugins.smartdata.svDb() public createdById: string = ''; // Connection test tracking @plugins.smartdata.svDb() public lastTestedAt?: Date; @plugins.smartdata.svDb() public lastTestResult?: 'success' | 'failure'; @plugins.smartdata.svDb() public lastTestError?: string; /** * Find provider by ID */ public static async findById(id: string): Promise { return await AuthProvider.getInstance({ id }); } /** * Find provider by name (slug) */ public static async findByName(name: string): Promise { return await AuthProvider.getInstance({ name: name.toLowerCase() }); } /** * Get all active providers (for login page) */ public static async getActiveProviders(): Promise { const providers = await AuthProvider.getInstances({ status: 'active' }); return providers.sort((a, b) => a.priority - b.priority); } /** * Get all providers (for admin) */ public static async getAllProviders(): Promise { const providers = await AuthProvider.getInstances({}); return providers.sort((a, b) => a.priority - b.priority); } /** * Create a new OAuth/OIDC provider */ public static async createOAuthProvider(data: { name: string; displayName: string; oauthConfig: IOAuthConfig; attributeMapping?: IAttributeMapping; provisioning?: IProvisioningSettings; createdById: string; }): Promise { const provider = new AuthProvider(); provider.id = await AuthProvider.getNewId(); provider.name = data.name.toLowerCase().replace(/[^a-z0-9-]/g, '-'); provider.displayName = data.displayName; provider.type = 'oidc'; provider.status = 'disabled'; provider.oauthConfig = data.oauthConfig; provider.attributeMapping = data.attributeMapping || DEFAULT_ATTRIBUTE_MAPPING; provider.provisioning = data.provisioning || DEFAULT_PROVISIONING; provider.createdById = data.createdById; provider.createdAt = new Date(); provider.updatedAt = new Date(); await provider.save(); return provider; } /** * Create a new LDAP provider */ public static async createLdapProvider(data: { name: string; displayName: string; ldapConfig: ILdapConfig; attributeMapping?: IAttributeMapping; provisioning?: IProvisioningSettings; createdById: string; }): Promise { const provider = new AuthProvider(); provider.id = await AuthProvider.getNewId(); provider.name = data.name.toLowerCase().replace(/[^a-z0-9-]/g, '-'); provider.displayName = data.displayName; provider.type = 'ldap'; provider.status = 'disabled'; provider.ldapConfig = data.ldapConfig; provider.attributeMapping = data.attributeMapping || { email: 'mail', username: 'uid', displayName: 'displayName', }; provider.provisioning = data.provisioning || DEFAULT_PROVISIONING; provider.createdById = data.createdById; provider.createdAt = new Date(); provider.updatedAt = new Date(); await provider.save(); return provider; } /** * Update connection test result */ public async updateTestResult(success: boolean, error?: string): Promise { this.lastTestedAt = new Date(); this.lastTestResult = success ? 'success' : 'failure'; this.lastTestError = error; await this.save(); } /** * Lifecycle hook: Update timestamps before save */ public async beforeSave(): Promise { this.updatedAt = new Date(); if (!this.id) { this.id = await AuthProvider.getNewId(); } } /** * Get public info (for login page - no secrets) */ public toPublicInfo(): { id: string; name: string; displayName: string; type: TAuthProviderType; } { return { id: this.id, name: this.name, displayName: this.displayName, type: this.type, }; } /** * Get admin info (secrets masked) */ public toAdminInfo(): Record { const info: Record = { id: this.id, name: this.name, displayName: this.displayName, type: this.type, status: this.status, priority: this.priority, attributeMapping: this.attributeMapping, provisioning: this.provisioning, createdAt: this.createdAt, updatedAt: this.updatedAt, createdById: this.createdById, lastTestedAt: this.lastTestedAt, lastTestResult: this.lastTestResult, lastTestError: this.lastTestError, }; // Mask secrets in config if (this.oauthConfig) { info.oauthConfig = { ...this.oauthConfig, clientSecretEncrypted: this.oauthConfig.clientSecretEncrypted ? '********' : undefined, }; } if (this.ldapConfig) { info.ldapConfig = { ...this.ldapConfig, bindPasswordEncrypted: this.ldapConfig.bindPasswordEncrypted ? '********' : undefined, }; } return info; } }