feat(opsserver,web): replace the Angular UI and REST management layer with a TypedRequest-based ops server and bundled web frontend
This commit is contained in:
380
ts/opsserver/handlers/admin.handler.ts
Normal file
380
ts/opsserver/handlers/admin.handler.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
import * as plugins from '../../plugins.ts';
|
||||
import * as interfaces from '../../../ts_interfaces/index.ts';
|
||||
import type { OpsServer } from '../classes.opsserver.ts';
|
||||
import { requireAdminIdentity } from '../helpers/guards.ts';
|
||||
import { AuthProvider, PlatformSettings } from '../../models/index.ts';
|
||||
import { cryptoService } from '../../services/crypto.service.ts';
|
||||
import { externalAuthService } from '../../services/external.auth.service.ts';
|
||||
import { AuditService } from '../../services/audit.service.ts';
|
||||
|
||||
export class AdminHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
// Get Admin Providers
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAdminProviders>(
|
||||
'getAdminProviders',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const providers = await AuthProvider.getAllProviders();
|
||||
return {
|
||||
providers: providers.map((p) => p.toAdminInfo()),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to list providers');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Create Admin Provider
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateAdminProvider>(
|
||||
'createAdminProvider',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const { name, displayName, type, oauthConfig, ldapConfig, attributeMapping, provisioning } = dataArg;
|
||||
|
||||
// Validate required fields
|
||||
if (!name || !displayName || !type) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
'name, displayName, and type are required',
|
||||
);
|
||||
}
|
||||
|
||||
// Check name uniqueness
|
||||
const existing = await AuthProvider.findByName(name);
|
||||
if (existing) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Provider name already exists');
|
||||
}
|
||||
|
||||
// Validate type-specific config
|
||||
if (type === 'oidc' && !oauthConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
'oauthConfig is required for OIDC provider',
|
||||
);
|
||||
}
|
||||
if (type === 'ldap' && !ldapConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
'ldapConfig is required for LDAP provider',
|
||||
);
|
||||
}
|
||||
|
||||
let provider: AuthProvider;
|
||||
|
||||
if (type === 'oidc' && oauthConfig) {
|
||||
// Encrypt client secret
|
||||
const encryptedSecret = await cryptoService.encrypt(
|
||||
oauthConfig.clientSecretEncrypted,
|
||||
);
|
||||
|
||||
provider = await AuthProvider.createOAuthProvider({
|
||||
name,
|
||||
displayName,
|
||||
oauthConfig: {
|
||||
...oauthConfig,
|
||||
clientSecretEncrypted: encryptedSecret,
|
||||
},
|
||||
attributeMapping,
|
||||
provisioning,
|
||||
createdById: dataArg.identity.userId,
|
||||
});
|
||||
} else if (type === 'ldap' && ldapConfig) {
|
||||
// Encrypt bind password
|
||||
const encryptedPassword = await cryptoService.encrypt(
|
||||
ldapConfig.bindPasswordEncrypted,
|
||||
);
|
||||
|
||||
provider = await AuthProvider.createLdapProvider({
|
||||
name,
|
||||
displayName,
|
||||
ldapConfig: {
|
||||
...ldapConfig,
|
||||
bindPasswordEncrypted: encryptedPassword,
|
||||
},
|
||||
attributeMapping,
|
||||
provisioning,
|
||||
createdById: dataArg.identity.userId,
|
||||
});
|
||||
} else {
|
||||
throw new plugins.typedrequest.TypedResponseError('Invalid provider type');
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: dataArg.identity.userId,
|
||||
actorType: 'user',
|
||||
}).log('AUTH_PROVIDER_CREATED', 'auth_provider', {
|
||||
resourceId: provider.id,
|
||||
success: true,
|
||||
metadata: {
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
},
|
||||
});
|
||||
|
||||
return { provider: provider.toAdminInfo() };
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to create provider');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Get Admin Provider
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAdminProvider>(
|
||||
'getAdminProvider',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const provider = await AuthProvider.findById(dataArg.providerId);
|
||||
if (!provider) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Provider not found');
|
||||
}
|
||||
|
||||
return { provider: provider.toAdminInfo() };
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to get provider');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Update Admin Provider
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateAdminProvider>(
|
||||
'updateAdminProvider',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const provider = await AuthProvider.findById(dataArg.providerId);
|
||||
if (!provider) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Provider not found');
|
||||
}
|
||||
|
||||
// Update basic fields
|
||||
if (dataArg.displayName !== undefined) provider.displayName = dataArg.displayName;
|
||||
if (dataArg.status !== undefined) provider.status = dataArg.status as any;
|
||||
if (dataArg.priority !== undefined) provider.priority = dataArg.priority;
|
||||
|
||||
// Update OAuth config
|
||||
if (dataArg.oauthConfig && provider.oauthConfig) {
|
||||
const newOAuthConfig = { ...provider.oauthConfig, ...dataArg.oauthConfig };
|
||||
|
||||
// Encrypt new client secret if provided and not already encrypted
|
||||
if (
|
||||
dataArg.oauthConfig.clientSecretEncrypted &&
|
||||
!cryptoService.isEncrypted(dataArg.oauthConfig.clientSecretEncrypted)
|
||||
) {
|
||||
newOAuthConfig.clientSecretEncrypted = await cryptoService.encrypt(
|
||||
dataArg.oauthConfig.clientSecretEncrypted,
|
||||
);
|
||||
}
|
||||
|
||||
provider.oauthConfig = newOAuthConfig;
|
||||
}
|
||||
|
||||
// Update LDAP config
|
||||
if (dataArg.ldapConfig && provider.ldapConfig) {
|
||||
const newLdapConfig = { ...provider.ldapConfig, ...dataArg.ldapConfig };
|
||||
|
||||
// Encrypt new bind password if provided and not already encrypted
|
||||
if (
|
||||
dataArg.ldapConfig.bindPasswordEncrypted &&
|
||||
!cryptoService.isEncrypted(dataArg.ldapConfig.bindPasswordEncrypted)
|
||||
) {
|
||||
newLdapConfig.bindPasswordEncrypted = await cryptoService.encrypt(
|
||||
dataArg.ldapConfig.bindPasswordEncrypted,
|
||||
);
|
||||
}
|
||||
|
||||
provider.ldapConfig = newLdapConfig;
|
||||
}
|
||||
|
||||
// Update attribute mapping
|
||||
if (dataArg.attributeMapping) {
|
||||
provider.attributeMapping = {
|
||||
...provider.attributeMapping,
|
||||
...dataArg.attributeMapping,
|
||||
} as any;
|
||||
}
|
||||
|
||||
// Update provisioning settings
|
||||
if (dataArg.provisioning) {
|
||||
provider.provisioning = {
|
||||
...provider.provisioning,
|
||||
...dataArg.provisioning,
|
||||
} as any;
|
||||
}
|
||||
|
||||
await provider.save();
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: dataArg.identity.userId,
|
||||
actorType: 'user',
|
||||
}).log('AUTH_PROVIDER_UPDATED', 'auth_provider', {
|
||||
resourceId: provider.id,
|
||||
success: true,
|
||||
metadata: { providerName: provider.name },
|
||||
});
|
||||
|
||||
return { provider: provider.toAdminInfo() };
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to update provider');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Delete Admin Provider
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteAdminProvider>(
|
||||
'deleteAdminProvider',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const provider = await AuthProvider.findById(dataArg.providerId);
|
||||
if (!provider) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Provider not found');
|
||||
}
|
||||
|
||||
// Soft delete - disable instead of removing
|
||||
provider.status = 'disabled';
|
||||
await provider.save();
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: dataArg.identity.userId,
|
||||
actorType: 'user',
|
||||
}).log('AUTH_PROVIDER_DELETED', 'auth_provider', {
|
||||
resourceId: provider.id,
|
||||
success: true,
|
||||
metadata: { providerName: provider.name },
|
||||
});
|
||||
|
||||
return { message: 'Provider disabled' };
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to delete provider');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Test Admin Provider
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestAdminProvider>(
|
||||
'testAdminProvider',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const result = await externalAuthService.testConnection(dataArg.providerId);
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: dataArg.identity.userId,
|
||||
actorType: 'user',
|
||||
}).log('AUTH_PROVIDER_TESTED', 'auth_provider', {
|
||||
resourceId: dataArg.providerId,
|
||||
success: result.success,
|
||||
metadata: {
|
||||
result: result.success ? 'success' : 'failure',
|
||||
latencyMs: result.latencyMs,
|
||||
error: result.error,
|
||||
},
|
||||
});
|
||||
|
||||
return { result };
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to test provider');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Get Platform Settings
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformSettings>(
|
||||
'getPlatformSettings',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const settings = await PlatformSettings.get();
|
||||
return {
|
||||
settings: {
|
||||
id: settings.id,
|
||||
auth: settings.auth,
|
||||
updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : String(settings.updatedAt),
|
||||
updatedById: settings.updatedById,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to get settings');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Update Platform Settings
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdatePlatformSettings>(
|
||||
'updatePlatformSettings',
|
||||
async (dataArg) => {
|
||||
await requireAdminIdentity(this.opsServerRef.authHandler, dataArg);
|
||||
|
||||
try {
|
||||
const settings = await PlatformSettings.get();
|
||||
|
||||
if (dataArg.auth) {
|
||||
await settings.updateAuthSettings(dataArg.auth as any, dataArg.identity.userId);
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: dataArg.identity.userId,
|
||||
actorType: 'user',
|
||||
}).log('PLATFORM_SETTINGS_UPDATED', 'platform_settings', {
|
||||
resourceId: 'platform-settings',
|
||||
success: true,
|
||||
});
|
||||
|
||||
return {
|
||||
settings: {
|
||||
id: settings.id,
|
||||
auth: settings.auth,
|
||||
updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : String(settings.updatedAt),
|
||||
updatedById: settings.updatedById,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
|
||||
throw new plugins.typedrequest.TypedResponseError('Failed to update settings');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user