show activation flow
This commit is contained in:
@@ -1935,6 +1935,12 @@ export default {
|
||||
'2faProviderIsDisabledMsg': 'This two-factor provider is now disabled',
|
||||
'2faProviderIsNotDisabledMsg': 'Something went wrong with trying to disable this two-factor provider',
|
||||
'2faDisableForUser': 'Do you want to disable this two-factor provider for this user?',
|
||||
'2faQrCodeAlt': 'QR code for two-factor authentication with {0}',
|
||||
'2faQrCodeTitle': 'QR code for two-factor authentication with {0}',
|
||||
'2faQrCodeDescription': 'Scan this QR code with your authenticator app to enable two-factor authentication',
|
||||
'2faCodeInput': 'Verification code',
|
||||
'2faCodeInputHelp': 'Please enter the verification code',
|
||||
'2faInvalidCode': 'Invalid code entered',
|
||||
},
|
||||
validation: {
|
||||
validation: 'Validation',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { UmbMfaProviderConfigurationElementProps } from '../types.js';
|
||||
import { UserResource } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, property, state, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
|
||||
@@ -11,14 +11,11 @@ export class UmbMfaProviderDefaultElement extends UmbLitElement implements UmbMf
|
||||
@property({ attribute: false })
|
||||
providerName = '';
|
||||
|
||||
@property({ type: Boolean, attribute: false })
|
||||
isEnabled = false;
|
||||
@property({ attribute: false })
|
||||
enableProvider: (providerName: string, code: string, secret: string) => Promise<boolean> = async () => false;
|
||||
|
||||
@property({ attribute: false })
|
||||
onSubmit: (value: { code: string; secret?: string | undefined }) => void = () => {};
|
||||
|
||||
@property({ attribute: false })
|
||||
onClose = () => {};
|
||||
close = () => {};
|
||||
|
||||
@state()
|
||||
protected _loading = true;
|
||||
@@ -31,6 +28,9 @@ export class UmbMfaProviderDefaultElement extends UmbLitElement implements UmbMf
|
||||
|
||||
protected notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE;
|
||||
|
||||
@query('#code')
|
||||
protected codeField?: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -75,23 +75,55 @@ export class UmbMfaProviderDefaultElement extends UmbLitElement implements UmbMf
|
||||
}
|
||||
|
||||
return html`
|
||||
<form id="authForm" name="authForm" @submit=${this.submit}>
|
||||
<umb-body-layout headline=${this.providerName}>
|
||||
<div id="main"></div>
|
||||
<div slot="actions">
|
||||
<uui-button
|
||||
type="button"
|
||||
look="secondary"
|
||||
.label=${this.localize.term('general_close')}
|
||||
@click=${this.onClose}>
|
||||
${this.localize.term('general_close')}
|
||||
</uui-button>
|
||||
<uui-button type="submit" look="primary" .label=${this.localize.term('buttons_save')}>
|
||||
${this.localize.term('general_submit')}
|
||||
</uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
</form>
|
||||
<uui-form>
|
||||
<form id="authForm" name="authForm" @submit=${this.submit} novalidate>
|
||||
<umb-body-layout headline=${this.providerName}>
|
||||
<div id="main">
|
||||
<uui-box .headline=${this.localize.term('member_2fa')}>
|
||||
${this._qrCodeSetupImageUrl
|
||||
? html` <div class="text-center">
|
||||
<p>
|
||||
<umb-localize key="user_2faQrCodeDescription">
|
||||
Scan this QR code with your authenticator app to enable two-factor authentication
|
||||
</umb-localize>
|
||||
</p>
|
||||
<img
|
||||
.src=${this._qrCodeSetupImageUrl}
|
||||
alt=${this.localize.term('user_2faQrCodeAlt')}
|
||||
title=${this.localize.term('user_2faQrCodeTitle')} />
|
||||
</div>`
|
||||
: ''}
|
||||
<uui-form-layout-item class="text-center">
|
||||
<uui-label for="code" slot="label" required>
|
||||
<umb-localize key="user_2faCodeInput"></umb-localize>
|
||||
</uui-label>
|
||||
<uui-input
|
||||
id="code"
|
||||
name="code"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
autocomplete="one-time-code"
|
||||
required
|
||||
required-message=${this.localize.term('general_required')}
|
||||
placeholder=${this.localize.term('user_2faCodeInputHelp')}></uui-input>
|
||||
</uui-form-layout-item>
|
||||
</uui-box>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button
|
||||
type="button"
|
||||
look="secondary"
|
||||
.label=${this.localize.term('general_close')}
|
||||
@click=${this.close}>
|
||||
${this.localize.term('general_close')}
|
||||
</uui-button>
|
||||
<uui-button type="submit" look="primary" .label=${this.localize.term('buttons_save')}>
|
||||
${this.localize.term('general_submit')}
|
||||
</uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
</form>
|
||||
</uui-form>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -112,9 +144,19 @@ export class UmbMfaProviderDefaultElement extends UmbLitElement implements UmbMf
|
||||
* Submit the form with the code and secret back to the opener.
|
||||
* @param e The submit event
|
||||
*/
|
||||
protected submit(e: SubmitEvent) {
|
||||
protected async submit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
this.onSubmit({ code: '123456', secret: '123' });
|
||||
this.codeField?.setCustomValidity('');
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const code = formData.get('code') as string;
|
||||
const successful = await this.enableProvider(this.providerName, code, this._secret);
|
||||
debugger;
|
||||
if (successful) {
|
||||
this.peek('Two-factor authentication has successfully been enabled.');
|
||||
} else {
|
||||
this.codeField?.setCustomValidity(this.localize.term('user_2faInvalidCode'));
|
||||
this.codeField?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -123,6 +165,15 @@ export class UmbMfaProviderDefaultElement extends UmbLitElement implements UmbMf
|
||||
#authForm {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#code {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ const meta: Meta<UmbMfaProviderDefaultElement> = {
|
||||
decorators: [(Story) => html`<div style="width: 500px; height: 500px;">${Story()}</div>`],
|
||||
args: {
|
||||
providerName: 'SMS',
|
||||
isEnabled: true,
|
||||
enableProvider: async (_provider, code) => (code === 'fail' ? false : true),
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
actions: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { UmbMfaProviderConfigurationElementProps } from '../../types.js';
|
||||
import type {
|
||||
UmbCurrentUserMfaProviderModalConfig,
|
||||
UmbCurrentUserMfaProviderModalValue,
|
||||
} from './current-user-mfa-provider-modal.token.js';
|
||||
import type { UmbCurrentUserMfaProviderModalConfig } from './current-user-mfa-provider-modal.token.js';
|
||||
import type { ManifestMfaLoginProvider } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
@@ -12,13 +9,8 @@ import '../../components/mfa-provider-default.element.js';
|
||||
@customElement('umb-current-user-mfa-provider-modal')
|
||||
export class UmbCurrentUserMfaProviderModalElement extends UmbModalBaseElement<
|
||||
UmbCurrentUserMfaProviderModalConfig,
|
||||
UmbCurrentUserMfaProviderModalValue
|
||||
never
|
||||
> {
|
||||
#submit = (value: UmbCurrentUserMfaProviderModalValue) => {
|
||||
this.value = value;
|
||||
this._submitModal();
|
||||
};
|
||||
|
||||
#close = () => {
|
||||
this._rejectModal();
|
||||
};
|
||||
@@ -26,9 +18,8 @@ export class UmbCurrentUserMfaProviderModalElement extends UmbModalBaseElement<
|
||||
get #extensionSlotProps(): UmbMfaProviderConfigurationElementProps {
|
||||
return {
|
||||
providerName: this.data!.providerName,
|
||||
isEnabled: this.data!.isEnabled,
|
||||
onSubmit: this.#submit,
|
||||
onClose: this.#close,
|
||||
enableProvider: this.data!.repository.enableMfaProvider,
|
||||
close: this.#close,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import type { UmbCurrentUserRepository } from '../../repository/index.js';
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbCurrentUserMfaProviderModalConfig {
|
||||
providerName: string;
|
||||
isEnabled: boolean;
|
||||
repository: UmbCurrentUserRepository;
|
||||
}
|
||||
|
||||
export interface UmbCurrentUserMfaProviderModalValue {
|
||||
secret?: string;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export const UMB_CURRENT_USER_MFA_PROVIDER_MODAL = new UmbModalToken<
|
||||
UmbCurrentUserMfaProviderModalConfig,
|
||||
UmbCurrentUserMfaProviderModalValue
|
||||
>('Umb.Modal.CurrentUserMfaProvider', {
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
export const UMB_CURRENT_USER_MFA_PROVIDER_MODAL = new UmbModalToken<UmbCurrentUserMfaProviderModalConfig, never>(
|
||||
'Umb.Modal.CurrentUserMfaProvider',
|
||||
{
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
UMB_CURRENT_USER_MFA_PROVIDER_MODAL,
|
||||
type UmbCurrentUserMfaProviderModalValue,
|
||||
} from '../current-user-mfa-provider/current-user-mfa-provider-modal.token.js';
|
||||
import { UMB_CURRENT_USER_MFA_PROVIDER_MODAL } from '../current-user-mfa-provider/current-user-mfa-provider-modal.token.js';
|
||||
import { UmbCurrentUserRepository } from '../../repository/index.js';
|
||||
import type { UmbCurrentUserMfaProviderModel } from '../../types.js';
|
||||
import { customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -80,26 +77,14 @@ export class UmbCurrentUserMfaModalElement extends UmbLitElement {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const { code, secret } = await this.#openProviderModal(item);
|
||||
|
||||
// If no code, do nothing
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If already enabled, disable it
|
||||
if (item.isEnabledOnUser) {
|
||||
// Disable provider
|
||||
return this.#currentUserRepository.disableMfaProvider(item.providerName, code);
|
||||
alert('TODO: Implement disabling provider');
|
||||
}
|
||||
|
||||
// Enable provider
|
||||
// If no secret, do nothing
|
||||
if (!secret) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.#currentUserRepository.enableMfaProvider(item.providerName, code, secret);
|
||||
await this.#openProviderModal(item);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,11 +92,11 @@ export class UmbCurrentUserMfaModalElement extends UmbLitElement {
|
||||
* This will show the QR code and/or other means of validation for the given provider and return the activation code.
|
||||
* The activation code is then used to either enable or disable the provider.
|
||||
*/
|
||||
async #openProviderModal(item: UmbCurrentUserMfaProviderModel): Promise<UmbCurrentUserMfaProviderModalValue> {
|
||||
async #openProviderModal(item: UmbCurrentUserMfaProviderModel) {
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
return modalManager
|
||||
return await modalManager
|
||||
.open(this, UMB_CURRENT_USER_MFA_PROVIDER_MODAL, {
|
||||
data: { providerName: item.providerName, isEnabled: item.isEnabledOnUser },
|
||||
data: { providerName: item.providerName, repository: this.#currentUserRepository },
|
||||
})
|
||||
.onSubmit()
|
||||
.catch(() => ({}));
|
||||
|
||||
@@ -11,6 +11,9 @@ const meta: Meta<UmbCurrentUserMfaModalElement> = {
|
||||
decorators: [(Story) => html`<div style="width: 500px; height: 500px;">${Story()}</div>`],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
actions: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -23,8 +23,22 @@ export interface UmbCurrentUserModel {
|
||||
export type UmbCurrentUserMfaProviderModel = UserTwoFactorProviderModel;
|
||||
|
||||
export interface UmbMfaProviderConfigurationElementProps {
|
||||
/**
|
||||
* The name of the provider reflecting the provider name in the backend.
|
||||
*/
|
||||
providerName: string;
|
||||
isEnabled: boolean;
|
||||
onSubmit: (value: { code: string; secret?: string }) => void;
|
||||
onClose: () => void;
|
||||
|
||||
/**
|
||||
* Enable the provider with the given code and secret.
|
||||
* @param providerName The name of the provider to enable.
|
||||
* @param code The authentication code from the authentication method.
|
||||
* @param secret The secret from the authentication backend.
|
||||
* @returns True if the provider was enabled successfully.
|
||||
*/
|
||||
enableProvider: (providerName: string, code: string, secret: string) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Call this function to close the modal.
|
||||
*/
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user