Skip to content

Commit bc5fd72

Browse files
authored
Merge d440685 into c8e48c4
2 parents c8e48c4 + d440685 commit bc5fd72

File tree

20 files changed

+1824
-1259
lines changed

20 files changed

+1824
-1259
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<AkModal
2+
@showHeader={{true}}
3+
@headerTitle={{t 'modalCard.mfaTitle.enable'}}
4+
@onClose={{this.closeAppEnable}}
5+
>
6+
<AccountSettings::Security::MfaAppverify
7+
@secret={{this.mfaAppSecret}}
8+
@email='{{this.user.email}}'
9+
@otp={{this.appOTP}}
10+
@onContinue={{perform this.verifyAppOTP}}
11+
@onCancel={{this.closeAppEnable}}
12+
@waiting={{this.verifyAppOTP.isRunning}}
13+
@loading={{this.noMFAEnableApp.isRunning}}
14+
/>
15+
</AkModal>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import Component from '@glimmer/component';
2+
import { tracked } from '@glimmer/tracking';
3+
import { service } from '@ember/service';
4+
import { action } from '@ember/object';
5+
import { task } from 'ember-concurrency';
6+
import Owner from '@ember/owner';
7+
import type IntlService from 'ember-intl/services/intl';
8+
9+
import ENUMS from 'irene/enums';
10+
import type IreneAjaxService from 'irene/services/ajax';
11+
import type UserModel from 'irene/models/user';
12+
import type { AjaxError } from 'irene/services/ajax';
13+
14+
type TokenData = {
15+
token: string;
16+
secret: string;
17+
};
18+
19+
interface AccountSettingsSecurityMfaAppEnableSignature {
20+
Args: {
21+
user: UserModel;
22+
closeModal: () => void;
23+
reloadMfa: () => void;
24+
};
25+
}
26+
27+
export default class AccountSettingsSecurityMfaAppEnableComponent extends Component<AccountSettingsSecurityMfaAppEnableSignature> {
28+
@service declare ajax: IreneAjaxService;
29+
@service('notifications') declare notify: NotificationService;
30+
@service declare intl: IntlService;
31+
32+
@tracked appOTP = '';
33+
@tracked mfaAppSecret = '';
34+
@tracked mfaAppToken = '';
35+
36+
mfaEndpoint = '/v2/mfa';
37+
38+
tEnterOTP: string;
39+
tInvalidOTP: string;
40+
tsomethingWentWrong: string;
41+
42+
constructor(
43+
owner: Owner,
44+
args: AccountSettingsSecurityMfaAppEnableSignature['Args']
45+
) {
46+
super(owner, args);
47+
48+
this.tEnterOTP = this.intl.t('enterOTP');
49+
this.tInvalidOTP = this.intl.t('invalidOTP');
50+
this.tsomethingWentWrong = this.intl.t('somethingWentWrong');
51+
52+
this.noMFAEnableApp.perform();
53+
}
54+
55+
get user() {
56+
return this.args.user;
57+
}
58+
59+
@action
60+
closeAppEnable() {
61+
this.args.closeModal();
62+
}
63+
64+
noMFAEnableApp = task(async () => {
65+
this.appOTP = '';
66+
67+
try {
68+
const tokenData = await this.getMFAEnableAppToken.perform();
69+
70+
this.mfaAppSecret = tokenData.secret;
71+
72+
this.mfaAppToken = tokenData.token;
73+
} catch (error) {
74+
this.closeAppEnable();
75+
76+
this.notify.error(this.tsomethingWentWrong);
77+
}
78+
});
79+
80+
getMFAEnableAppToken = task(async () => {
81+
return await this.ajax.post<TokenData>(this.mfaEndpoint, {
82+
data: {
83+
method: ENUMS.MFA_METHOD.TOTP,
84+
},
85+
});
86+
});
87+
88+
verifyAppOTP = task(async () => {
89+
const otp = this.appOTP;
90+
const token = this.mfaAppToken;
91+
92+
if (!otp) {
93+
this.notify.error(this.tEnterOTP);
94+
95+
return false;
96+
}
97+
98+
try {
99+
await this.ajax.post(this.mfaEndpoint, {
100+
data: {
101+
method: ENUMS.MFA_METHOD.TOTP,
102+
otp: otp || '',
103+
token: token,
104+
},
105+
});
106+
107+
this.args.reloadMfa();
108+
109+
this.closeAppEnable();
110+
} catch (error) {
111+
const errorObj = (error as AjaxError).payload || {};
112+
const otpMsg = errorObj.otp && errorObj.otp[0];
113+
114+
if (otpMsg) {
115+
this.notify.error(this.tInvalidOTP);
116+
} else {
117+
this.notify.error(this.tsomethingWentWrong);
118+
}
119+
}
120+
});
121+
}
122+
123+
declare module '@glint/environment-ember-loose/registry' {
124+
export default interface Registry {
125+
'AccountSettings::Security::MfaAppEnable': typeof AccountSettingsSecurityMfaAppEnableComponent;
126+
}
127+
}
Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,87 @@
11
<div {{style width='450px'}}>
2-
<AkStack
3-
@direction='column'
4-
@alignItems='center'
5-
@justifyContent='center'
6-
@spacing='2'
7-
>
8-
<AkTypography @variant='h4' @fontWeight='regular'>
9-
{{t 'modalCard.enableMFAApp.scanBarcode'}}
10-
</AkTypography>
11-
12-
<canvas {{did-insert this.onCanvasElementInsert}} />
13-
</AkStack>
14-
15-
<form aria-label='Enable MFA App Otp'>
16-
<AkTypography @color='textSecondary'>
17-
{{t 'modalCard.enableMFAApp.enterCode'}}
18-
</AkTypography>
19-
20-
<AkTextField @formControlClass='mt-2' @value={{@otp}} />
2+
{{#if @loading}}
3+
<AkStack
4+
@direction='column'
5+
@alignItems='center'
6+
@justifyContent='center'
7+
@spacing='2'
8+
>
9+
<AkSkeleton @height='28px' @width='340px' />
10+
11+
<AkSkeleton @height='250px' @width='250px' class='m-3' />
12+
</AkStack>
13+
14+
<AkStack @direction='column'>
15+
<AkSkeleton @height='38px' @width='100%' />
16+
17+
<AkSkeleton @height='35px' @width='100%' class='mt-2' />
2118

19+
<AkStack
20+
class='mt-2'
21+
@alignItems='center'
22+
@justifyContent='flex-end'
23+
@spacing='1.5'
24+
@width='full'
25+
>
26+
<AkSkeleton @height='35px' @width='80px' />
27+
28+
<AkSkeleton @height='35px' @width='80px' />
29+
</AkStack>
30+
</AkStack>
31+
{{else}}
2232
<AkStack
23-
class='mt-2'
33+
@direction='column'
2434
@alignItems='center'
25-
@justifyContent='flex-end'
26-
@spacing='1.5'
35+
@justifyContent='center'
36+
@spacing='2'
2737
>
28-
<AkButton @loading={{@waiting}} {{on 'click' this.continue}}>
29-
{{t 'continue'}}
30-
</AkButton>
38+
<AkTypography
39+
@variant='h4'
40+
@fontWeight='regular'
41+
data-test-mfa-appverify-title
42+
>
43+
{{t 'modalCard.enableMFAApp.scanBarcode'}}
44+
</AkTypography>
3145

32-
<AkButton @variant='outlined' @color='neutral' {{on 'click' this.cancel}}>
33-
{{t 'cancel'}}
34-
</AkButton>
46+
<canvas
47+
{{did-insert this.onCanvasElementInsert}}
48+
data-test-mfa-appverify-qrcode
49+
/>
3550
</AkStack>
36-
</form>
51+
52+
<form {{on 'submit' this.continue}} aria-label='Enable MFA App Otp'>
53+
<AkTypography @color='textSecondary'>
54+
{{t 'modalCard.enableMFAApp.enterCode'}}
55+
</AkTypography>
56+
57+
<AkTextField
58+
@formControlClass='mt-2'
59+
@value={{@otp}}
60+
data-test-mfa-appverify-textfield
61+
/>
62+
63+
<AkStack
64+
class='mt-2'
65+
@alignItems='center'
66+
@justifyContent='flex-end'
67+
@spacing='1.5'
68+
>
69+
<AkButton
70+
@loading={{@waiting}}
71+
{{on 'click' this.continue}}
72+
data-test-mfa-appverify-continue-button
73+
>
74+
{{t 'continue'}}
75+
</AkButton>
76+
77+
<AkButton
78+
@variant='outlined'
79+
@color='neutral'
80+
{{on 'click' this.cancel}}
81+
>
82+
{{t 'cancel'}}
83+
</AkButton>
84+
</AkStack>
85+
</form>
86+
{{/if}}
3787
</div>

app/components/account-settings/security/mfa-appverify/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface AccountSettingsSecurityMfaAppverifySignature {
1515
onContinue: (otp: string) => void;
1616
onCancel: () => void;
1717
waiting: boolean;
18+
loading?: boolean;
1819
};
1920
}
2021

@@ -45,7 +46,9 @@ export default class AccountSettingsSecurityMfaAppverifyComponent extends Compon
4546
}
4647

