feat(app): wire dashboard administration flows

This commit is contained in:
2026-05-07 15:35:37 +00:00
parent e9eb9b4172
commit 91f06ccae1
91 changed files with 4087 additions and 5863 deletions
@@ -1,6 +1,7 @@
import * as plugins from '../plugins.js';
import type { Reception } from './classes.reception.js';
import { AppConnection } from './classes.appconnection.js';
import type { User } from './classes.user.js';
export class AppConnectionManager {
public receptionRef: Reception;
@@ -150,6 +151,7 @@ export class AppConnectionManager {
connectedAt: Date.now(),
connectedByUserId: user.id,
grantedScopes: app.data.oauthCredentials?.allowedScopes || [],
roleMappings: [],
};
await connection.save();
}
@@ -198,6 +200,116 @@ export class AppConnectionManager {
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_UpdateAppRoleMappings>(
'updateAppRoleMappings',
async (requestArg) => {
const jwtData = await this.receptionRef.jwtManager.verifyJWTAndGetData(requestArg.jwt);
const user = await this.receptionRef.userManager.CUser.getInstance({
id: jwtData.data.userId,
});
const connection = await this.updateAppRoleMappings({
user,
organizationId: requestArg.organizationId,
appId: requestArg.appId,
roleMappings: requestArg.roleMappings,
});
return {
success: true,
connection: await connection.createSavableObject(),
};
}
)
);
}
public async updateAppRoleMappings(optionsArg: {
user: User;
organizationId: string;
appId: string;
roleMappings: plugins.idpInterfaces.data.IAppRoleMapping[];
}) {
const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({
id: optionsArg.organizationId,
});
if (!organization) {
throw new plugins.typedrequest.TypedResponseError('Organization not found');
}
if (!await organization.checkIfUserIsAdmin(optionsArg.user)) {
throw new plugins.typedrequest.TypedResponseError('Only organization admins can manage app role mappings');
}
const app = await this.receptionRef.appManager.getAppById(optionsArg.appId);
if (!app) {
throw new plugins.typedrequest.TypedResponseError('App not found');
}
const connection = await this.CAppConnection.getInstance({
'data.organizationId': optionsArg.organizationId,
'data.appId': optionsArg.appId,
});
if (!connection || !connection.isActive()) {
throw new plugins.typedrequest.TypedResponseError('App must be connected before role mappings can be configured');
}
const availableRoleKeys = await this.receptionRef.organizationmanager.getAvailableRoleKeys(optionsArg.organizationId);
const cleanMappings = (optionsArg.roleMappings || []).map((mappingArg) => ({
orgRoleKey: this.receptionRef.organizationmanager.validateRoleKey(mappingArg.orgRoleKey),
appRoles: this.cleanStringList(mappingArg.appRoles),
permissions: this.cleanStringList(mappingArg.permissions),
scopes: this.cleanStringList(mappingArg.scopes),
})).filter((mappingArg) => mappingArg.appRoles.length || mappingArg.permissions.length || mappingArg.scopes.length);
const invalidRoleKeys = cleanMappings
.map((mappingArg) => mappingArg.orgRoleKey)
.filter((roleKeyArg) => !availableRoleKeys.includes(roleKeyArg));
if (invalidRoleKeys.length) {
throw new plugins.typedrequest.TypedResponseError(`Unknown organization roles: ${[...new Set(invalidRoleKeys)].join(', ')}.`);
}
const requestedScopes = cleanMappings.flatMap((mappingArg) => mappingArg.scopes);
const allowedScopes = app.data.oauthCredentials?.allowedScopes || [];
const grantedScopes = connection.data.grantedScopes || [];
const unsupportedScopes = requestedScopes.filter((scopeArg) => !allowedScopes.includes(scopeArg));
if (unsupportedScopes.length) {
throw new plugins.typedrequest.TypedResponseError(`Unsupported app scopes: ${[...new Set(unsupportedScopes)].join(', ')}.`);
}
const ungrantedScopes = requestedScopes.filter((scopeArg) => !grantedScopes.includes(scopeArg));
if (ungrantedScopes.length) {
throw new plugins.typedrequest.TypedResponseError(`Scopes not granted to this connection: ${[...new Set(ungrantedScopes)].join(', ')}.`);
}
connection.data.roleMappings = cleanMappings;
await connection.save();
await this.receptionRef.activityLogManager.logActivity(
optionsArg.user.id,
'org_app_role_mappings_updated',
`${optionsArg.user.data.email} updated ${cleanMappings.length} role mappings for ${app.data.name}.`,
{
targetId: connection.id,
targetType: 'app-connection',
}
);
await this.emitOrganizationAlert({
organizationId: optionsArg.organizationId,
eventType: 'org_app_role_mappings_updated',
severity: 'medium',
title: 'Organization app role mappings updated',
body: `${optionsArg.user.data.email} updated role mappings for ${app.data.name}.`,
actorUserId: optionsArg.user.id,
relatedEntityId: app.id,
relatedEntityType: 'global-app',
});
return connection;
}
private cleanStringList(valuesArg: string[]) {
return [...new Set((valuesArg || [])
.map((valueArg) => (valueArg || '').trim())
.filter(Boolean))];
}
/**