Files
registry/ts/models/auth.provider.ts

253 lines
6.8 KiB
TypeScript
Raw Normal View History

/**
* 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<AuthProvider, AuthProvider>
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<AuthProvider | null> {
return await AuthProvider.getInstance({ id });
}
/**
* Find provider by name (slug)
*/
public static async findByName(name: string): Promise<AuthProvider | null> {
return await AuthProvider.getInstance({ name: name.toLowerCase() });
}
/**
* Get all active providers (for login page)
*/
public static async getActiveProviders(): Promise<AuthProvider[]> {
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<AuthProvider[]> {
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<AuthProvider> {
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<AuthProvider> {
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<void> {
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<void> {
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<string, unknown> {
const info: Record<string, unknown> = {
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;
}
}