Skip to content

Commit b2c4c00

Browse files
committed
Convert MFA to Glimmer Component
1 parent 7c24b72 commit b2c4c00

File tree

19 files changed

+1224
-1258
lines changed

19 files changed

+1224
-1258
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: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
constructor(
39+
owner: Owner,
40+
args: AccountSettingsSecurityMfaAppEnableSignature['Args']
41+
) {
42+
super(owner, args);
43+
44+
this.noMFAEnableApp.perform();
45+
}
46+
47+
get user() {
48+
return this.args.user;
49+
}
50+
51+
@action
52+
closeAppEnable() {
53+
this.args.closeModal();
54+
}
55+
56+
noMFAEnableApp = task(async () => {
57+
this.appOTP = '';
58+
59+
try {
60+
const tokenData = await this.getMFAEnableAppToken.perform();
61+
62+
this.mfaAppSecret = tokenData.secret;
63+
64+
this.mfaAppToken = tokenData.token;
65+
} catch (error) {
66+
this.closeAppEnable();
67+
68+
this.notify.error(this.intl.t('somethingWentWrong'));
69+
}
70+
});
71+
72+
getMFAEnableAppToken = task(async () => {
73+
return await this.ajax.post<TokenData>(this.mfaEndpoint, {
74+
data: {
75+
method: ENUMS.MFA_METHOD.TOTP,
76+
},
77+
});
78+
});
79+
80+
verifyAppOTP = task(async () => {
81+
const otp = this.appOTP;
82+
const token = this.mfaAppToken;
83+
84+
try {
85+
await this.ajax.post(this.mfaEndpoint, {
86+
data: {
87+
method: ENUMS.MFA_METHOD.TOTP,
88+
otp: otp || '',
89+
token: token,
90+
},
91+
});
92+
93+
this.args.reloadMfa();
94+
95+
this.closeAppEnable();
96+
} catch (error) {
97+
const errorObj = (error as AjaxError).payload || {};
98+
const otpMsg = errorObj.otp && errorObj.otp[0];
99+
100+
if (otpMsg) {
101+
this.notify.error(this.intl.t('invalidOTP'));
102+
} else {
103+
this.notify.error(this.intl.t('somethingWentWrong'));
104+
}
105+
}
106+
});
107+
}
108+
109+
declare module '@glint/environment-ember-loose/registry' {
110+
export default interface Registry {
111+
'AccountSettings::Security::MfaAppEnable': typeof AccountSettingsSecurityMfaAppEnableComponent;
112+
}
113+
}
Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,72 @@
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 @variant='h4' @fontWeight='regular'>
39+
{{t 'modalCard.enableMFAApp.scanBarcode'}}
40+
</AkTypography>
3141

32-
<AkButton @variant='outlined' @color='neutral' {{on 'click' this.cancel}}>
33-
{{t 'cancel'}}
34-
</AkButton>
42+
<canvas {{did-insert this.onCanvasElementInsert}} />
3543
</AkStack>
36-
</form>
44+
45+
<form {{on 'submit' this.continue}} aria-label='Enable MFA App Otp'>
46+
<AkTypography @color='textSecondary'>
47+
{{t 'modalCard.enableMFAApp.enterCode'}}
48+
</AkTypography>
49+
50+
<AkTextField @formControlClass='mt-2' @value={{@otp}} />
51+
52+
<AkStack
53+
class='mt-2'
54+
@alignItems='center'
55+
@justifyContent='flex-end'
56+
@spacing='1.5'
57+
>
58+
<AkButton @loading={{@waiting}} {{on 'click' this.continue}}>
59+
{{t 'continue'}}
60+
</AkButton>
61+
62+
<AkButton
63+
@variant='outlined'
64+
@color='neutral'
65+
{{on 'click' this.cancel}}
66+
>
67+
{{t 'cancel'}}
68+
</AkButton>
69+
</AkStack>
70+
</form>
71+
{{/if}}
3772
</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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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>
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>{{t 'disableMFADenied'}}</AkTypography>
22+
</AkStack>
23+
</AkStack>
24+
{{/if}}
25+
26+
{{#if this.showDisableMFA}}
27+
<AkTypography>
28+
{{t 'modalCard.disableMFA.enterOTP'}}
29+
30+
{{#if this.isEmailMFAEnabled}}
31+
{{t 'modalCard.disableMFA.viaEmail'}}
32+
{{/if}}
33+
34+
{{#if this.isAppMFAEnabled}}
35+
{{t 'modalCard.disableMFA.viaApp'}}
36+
{{/if}}
37+
</AkTypography>
38+
39+
<form
40+
{{on 'submit' (perform this.verifyDisableOTP)}}
41+
aria-label='Disable Email MFA Otp'
42+
class='mt-3'
43+
>
44+
<AkTextField @value={{this.disableOTP}} />
45+
</form>
46+
47+
{{/if}}
48+
</:default>
49+
50+
<:footer>
51+
<AkStack
52+
class='px-3 py-2'
53+
@alignItems='center'
54+
@justifyContent='flex-end'
55+
@spacing='1.5'
56+
>
57+
{{#if (and this.showConfirmDisableMFA this.user.canDisableMfa)}}
58+
<AkButton
59+
@loading={{this.sendDisableMFAOTPEmail.isRunning}}
60+
{{on 'click' (perform this.continueDisableMFA)}}
61+
>
62+
{{t 'continue'}}
63+
</AkButton>
64+
65+
<AkButton
66+
@variant='outlined'
67+
@color='neutral'
68+
{{on 'click' this.closeMFADisable}}
69+
>
70+
{{t 'cancel'}}
71+
</AkButton>
72+
{{/if}}
73+
74+
{{#if this.showDisableMFA}}
75+
<AkButton
76+
@loading={{this.verifyDisableOTP.isRunning}}
77+
{{on 'click' (perform this.verifyDisableOTP)}}
78+
>
79+
{{t 'confirm'}}
80+
</AkButton>
81+
82+
<AkButton
83+
@variant='outlined'
84+
@color='neutral'
85+
{{on 'click' this.closeMFADisable}}
86+
>
87+
{{t 'cancel'}}
88+
</AkButton>
89+
{{/if}}
90+
</AkStack>
91+
</:footer>
92+
</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)