add modal to configure mfa providers
This commit is contained in:
@@ -1,84 +1,43 @@
|
||||
import { UmbCurrentUserRepository } from '../repository/index.js';
|
||||
import { html, customElement, state, when, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_CURRENT_USER_MFA_MODAL } from '../modals/current-user-mfa/current-user-mfa-modal.token.js';
|
||||
import { html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import {
|
||||
type UmbExtensionElementInitializer,
|
||||
UmbExtensionsElementInitializer,
|
||||
} from '@umbraco-cms/backoffice/extension-api';
|
||||
import { type ManifestMfaLoginProvider, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-mfa-providers-user-profile-app')
|
||||
export class UmbMfaProvidersUserProfileAppElement extends UmbLitElement {
|
||||
@state()
|
||||
_items: Array<UmbExtensionElementInitializer<ManifestMfaLoginProvider>> = [];
|
||||
|
||||
#currentUserRepository = new UmbCurrentUserRepository(this);
|
||||
#extensionsInitializer?: UmbExtensionsElementInitializer<
|
||||
ManifestMfaLoginProvider,
|
||||
'mfaLoginProvider',
|
||||
ManifestMfaLoginProvider
|
||||
>;
|
||||
|
||||
@state()
|
||||
_hasProviders = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#extensionsInitializer = new UmbExtensionsElementInitializer<
|
||||
ManifestMfaLoginProvider,
|
||||
'mfaLoginProvider',
|
||||
ManifestMfaLoginProvider
|
||||
>(
|
||||
this,
|
||||
umbExtensionsRegistry,
|
||||
'mfaLoginProvider',
|
||||
undefined,
|
||||
(permitted) => (this._items = permitted),
|
||||
'_mfaLoginProviders',
|
||||
'umb-mfa-login-provider-default',
|
||||
);
|
||||
|
||||
this.#loadProviders();
|
||||
this.#init();
|
||||
}
|
||||
|
||||
async #loadProviders() {
|
||||
const { data: providers } = await this.#currentUserRepository.requestMfaLoginProviders();
|
||||
|
||||
if (!providers) return;
|
||||
|
||||
for (const provider of providers) {
|
||||
// Check if provider is initialized as extension
|
||||
const extension = this._items.find((item) => item.manifest?.forProviderNames.includes(provider.providerName));
|
||||
if (extension) {
|
||||
extension.properties = { provider };
|
||||
} else {
|
||||
// Register provider as extension
|
||||
const manifest: ManifestMfaLoginProvider = {
|
||||
type: 'mfaLoginProvider',
|
||||
alias: provider.providerName,
|
||||
name: provider.providerName,
|
||||
forProviderNames: [provider.providerName],
|
||||
meta: {
|
||||
label: provider.providerName,
|
||||
},
|
||||
};
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
}
|
||||
}
|
||||
async #init() {
|
||||
this._hasProviders = await this.#currentUserRepository.hasMfaLoginProviders();
|
||||
}
|
||||
|
||||
render() {
|
||||
return when(
|
||||
this._items.length > 0,
|
||||
() => html`
|
||||
<uui-box headline=${this.localize.term('member_2fa')}>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.alias,
|
||||
(item) => item.component,
|
||||
)}
|
||||
</uui-box>
|
||||
`,
|
||||
);
|
||||
if (!this._hasProviders) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<uui-box .headline=${this.localize.term('member_2fa')}>
|
||||
<uui-button type="button" look="primary" @click=${this.#onClick}>
|
||||
<umb-localize key="user_configureTwoFactor">Configure Two Factor</umb-localize>
|
||||
</uui-button>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
async #onClick() {
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
await modalManagerContext.open(this, UMB_CURRENT_USER_MFA_MODAL).onSubmit();
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { UmbCurrentUserRepository } from '../../repository/index.js';
|
||||
import { customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UserTwoFactorProviderModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-current-user-mfa-modal')
|
||||
export class UmbCurrentUserMfaModalElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
modalContext?: UmbModalContext;
|
||||
|
||||
@state()
|
||||
_items: Array<UserTwoFactorProviderModel> = [];
|
||||
|
||||
#currentUserRepository = new UmbCurrentUserRepository(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#loadProviders();
|
||||
}
|
||||
|
||||
async #loadProviders() {
|
||||
const { data: providers } = await this.#currentUserRepository.requestMfaLoginProviders();
|
||||
|
||||
if (!providers) return;
|
||||
|
||||
this._items = providers;
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-body-layout headline="${this.localize.term('member_2fa')}">
|
||||
<div id="main">
|
||||
${when(
|
||||
this._items.length > 0,
|
||||
() => html`
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.providerName,
|
||||
(item) => this.#renderProvider(item),
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button @click=${this.#close} look="secondary" .label=${this.localize.term('general_close')}>
|
||||
${this.localize.term('general_close')}
|
||||
</uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a provider with a toggle to enable/disable it
|
||||
*/
|
||||
#renderProvider(item: UserTwoFactorProviderModel) {
|
||||
return html`
|
||||
<div>
|
||||
<uui-toggle
|
||||
label=${item.providerName}
|
||||
?checked=${item.isEnabledOnUser}
|
||||
@change=${() => this.#onProviderToggleChange(item)}></uui-toggle>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
#onProviderToggleChange = (item: UserTwoFactorProviderModel) => {
|
||||
// If already enabled, disable it
|
||||
if (item.isEnabledOnUser) {
|
||||
// Disable provider
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable provider
|
||||
};
|
||||
}
|
||||
|
||||
export default UmbCurrentUserMfaModalElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-current-user-mfa-modal': UmbCurrentUserMfaModalElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export const UMB_CURRENT_USER_MFA_MODAL = new UmbModalToken('Umb.Modal.CurrentUserMfa', {
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
@@ -7,6 +7,12 @@ const modals: Array<ManifestModal> = [
|
||||
name: 'Current User Modal',
|
||||
js: () => import('./current-user/current-user-modal.element.js'),
|
||||
},
|
||||
{
|
||||
type: 'modal',
|
||||
alias: 'Umb.Modal.CurrentUserMfa',
|
||||
name: 'Current User MFA Modal',
|
||||
js: () => import('./current-user-mfa/current-user-mfa-modal.element.js'),
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [...modals];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { UmbCurrentUserServerDataSource } from './current-user.server.data-source.js';
|
||||
import { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.js';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
import { UserResource } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
|
||||
/**
|
||||
* A repository for the current user
|
||||
@@ -46,9 +48,51 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase {
|
||||
* Request the current user's available MFA login providers
|
||||
* @memberof UmbCurrentUserRepository
|
||||
*/
|
||||
async requestMfaLoginProviders() {
|
||||
requestMfaLoginProviders() {
|
||||
return this.#currentUserSource.getMfaLoginProviders();
|
||||
}
|
||||
|
||||
async hasMfaLoginProviders(): Promise<boolean> {
|
||||
const { data } = await this.requestMfaLoginProviders();
|
||||
|
||||
return !!data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable an MFA provider
|
||||
* @param provider The provider to enable
|
||||
* @param code The activation code of the provider to enable
|
||||
*/
|
||||
async enableMfaProvider(provider: string, code: string): Promise<boolean> {
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
this._host,
|
||||
UserResource.postUserCurrent2FaByProviderName({ providerName: provider, requestBody: { code, secret: code } }),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable an MFA provider
|
||||
* @param provider The provider to disable
|
||||
* @param code The activation code of the provider to disable
|
||||
*/
|
||||
async disableMfaProvider(provider: string, code: string): Promise<boolean> {
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
this._host,
|
||||
UserResource.deleteUserCurrent2FaByProviderName({ providerName: provider, code }),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbCurrentUserRepository;
|
||||
|
||||
Reference in New Issue
Block a user