Files
app/ts_web/elements/account/content.ts
T

872 lines
34 KiB
TypeScript
Raw Normal View History

import * as plugins from '../../plugins.js';
import * as states from '../../states/accountstate.js';
import { IdpState } from '../../states/idp.state.js';
import { BulkInviteModal } from './bulk-invite-modal.js';
import { CreateOrgModal } from './create-org-modal.js';
import {
customElement,
DeesElement,
html,
cssManager,
css,
state,
type TemplateResult
} from '@design.estate/dees-element';
declare global {
interface HTMLElementTagNameMap {
'idp-accountcontent': IdpAccountContent;
}
}
@customElement('idp-accountcontent')
export class IdpAccountContent extends DeesElement {
public subrouter: plugins.deesDomtools.plugins.smartrouter.SmartRouter;
private dataLoadRun = 0;
@state()
private accessor adminPage: plugins.idpCatalog.IdpAdminShell['page'] = 'overview';
@state()
private accessor adminUser: plugins.idpCatalog.IIdpAdminUser = {
name: 'Loading account',
email: '',
};
@state()
private accessor adminOrgs: plugins.idpCatalog.IIdpAdminOrg[] = [];
@state()
private accessor selectedOrgId = '';
@state()
private accessor globalAdmin = false;
@state()
private accessor dataLoading = false;
@state()
private accessor dataError = '';
@state()
private accessor sessions: plugins.idpCatalog.IIdpAdminSession[] = [];
@state()
private accessor activities: plugins.idpCatalog.IIdpAdminActivity[] = [];
@state()
private accessor orgMembers: plugins.idpCatalog.IIdpAdminMember[] = [];
@state()
private accessor orgInvitations: plugins.idpCatalog.IIdpAdminInvitation[] = [];
@state()
private accessor orgRoleDefinitions: plugins.idpCatalog.IIdpAdminOrgRoleDefinition[] = [];
@state()
private accessor orgApps: plugins.idpCatalog.IIdpAdminApp[] = [];
@state()
private accessor adminApps: plugins.idpCatalog.IIdpAdminApp[] = [];
@state()
private accessor passportDevices: plugins.idpCatalog.IIdpAdminPassportDevice[] = [];
@state()
private accessor passportEnrollment: plugins.idpCatalog.IIdpAdminPassportEnrollment | null = null;
@state()
private accessor credentialMessage = '';
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
height: 100vh;
max-height: 100vh;
min-height: 0;
width: 100%;
overflow: hidden;
background: var(--idp-bg, hsl(240 10% 3.9%));
}
:host([hidden]) {
display: none;
}
idp-admin-shell {
height: 100%;
}
`,
];
public render(): TemplateResult {
return html`
<idp-admin-shell
.page=${this.adminPage}
.user=${this.adminUser}
.orgs=${this.adminOrgs}
.selectedOrgId=${this.selectedOrgId}
.globalAdmin=${this.globalAdmin}
.dataLoading=${this.dataLoading}
.dataError=${this.dataError}
.sessions=${this.sessions}
.activities=${this.activities}
.orgMembers=${this.orgMembers}
.orgInvitations=${this.orgInvitations}
.orgRoleDefinitions=${this.orgRoleDefinitions}
.orgApps=${this.orgApps}
.adminApps=${this.adminApps}
.passportDevices=${this.passportDevices}
.passportEnrollment=${this.passportEnrollment}
.credentialMessage=${this.credentialMessage}
@idp-admin-navigate=${this.handleAdminNavigate}
@idp-admin-org-select=${this.handleOrgSelect}
@idp-admin-org-create=${this.handleOrgCreate}
@idp-admin-org-update=${this.handleOrgUpdate}
@idp-admin-org-transfer=${this.handleOrgTransfer}
@idp-admin-org-delete=${this.handleOrgDelete}
@idp-admin-session-revoke=${this.handleSessionRevoke}
@idp-admin-app-toggle=${this.handleAppToggle}
@idp-admin-password-change=${this.handlePasswordChange}
@idp-admin-passport-enroll=${this.handlePassportEnroll}
@idp-admin-passport-revoke=${this.handlePassportRevoke}
@idp-admin-member-invite=${this.handleMemberInvite}
@idp-admin-member-remove=${this.handleMemberRemove}
@idp-admin-member-roles-update=${this.handleMemberRolesUpdate}
@idp-admin-invitation-resend=${this.handleInvitationResend}
@idp-admin-invitation-cancel=${this.handleInvitationCancel}
@idp-admin-org-role-upsert=${this.handleOrgRoleUpsert}
@idp-admin-org-role-delete=${this.handleOrgRoleDelete}
@idp-admin-app-role-mappings-update=${this.handleAppRoleMappingsUpdate}
></idp-admin-shell>
`;
}
private setAdminPage(pageArg: plugins.idpCatalog.IdpAdminShell['page']) {
this.adminPage = pageArg;
if (this.subrouter) {
void this.loadAdminShellData();
}
}
private getSelectedOrgSlug(): string {
const currentState = states.accountState.getState();
const selectedOrg = currentState.selectedOrg
|| currentState.organizations.find((orgArg) => orgArg.id === this.selectedOrgId)
|| currentState.organizations[0];
return selectedOrg?.data?.slug || this.adminOrgs.find((orgArg) => orgArg.id === this.selectedOrgId)?.slug || this.adminOrgs[0]?.slug || '';
}
private getPathForPage(pageArg: plugins.idpCatalog.IdpAdminShell['page']): string | null {
const orgSlug = this.getSelectedOrgSlug();
const orgPath = (suffixArg = '') => orgSlug ? `/org/${orgSlug}${suffixArg}` : null;
const pageMap: Record<plugins.idpCatalog.IdpAdminShell['page'], string | null> = {
overview: '/overview',
profile: '/account/profile',
security: '/account/security',
sessions: '/account/sessions',
apps: '/account/apps',
'org-general': orgPath(),
'org-settings': orgPath('/settings'),
'org-members': orgPath('/users'),
'org-apps': orgPath('/apps'),
support: '/support',
'ga-users': '/admin/users',
'ga-orgs': '/admin/orgs',
'ga-apps': '/admin/apps',
};
return pageMap[pageArg];
}
private pushDashPath(pathArg: string) {
const normalizedPath = pathArg || '';
const absolutePath = `/dash${normalizedPath}`.replace(/\/$/, '') || '/dash';
if (window.location.pathname.replace(/\/$/, '') === absolutePath) {
return;
}
this.subrouter.pushUrl(normalizedPath);
}
private async handleAdminNavigate(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminNavigateEventDetail>) {
const page = eventArg.detail.page;
this.setAdminPage(page);
const path = this.getPathForPage(page);
if (path !== null) {
this.pushDashPath(path);
}
}
private async handleOrgSelect(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgSelectEventDetail>) {
const currentState = states.accountState.getState();
const selectedOrg = currentState.organizations.find((orgArg) => orgArg.id === eventArg.detail.orgId)
|| currentState.organizations.find((orgArg) => orgArg.data.slug === eventArg.detail.org?.slug);
this.selectedOrgId = eventArg.detail.orgId;
this.setAdminPage('org-general');
if (selectedOrg) {
await states.accountState.dispatchAction(states.setSelectedOrg, selectedOrg);
this.pushDashPath(`/org/${selectedOrg.data.slug}`);
} else if (eventArg.detail.org?.slug) {
this.pushDashPath(`/org/${eventArg.detail.org.slug}`);
}
}
private async handleOrgCreate() {
const org = await CreateOrgModal.show();
if (!org) {
return;
}
this.applyAccountState();
this.selectedOrgId = org.id;
this.setAdminPage('org-general');
this.pushDashPath(`/org/${org.data.slug}`);
}
private async handleOrgUpdate(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgUpdateEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpdateOrganization>('updateOrganization');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
name: eventArg.detail.name,
slug: eventArg.detail.slug,
confirmationText: eventArg.detail.confirmationText,
});
if (!response.success) {
throw new Error(response.message || 'Organization update failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
const refreshedOrg = states.accountState.getState().organizations.find((orgArg) => orgArg.id === response.organization.id) || response.organization;
await states.accountState.dispatchAction(states.setSelectedOrg, refreshedOrg);
this.applyAccountState();
this.selectedOrgId = refreshedOrg.id;
this.setAdminPage('org-settings');
this.pushDashPath(`/org/${refreshedOrg.data.slug}/settings`);
});
}
private async handleOrgTransfer(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgTransferEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_TransferOwnership>('transferOwnership');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
newOwnerId: eventArg.detail.newOwnerId,
confirmationText: eventArg.detail.confirmationText,
});
if (!response.success) {
throw new Error(response.message || 'Ownership transfer failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
const refreshedOrg = states.accountState.getState().organizations.find((orgArg) => orgArg.id === eventArg.detail.organizationId);
if (refreshedOrg) {
await states.accountState.dispatchAction(states.setSelectedOrg, refreshedOrg);
this.selectedOrgId = refreshedOrg.id;
}
this.applyAccountState();
this.setAdminPage('org-settings');
});
}
private async handleOrgDelete(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgDeleteEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_DeleteOrganization>('deleteOrganization');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
confirmationText: eventArg.detail.confirmationText,
});
if (!response.success) {
throw new Error(response.message || 'Organization deletion failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
const nextOrg = states.accountState.getState().organizations[0] || null;
if (nextOrg) {
await states.accountState.dispatchAction(states.setSelectedOrg, nextOrg);
} else {
await states.accountState.dispatchAction(states.setSelectedOrg, null as any);
}
this.selectedOrgId = nextOrg?.id || '';
this.applyAccountState();
this.setAdminPage('overview');
this.pushDashPath('/overview');
});
}
private async syncSelectedOrgFromPath() {
const orgSlug = window.location.pathname.match(/^\/dash\/org\/([^/]+)/)?.[1];
if (!orgSlug) {
return;
}
const currentState = states.accountState.getState();
const selectedOrg = currentState.organizations.find((orgArg) => orgArg.data.slug === orgSlug);
if (!selectedOrg) {
return;
}
this.selectedOrgId = selectedOrg.id;
if (currentState.selectedOrg?.id !== selectedOrg.id) {
await states.accountState.dispatchAction(states.setSelectedOrg, selectedOrg);
}
}
private applyAccountState() {
const currentState = states.accountState.getState();
const user = currentState.user;
if (user) {
this.adminUser = {
name: user.data.name || user.data.username || user.data.email,
email: user.data.email,
username: user.data.username,
mobileNumber: user.data.mobileNumber,
status: user.data.status,
};
this.globalAdmin = Boolean(user.data.isGlobalAdmin);
}
this.adminOrgs = currentState.organizations.map((orgArg) => {
const role = currentState.roles.find((roleArg) => roleArg.data.organizationId === orgArg.id);
return {
id: orgArg.id,
name: orgArg.data.name,
slug: orgArg.data.slug,
myRole: role?.data.roles?.[0] || 'member',
};
});
this.selectedOrgId = currentState.selectedOrg?.id || this.selectedOrgId || currentState.organizations[0]?.id || '';
const selectedOrg = currentState.organizations.find((orgArg) => orgArg.id === this.selectedOrgId) || currentState.selectedOrg || currentState.organizations[0];
this.orgRoleDefinitions = selectedOrg?.data.roleDefinitions || [];
}
private async setOrgPage(pageArg: plugins.idpCatalog.IdpAdminShell['page']) {
await this.syncSelectedOrgFromPath();
this.setAdminPage(pageArg);
}
private getSelectedOrganization(): plugins.idpInterfaces.data.IOrganization | null {
const currentState = states.accountState.getState();
return currentState.selectedOrg
|| currentState.organizations.find((orgArg) => orgArg.id === this.selectedOrgId)
|| currentState.organizations[0]
|| null;
}
private async loadSessions(idpStateArg: IdpState, jwtArg: string): Promise<plugins.idpCatalog.IIdpAdminSession[]> {
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetUserSessions>('getUserSessions');
const response = await request.fire({ jwt: jwtArg });
return (response.sessions || []).map((sessionArg) => ({
id: sessionArg.id,
deviceName: sessionArg.deviceName,
browser: sessionArg.browser,
os: sessionArg.os,
ip: sessionArg.ip,
lastActive: sessionArg.lastActive,
createdAt: sessionArg.createdAt,
isCurrent: sessionArg.isCurrent,
}));
}
private async loadActivities(idpStateArg: IdpState, jwtArg: string): Promise<plugins.idpCatalog.IIdpAdminActivity[]> {
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetUserActivity>('getUserActivity');
const response = await request.fire({ jwt: jwtArg, limit: 20 });
return (response.activities || []).map((activityArg) => ({
id: activityArg.id,
action: activityArg.data.action,
description: activityArg.data.metadata.description,
timestamp: activityArg.data.timestamp,
ip: activityArg.data.metadata.ip,
targetType: activityArg.data.metadata.targetType,
}));
}
private async loadOrgMembers(idpStateArg: IdpState, jwtArg: string, organizationIdArg: string): Promise<plugins.idpCatalog.IIdpAdminMember[]> {
const currentState = states.accountState.getState();
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetOrgMembers>('getOrgMembers');
const response = await request.fire({ jwt: jwtArg, organizationId: organizationIdArg });
return (response.members || []).map((memberArg) => ({
userId: memberArg.user.id,
name: memberArg.user.data.name || memberArg.user.data.username || memberArg.user.data.email,
email: memberArg.user.data.email,
roles: memberArg.role.data.roles || [],
isCurrentUser: currentState.user?.id === memberArg.user.id,
}));
}
private async loadOrgInvitations(idpStateArg: IdpState, jwtArg: string, organizationIdArg: string): Promise<plugins.idpCatalog.IIdpAdminInvitation[]> {
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetOrgInvitations>('getOrgInvitations');
const response = await request.fire({ jwt: jwtArg, organizationId: organizationIdArg });
return (response.invitations || []).map((invitationArg) => {
const orgRef = invitationArg.data.organizationRefs.find((refArg) => refArg.organizationId === organizationIdArg)
|| invitationArg.data.organizationRefs[0];
return {
id: invitationArg.id,
email: invitationArg.data.email,
roles: orgRef?.roles || [],
invitedAt: orgRef?.invitedAt || invitationArg.data.createdAt,
expiresAt: invitationArg.data.expiresAt,
status: invitationArg.data.status,
};
});
}
private async loadOrgApps(idpStateArg: IdpState, jwtArg: string, organizationIdArg: string): Promise<plugins.idpCatalog.IIdpAdminApp[]> {
const appsRequest = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetGlobalApps>('getGlobalApps');
const connectionsRequest = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetAppConnections>('getAppConnections');
const [appsResponse, connectionsResponse] = await Promise.all([
appsRequest.fire({ jwt: jwtArg }),
connectionsRequest.fire({ jwt: jwtArg, organizationId: organizationIdArg }),
]);
const activeConnectionMap = new Map((connectionsResponse.connections || [])
.filter((connectionArg) => connectionArg.data.status === 'active')
.map((connectionArg) => [connectionArg.data.appId, connectionArg]));
return (appsResponse.apps || []).map((appArg) => ({
id: appArg.id,
name: appArg.data.name,
description: appArg.data.description,
logoUrl: appArg.data.logoUrl,
appUrl: appArg.data.appUrl,
category: appArg.data.category,
type: appArg.type,
status: appArg.data.isActive ? 'active' : 'inactive',
isConnected: activeConnectionMap.has(appArg.id),
roleMappings: activeConnectionMap.get(appArg.id)?.data.roleMappings || [],
clientId: appArg.data.oauthCredentials.clientId,
scopes: activeConnectionMap.get(appArg.id)?.data.grantedScopes || appArg.data.oauthCredentials.allowedScopes || [],
grants: appArg.data.oauthCredentials.grantTypes || [],
}));
}
private async loadAdminApps(idpStateArg: IdpState, jwtArg: string): Promise<plugins.idpCatalog.IIdpAdminApp[]> {
if (!this.globalAdmin) {
return [];
}
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetGlobalAppStats>('getGlobalAppStats');
const response = await request.fire({ jwt: jwtArg });
return (response.apps || []).map((entryArg) => ({
id: entryArg.app.id,
name: entryArg.app.data.name,
description: entryArg.app.data.description,
logoUrl: entryArg.app.data.logoUrl,
appUrl: entryArg.app.data.appUrl,
category: entryArg.app.data.category,
type: entryArg.app.type,
status: entryArg.app.data.isActive ? 'active' : 'inactive',
connectionCount: entryArg.connectionCount,
clientId: entryArg.app.data.oauthCredentials.clientId,
scopes: entryArg.app.data.oauthCredentials.allowedScopes || [],
grants: entryArg.app.data.oauthCredentials.grantTypes || [],
}));
}
private async loadPassportDevices(idpStateArg: IdpState, jwtArg: string): Promise<plugins.idpCatalog.IIdpAdminPassportDevice[]> {
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetPassportDevices>('getPassportDevices');
const response = await request.fire({ jwt: jwtArg });
return (response.devices || []).map((deviceArg) => ({
id: deviceArg.id,
label: deviceArg.data.label,
platform: deviceArg.data.platform,
status: deviceArg.data.status,
capabilities: deviceArg.data.capabilities,
appVersion: deviceArg.data.appVersion,
createdAt: deviceArg.data.createdAt,
lastSeenAt: deviceArg.data.lastSeenAt,
lastChallengeAt: deviceArg.data.lastChallengeAt,
pushRegistered: Boolean(deviceArg.data.pushRegistration),
}));
}
private async loadAdminShellData() {
const currentRun = ++this.dataLoadRun;
this.dataLoading = true;
this.dataError = '';
try {
const idpState = await IdpState.getSingletonInstance();
const jwt = await idpState.idpClient.getJwt();
const selectedOrg = this.getSelectedOrganization();
const orgId = selectedOrg?.id || '';
const [sessions, activities, members, invitations, orgApps, adminApps, passportDevices] = await Promise.all([
this.loadSessions(idpState, jwt).catch((error) => {
console.error('Error loading sessions:', error);
return this.sessions;
}),
this.loadActivities(idpState, jwt).catch((error) => {
console.error('Error loading activity:', error);
return this.activities;
}),
orgId ? this.loadOrgMembers(idpState, jwt, orgId).catch((error) => {
console.error('Error loading org members:', error);
return this.orgMembers;
}) : Promise.resolve([]),
orgId ? this.loadOrgInvitations(idpState, jwt, orgId).catch((error) => {
console.error('Error loading org invitations:', error);
return this.orgInvitations;
}) : Promise.resolve([]),
orgId ? this.loadOrgApps(idpState, jwt, orgId).catch((error) => {
console.error('Error loading org apps:', error);
return this.orgApps;
}) : Promise.resolve([]),
this.loadAdminApps(idpState, jwt).catch((error) => {
console.error('Error loading admin apps:', error);
return this.adminApps;
}),
this.loadPassportDevices(idpState, jwt).catch((error) => {
console.error('Error loading passport devices:', error);
return this.passportDevices;
}),
]);
if (currentRun !== this.dataLoadRun) {
return;
}
this.sessions = sessions;
this.activities = activities;
this.orgMembers = members;
this.orgInvitations = invitations;
this.orgApps = orgApps;
this.adminApps = adminApps;
this.passportDevices = passportDevices;
} catch (error) {
console.error('Error loading admin shell data:', error);
if (currentRun === this.dataLoadRun) {
this.dataError = error instanceof Error ? error.message : 'Failed to load admin console data.';
}
} finally {
if (currentRun === this.dataLoadRun) {
this.dataLoading = false;
}
}
}
private async runAdminAction(actionArg: () => Promise<void>) {
this.dataError = '';
try {
await actionArg();
await this.loadAdminShellData();
} catch (error) {
console.error('Admin console action failed:', error);
this.dataError = error instanceof Error ? error.message : 'Action failed. Please try again.';
}
}
private async handleSessionRevoke(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminSessionEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RevokeSession>('revokeSession');
await request.fire({ jwt: await idpState.idpClient.getJwt(), sessionId: eventArg.detail.sessionId });
});
}
private async handleAppToggle(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminAppToggleEventDetail>) {
const selectedOrg = this.getSelectedOrganization();
if (!selectedOrg) {
this.dataError = 'Select an organisation before changing app connections.';
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_ToggleAppConnection>('toggleAppConnection');
await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: selectedOrg.id,
appId: eventArg.detail.appId,
action: eventArg.detail.connected ? 'connect' : 'disconnect',
});
});
}
private async handlePasswordChange(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminPasswordChangeEventDetail>) {
const email = states.accountState.getState().user?.data.email;
if (!email) {
this.credentialMessage = '';
this.dataError = 'Cannot change password before account data is loaded.';
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_SetNewPassword>('setNewPassword');
const response = await request.fire({
email,
oldPassword: eventArg.detail.currentPassword,
newPassword: eventArg.detail.newPassword,
});
if (response.status !== 'ok') {
throw new Error('Password change failed.');
}
this.credentialMessage = 'Password changed successfully.';
});
}
private async handlePassportEnroll(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminPassportEnrollmentEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CreatePassportEnrollmentChallenge>('createPassportEnrollmentChallenge');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
deviceLabel: eventArg.detail.deviceLabel,
platform: 'web',
capabilities: {
gps: false,
nfc: false,
push: false,
},
});
this.passportEnrollment = response;
this.credentialMessage = 'Passport enrollment challenge created.';
});
}
private async handlePassportRevoke(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminPassportDeviceEventDetail>) {
const device = this.passportDevices.find((deviceArg) => deviceArg.id === eventArg.detail.deviceId);
if (!device || !confirm(`Revoke passport device ${device.label}?`)) {
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RevokePassportDevice>('revokePassportDevice');
await request.fire({
jwt: await idpState.idpClient.getJwt(),
deviceId: eventArg.detail.deviceId,
});
this.credentialMessage = 'Passport device revoked.';
});
}
private async handleMemberInvite() {
const selectedOrg = this.getSelectedOrganization();
if (!selectedOrg) {
this.dataError = 'Select an organisation before inviting members.';
return;
}
const result = await BulkInviteModal.show({
organizationId: selectedOrg.id,
organizationName: selectedOrg.data.name,
});
if (result?.invitedCount) {
await this.loadAdminShellData();
}
}
private async handleMemberRemove(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminMemberEventDetail>) {
const selectedOrg = this.getSelectedOrganization();
const member = this.orgMembers.find((memberArg) => memberArg.userId === eventArg.detail.userId);
if (!selectedOrg || !member || !confirm(`Remove ${member.name} from ${selectedOrg.data.name}?`)) {
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RemoveMember>('removeMember');
await request.fire({ jwt: await idpState.idpClient.getJwt(), organizationId: selectedOrg.id, userId: member.userId });
});
}
private async handleMemberRolesUpdate(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminMemberRolesEventDetail>) {
const selectedOrg = this.getSelectedOrganization();
if (!selectedOrg) {
this.dataError = 'Select an organisation before editing member roles.';
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpdateMemberRoles>('updateMemberRoles');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: selectedOrg.id,
userId: eventArg.detail.userId,
roles: eventArg.detail.roles,
});
if (!response.success) {
throw new Error(response.message || 'Member role update failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
this.applyAccountState();
});
}
private async handleOrgRoleUpsert(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgRoleUpsertEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpsertOrgRoleDefinition>('upsertOrgRoleDefinition');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
roleDefinition: eventArg.detail.roleDefinition,
});
if (!response.success) {
throw new Error(response.message || 'Organization role update failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
this.applyAccountState();
});
}
private async handleOrgRoleDelete(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminOrgRoleDeleteEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_DeleteOrgRoleDefinition>('deleteOrgRoleDefinition');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
roleKey: eventArg.detail.roleKey,
confirmationText: eventArg.detail.confirmationText,
});
if (!response.success) {
throw new Error(response.message || 'Organization role delete failed.');
}
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
this.applyAccountState();
});
}
private async handleAppRoleMappingsUpdate(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminAppRoleMappingsEventDetail>) {
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpdateAppRoleMappings>('updateAppRoleMappings');
const response = await request.fire({
jwt: await idpState.idpClient.getJwt(),
organizationId: eventArg.detail.organizationId,
appId: eventArg.detail.appId,
roleMappings: eventArg.detail.roleMappings,
});
if (!response.success) {
throw new Error(response.message || 'App role mapping update failed.');
}
});
}
private async handleInvitationResend(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminInvitationEventDetail>) {
const selectedOrg = this.getSelectedOrganization();
if (!selectedOrg) {
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_ResendInvitation>('resendInvitation');
await request.fire({ jwt: await idpState.idpClient.getJwt(), organizationId: selectedOrg.id, invitationId: eventArg.detail.invitationId });
});
}
private async handleInvitationCancel(eventArg: CustomEvent<plugins.idpCatalog.IIdpAdminInvitationEventDetail>) {
const selectedOrg = this.getSelectedOrganization();
if (!selectedOrg || !confirm('Cancel this invitation?')) {
return;
}
await this.runAdminAction(async () => {
const idpState = await IdpState.getSingletonInstance();
const request = idpState.idpClient.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CancelInvitation>('cancelInvitation');
await request.fire({ jwt: await idpState.idpClient.getJwt(), organizationId: selectedOrg.id, invitationId: eventArg.detail.invitationId });
});
}
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
super.firstUpdated(_changedProperties);
await this.domtoolsPromise;
this.subrouter = this.domtools.router.createSubRouter('/dash');
await states.accountState.dispatchAction(states.getOrganizationsAction, null);
this.applyAccountState();
2024-10-07 15:14:44 +02:00
this.subrouter.on('', async () => {
this.pushDashPath('/overview');
});
this.subrouter.on('/overview', async () => {
this.setAdminPage('overview');
});
this.subrouter.on('/account/profile', async () => {
this.setAdminPage('profile');
});
this.subrouter.on('/account/security', async () => {
this.setAdminPage('security');
});
this.subrouter.on('/account/sessions', async () => {
this.setAdminPage('sessions');
});
this.subrouter.on('/account/apps', async () => {
this.setAdminPage('apps');
});
this.subrouter.on('/support', async () => {
this.setAdminPage('support');
});
this.subrouter.on('/org/:orgName', async () => {
await this.setOrgPage('org-general');
});
this.subrouter.on('/org/:orgName/settings', async () => {
await this.setOrgPage('org-settings');
2025-12-01 04:44:47 +00:00
});
this.subrouter.on('/org/:orgName/apps', async () => {
await this.setOrgPage('org-apps');
});
2025-12-04 17:45:40 +00:00
this.subrouter.on('/org/:orgName/users', async () => {
await this.setOrgPage('org-members');
2025-12-04 17:45:40 +00:00
});
this.subrouter.on('/admin', async () => {
this.pushDashPath('/admin/apps');
});
this.subrouter.on('/admin/users', async () => {
this.setAdminPage('ga-users');
});
this.subrouter.on('/admin/orgs', async () => {
this.setAdminPage('ga-orgs');
});
this.subrouter.on('/admin/apps', async () => {
this.setAdminPage('ga-apps');
});
this.subrouter._handleRouteState();
2024-10-07 15:14:44 +02:00
states.accountState.select((stateArg) => stateArg.user).subscribe(() => this.applyAccountState());
states.accountState.select((stateArg) => stateArg.organizations).subscribe(() => this.applyAccountState());
states.accountState.select((stateArg) => stateArg.roles).subscribe(() => this.applyAccountState());
states.accountState.select((stateArg) => stateArg.selectedOrg).subscribe(() => this.applyAccountState());
2024-10-07 15:14:44 +02:00
this.registerGarbageFunction(async () => {
this.subrouter.destroy();
})
}
2024-10-07 15:14:44 +02:00
}