diff --git a/TODO.text b/TODO.text index 0edd188..750aad6 100644 --- a/TODO.text +++ b/TODO.text @@ -1,2 +1,2 @@ -- MFA recovery codes handling - OAuth2 -> support refresh tokens +- Localize all the things diff --git a/Units.flitter.js b/Units.flitter.js index c9337f7..2354c6b 100644 --- a/Units.flitter.js +++ b/Units.flitter.js @@ -31,6 +31,7 @@ const FlitterUnits = { * Custom units that modify or add functionality that needs to be made * available to the middleware-routing-controller stack. */ + 'Locale' : require('flitter-i18n/src/LocaleUnit'), 'Redis' : require('flitter-redis/src/RedisUnit'), 'Jobs' : require('flitter-jobs/src/JobsUnit'), 'Settings' : require('./app/unit/SettingsUnit'), diff --git a/app/assets/app/auth/MFAChallenge.component.js b/app/assets/app/auth/MFAChallenge.component.js index 2a16d50..5e9b734 100644 --- a/app/assets/app/auth/MFAChallenge.component.js +++ b/app/assets/app/auth/MFAChallenge.component.js @@ -26,6 +26,9 @@ const template = `
{{ error_message }}
{{ other_message }}
+ Lost your MFA device? ` @@ -70,4 +73,8 @@ export default class MFAChallengePage extends Component { return false } } + + async on_do_recovery(event) { + await location_service.redirect('/auth/mfa/recovery', 0) + } } diff --git a/app/assets/app/auth/MFARecovery.component.js b/app/assets/app/auth/MFARecovery.component.js new file mode 100644 index 0000000..5da2a73 --- /dev/null +++ b/app/assets/app/auth/MFARecovery.component.js @@ -0,0 +1,72 @@ +import { Component } from '../../lib/vues6/vues6.js' +import { location_service } from '../service/Location.service.js' +import { auth_api } from '../service/AuthApi.service.js' + +const template = ` +
+
+
{{ app_name }}
+
+ To recover access to your account, you can enter one of the generated MFA recovery codes: +
+
+ +
+
{{ error_message }}
+
{{ other_message }}
+ Have a normal MFA code? +
+
+
+` + +export default class MFARecoveryComponent extends Component { + static get selector() { return 'coreid-mfa-recovery-page' } + static get template() { return template } + static get props() { return ['app_name'] } + + verify_success = false + loading = false + recovery_code = '' + error_message = '' + other_message = '' + + async vue_on_create() { + this.$nextTick(() => { + this.$refs.verify_input.focus() + }) + } + + async on_key_up($event) { + if ( this.recovery_code.length === 36 ) { + this.error_message = '' + this.other_message = '' + this.loading = true + const result = await auth_api.attempt_mfa_recovery(this.recovery_code) + if ( result && result.success ) { + this.other_message = `Success! There are only ${result.remaining_codes} recovery codes remaining. Let's get you on your way...` + await location_service.redirect(result.next_destination, 5000) + } else { + this.loading = false + this.error_message = 'Hm. It doesn\'t look like that code is valid.' + } + } + } + + async on_do_challenge() { + await location_service.redirect('/auth/mfa/challenge', 0) + } +} diff --git a/app/assets/app/components.js b/app/assets/app/components.js index 93f3d5a..aa0e0ad 100644 --- a/app/assets/app/components.js +++ b/app/assets/app/components.js @@ -7,6 +7,7 @@ import PasswordResetComponent from './auth/PasswordReset.component.js' import InvokeActionComponent from './InvokeAction.component.js' import RegistrationFormComponent from './auth/register/Form.component.js' import MessageContainerComponent from './dash/message/MessageContainer.component.js' +import MFARecoveryComponent from './auth/MFARecovery.component.js' const components = { AuthLoginForm, @@ -18,6 +19,7 @@ const components = { InvokeActionComponent, RegistrationFormComponent, MessageContainerComponent, + MFARecoveryComponent, } export { components } diff --git a/app/assets/app/dash/message/MessageContainer.component.js b/app/assets/app/dash/message/MessageContainer.component.js index a8bb85f..3e270f6 100644 --- a/app/assets/app/dash/message/MessageContainer.component.js +++ b/app/assets/app/dash/message/MessageContainer.component.js @@ -35,7 +35,7 @@ const template = `