253 lines
6.8 KiB
TypeScript
253 lines
6.8 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|
|
}
|