feat(app): add MFA and tsdocker release
This commit is contained in:
@@ -77,6 +77,18 @@ export class IdpAccountContent extends DeesElement {
|
||||
@state()
|
||||
private accessor passportEnrollment: plugins.idpCatalog.IIdpAdminPassportEnrollment | null = null;
|
||||
|
||||
@state()
|
||||
private accessor totpEnabled = false;
|
||||
|
||||
@state()
|
||||
private accessor backupCodesRemaining = 0;
|
||||
|
||||
@state()
|
||||
private accessor totpEnrollment: any = null;
|
||||
|
||||
@state()
|
||||
private accessor passkeys: any[] = [];
|
||||
|
||||
@state()
|
||||
private accessor credentialMessage = '';
|
||||
|
||||
@@ -124,6 +136,10 @@ export class IdpAccountContent extends DeesElement {
|
||||
.adminApps=${this.adminApps}
|
||||
.passportDevices=${this.passportDevices}
|
||||
.passportEnrollment=${this.passportEnrollment}
|
||||
.totpEnabled=${this.totpEnabled}
|
||||
.backupCodesRemaining=${this.backupCodesRemaining}
|
||||
.totpEnrollment=${this.totpEnrollment}
|
||||
.passkeys=${this.passkeys}
|
||||
.credentialMessage=${this.credentialMessage}
|
||||
@idp-admin-navigate=${this.handleAdminNavigate}
|
||||
@idp-admin-org-select=${this.handleOrgSelect}
|
||||
@@ -136,6 +152,12 @@ export class IdpAccountContent extends DeesElement {
|
||||
@idp-admin-password-change=${this.handlePasswordChange}
|
||||
@idp-admin-passport-enroll=${this.handlePassportEnroll}
|
||||
@idp-admin-passport-revoke=${this.handlePassportRevoke}
|
||||
@idp-admin-totp-start=${this.handleTotpStart}
|
||||
@idp-admin-totp-verify=${this.handleTotpVerify}
|
||||
@idp-admin-totp-disable=${this.handleTotpDisable}
|
||||
@idp-admin-backup-codes-regenerate=${this.handleBackupCodesRegenerate}
|
||||
@idp-admin-passkey-register=${this.handlePasskeyRegister}
|
||||
@idp-admin-passkey-revoke=${this.handlePasskeyRevoke}
|
||||
@idp-admin-member-invite=${this.handleMemberInvite}
|
||||
@idp-admin-member-remove=${this.handleMemberRemove}
|
||||
@idp-admin-member-roles-update=${this.handleMemberRolesUpdate}
|
||||
@@ -495,6 +517,26 @@ export class IdpAccountContent extends DeesElement {
|
||||
}));
|
||||
}
|
||||
|
||||
private async loadMfaStatus(idpStateArg: IdpState, jwtArg: string) {
|
||||
const request = idpStateArg.idpClient.typedsocket.createTypedRequest<any>('getMfaStatus');
|
||||
const response = await request.fire({ jwt: jwtArg });
|
||||
return {
|
||||
totpEnabled: Boolean(response.totpEnabled),
|
||||
backupCodesRemaining: Number(response.backupCodesRemaining || 0),
|
||||
passkeys: (response.passkeys || []).map((passkeyArg: any) => ({
|
||||
id: passkeyArg.id,
|
||||
label: passkeyArg.data.label,
|
||||
credentialId: passkeyArg.data.credentialId,
|
||||
status: passkeyArg.data.status,
|
||||
backedUp: passkeyArg.data.backedUp,
|
||||
deviceType: passkeyArg.data.deviceType,
|
||||
transports: passkeyArg.data.transports || [],
|
||||
createdAt: passkeyArg.data.createdAt,
|
||||
lastUsedAt: passkeyArg.data.lastUsedAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private async loadAdminShellData() {
|
||||
const currentRun = ++this.dataLoadRun;
|
||||
this.dataLoading = true;
|
||||
@@ -506,7 +548,7 @@ export class IdpAccountContent extends DeesElement {
|
||||
const selectedOrg = this.getSelectedOrganization();
|
||||
const orgId = selectedOrg?.id || '';
|
||||
|
||||
const [sessions, activities, members, invitations, orgApps, adminApps, passportDevices] = await Promise.all([
|
||||
const [sessions, activities, members, invitations, orgApps, adminApps, passportDevices, mfaStatus] = await Promise.all([
|
||||
this.loadSessions(idpState, jwt).catch((error) => {
|
||||
console.error('Error loading sessions:', error);
|
||||
return this.sessions;
|
||||
@@ -535,6 +577,14 @@ export class IdpAccountContent extends DeesElement {
|
||||
console.error('Error loading passport devices:', error);
|
||||
return this.passportDevices;
|
||||
}),
|
||||
this.loadMfaStatus(idpState, jwt).catch((error) => {
|
||||
console.error('Error loading MFA status:', error);
|
||||
return {
|
||||
totpEnabled: this.totpEnabled,
|
||||
backupCodesRemaining: this.backupCodesRemaining,
|
||||
passkeys: this.passkeys,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
|
||||
if (currentRun !== this.dataLoadRun) {
|
||||
@@ -548,6 +598,9 @@ export class IdpAccountContent extends DeesElement {
|
||||
this.orgApps = orgApps;
|
||||
this.adminApps = adminApps;
|
||||
this.passportDevices = passportDevices;
|
||||
this.totpEnabled = mfaStatus.totpEnabled;
|
||||
this.backupCodesRemaining = mfaStatus.backupCodesRemaining;
|
||||
this.passkeys = mfaStatus.passkeys;
|
||||
} catch (error) {
|
||||
console.error('Error loading admin shell data:', error);
|
||||
if (currentRun === this.dataLoadRun) {
|
||||
@@ -657,6 +710,83 @@ export class IdpAccountContent extends DeesElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async handleTotpStart() {
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const request = idpState.idpClient.typedsocket.createTypedRequest<any>('startTotpEnrollment');
|
||||
this.totpEnrollment = await request.fire({ jwt: await idpState.idpClient.getJwt() });
|
||||
this.credentialMessage = 'Authenticator app enrollment started.';
|
||||
});
|
||||
}
|
||||
|
||||
private async handleTotpVerify(eventArg: CustomEvent<any>) {
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const request = idpState.idpClient.typedsocket.createTypedRequest<any>('finishTotpEnrollment');
|
||||
const response = await request.fire({
|
||||
jwt: await idpState.idpClient.getJwt(),
|
||||
credentialId: eventArg.detail.credentialId,
|
||||
code: eventArg.detail.code,
|
||||
});
|
||||
this.totpEnrollment = null;
|
||||
this.credentialMessage = `Authenticator app enabled. Save these backup codes now: ${(response.backupCodes || []).join(', ')}`;
|
||||
});
|
||||
}
|
||||
|
||||
private async handleTotpDisable(eventArg: CustomEvent<any>) {
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const request = idpState.idpClient.typedsocket.createTypedRequest<any>('disableTotp');
|
||||
await request.fire({ jwt: await idpState.idpClient.getJwt(), code: eventArg.detail.code });
|
||||
this.totpEnrollment = null;
|
||||
this.credentialMessage = 'Authenticator app disabled.';
|
||||
});
|
||||
}
|
||||
|
||||
private async handleBackupCodesRegenerate(eventArg: CustomEvent<any>) {
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const request = idpState.idpClient.typedsocket.createTypedRequest<any>('regenerateBackupCodes');
|
||||
const response = await request.fire({ jwt: await idpState.idpClient.getJwt(), code: eventArg.detail.code });
|
||||
this.credentialMessage = `New backup codes generated. Save them now: ${(response.backupCodes || []).join(', ')}`;
|
||||
});
|
||||
}
|
||||
|
||||
private async handlePasskeyRegister(eventArg: CustomEvent<any>) {
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const startRequest = idpState.idpClient.typedsocket.createTypedRequest<any>('startPasskeyRegistration');
|
||||
const finishRequest = idpState.idpClient.typedsocket.createTypedRequest<any>('finishPasskeyRegistration');
|
||||
const startResponse = await startRequest.fire({
|
||||
jwt: await idpState.idpClient.getJwt(),
|
||||
label: eventArg.detail.label,
|
||||
});
|
||||
const registrationResponse = await plugins.simpleWebAuthnBrowser.startRegistration({
|
||||
optionsJSON: startResponse.options,
|
||||
});
|
||||
await finishRequest.fire({
|
||||
jwt: await idpState.idpClient.getJwt(),
|
||||
challengeId: startResponse.challengeId,
|
||||
label: eventArg.detail.label,
|
||||
response: registrationResponse,
|
||||
});
|
||||
this.credentialMessage = 'Passkey registered.';
|
||||
});
|
||||
}
|
||||
|
||||
private async handlePasskeyRevoke(eventArg: CustomEvent<any>) {
|
||||
const passkey = this.passkeys.find((passkeyArg) => passkeyArg.id === eventArg.detail.passkeyId);
|
||||
if (!passkey || !confirm(`Revoke passkey ${passkey.label}?`)) {
|
||||
return;
|
||||
}
|
||||
await this.runAdminAction(async () => {
|
||||
const idpState = await IdpState.getSingletonInstance();
|
||||
const request = idpState.idpClient.typedsocket.createTypedRequest<any>('revokePasskey');
|
||||
await request.fire({ jwt: await idpState.idpClient.getJwt(), passkeyId: eventArg.detail.passkeyId });
|
||||
this.credentialMessage = 'Passkey revoked.';
|
||||
});
|
||||
}
|
||||
|
||||
private async handleMemberInvite() {
|
||||
const selectedOrg = this.getSelectedOrganization();
|
||||
if (!selectedOrg) {
|
||||
|
||||
Reference in New Issue
Block a user