4748
@action
48-
continue() {
49+
continue(event: Event) {
50+
event.preventDefault();
51+
4952
if (this.args.onContinue instanceof Function) {
5053
this.args.onContinue(this.args.otp);
5154
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<AkModal
2+
@showHeader={{true}}
3+
@headerTitle={{t 'modalCard.mfaTitle.disable'}}
4+
@onClose={{this.closeMFADisable}}
5+
>
6+
<:default>
7+
{{#if (and this.showConfirmDisableMFA this.user.canDisableMfa)}}
8+
<AkTypography data-test-mfa-disable-email-title>
9+
{{t 'modalCard.enableMFAEmail.sendMailMsg'}}
10+
</AkTypography>
11+
{{else}}
12+
<AkStack
13+
@alignItems='center'
14+
class='mb-2 p-2'
15+
local-class='alert-success'
16+
{{style width='450px'}}
17+
>
18+
<AkStack @alignItems='center' @spacing='1'>
19+
<AkIcon @iconName='security' color='inherit' />
20+
21+
<AkTypography data-test-mfa-disable-denied>
22+
{{t 'disableMFADenied'}}
23+
</AkTypography>
24+
</AkStack>
25+
</AkStack>
26+
{{/if}}
27+
28+
{{#if this.showDisableMFA}}
29+
<AkTypography data-test-mfa-disable-desc>
30+
{{t 'modalCard.disableMFA.enterOTP'}}
31+
32+
{{#if this.isEmailMFAEnabled}}
33+
{{t 'modalCard.disableMFA.viaEmail'}}
34+
{{/if}}
35+
36+
{{#if this.isAppMFAEnabled}}
37+
{{t 'modalCard.disableMFA.viaApp'}}
38+
{{/if}}
39+
</AkTypography>
40+
41+
<form
42+
{{on 'submit' (perform this.verifyDisableOTP)}}
43+
aria-label='Disable Email MFA Otp'
44+
class='mt-3'
45+
>
46+
<AkTextField
47+
@value={{this.disableOTP}}
48+
data-test-mfa-disable-textfield
49+
/>
50+
</form>
51+
52+
{{/if}}
53+
</:default>
54+
55+
<:footer>
56+
<AkStack
57+
class='px-3 py-2'
58+
@alignItems='center'
59+
@justifyContent='flex-end'
60+
@spacing='1.5'
61+
>
62+
{{#if (and this.showConfirmDisableMFA this.user.canDisableMfa)}}
63+
<AkButton
64+
@loading={{this.sendDisableMFAOTPEmail.isRunning}}
65+
{{on 'click' (perform this.continueDisableMFA)}}
66+
data-test-mfa-disable-continue
67+
>
68+
{{t 'continue'}}
69+
</AkButton>
70+
71+
<AkButton
72+
@variant='outlined'
73+
@color='neutral'
74+
{{on 'click' this.closeMFADisable}}
75+
>
76+
{{t 'cancel'}}
77+
</AkButton>
78+
{{/if}}
79+
80+
{{#if this.showDisableMFA}}
81+
<AkButton
82+
@loading={{this.verifyDisableOTP.isRunning}}
83+
{{on 'click' (perform this.verifyDisableOTP)}}
84+
data-test-mfa-disable-confirm
85+
>
86+
{{t 'confirm'}}
87+
</AkButton>
88+
89+
<AkButton
90+
@variant='outlined'
91+
@color='neutral'
92+
{{on 'click' this.closeMFADisable}}
93+
>
94+
{{t 'cancel'}}
95+
</AkButton>
96+
{{/if}}
97+
</AkStack>
98+
</:footer>
99+
</AkModal>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.alert-success {
2+
border: 1px solid
3+
var(--account-settings-security-mfa-alert-success-border-color);
4+
background-color: var(
5+
--account-settings-security-mfa-alert-success-background
6+
);
7+
color: var(--account-settings-security-mfa-alert-success-text-color);
8+
}

0 commit comments

Comments
 (0)