diff --git a/template/app/controllers/web/auth.js b/template/app/controllers/web/auth.js index e2e9a314..ae5bb78b 100644 --- a/template/app/controllers/web/auth.js +++ b/template/app/controllers/web/auth.js @@ -13,6 +13,7 @@ const bull = require('../../../bull'); const Users = require('../../models/user'); const passport = require('../../../helpers/passport'); const config = require('../../../config'); +const { Inquiries } = require('../../models'); const sanitize = string => sanitizeHtml(string, { @@ -447,7 +448,10 @@ async function verify(ctx) { ctx.state.redirectTo = redirectTo; - if (ctx.state.user[config.userFields.hasVerifiedEmail]) { + if ( + ctx.state.user[config.userFields.hasVerifiedEmail] && + !ctx.state.user[config.userFields.pendingRecovery] + ) { const message = ctx.translate('EMAIL_ALREADY_VERIFIED'); if (ctx.accepts('html')) { ctx.flash('success', message); @@ -526,9 +530,39 @@ async function verify(ctx) { ctx.state.user[config.userFields.hasVerifiedEmail] = true; await ctx.state.user.save(); - // send the user a success message - const message = ctx.translate('EMAIL_VERIFICATION_SUCCESS'); + const pendingRecovery = ctx.state.user[config.userFields.pendingRecovery]; + if (pendingRecovery) { + const body = {}; + body.email = ctx.state.user.email; + body.message = ctx.translate('SUPPORT_REQUEST_MESSAGE'); + body.is_email_only = true; + const inquiry = await Inquiries.create({ + ...body, + ip: ctx.ip + }); + + ctx.logger.debug('created inquiry', inquiry); + + const job = await bull.add('email', { + template: 'recovery', + message: { + to: ctx.state.user.email, + cc: config.email.message.from + }, + locals: { + locale: ctx.locale, + inquiry + } + }); + + ctx.logger.info('added job', bull.getMeta({ job })); + } + + const message = pendingRecovery + ? ctx.translate('PENDING_RECOVERY_VERIFICATION_SUCCESS') + : ctx.translate('EMAIL_VERIFICATION_SUCCESS'); + redirectTo = pendingRecovery ? '/logout' : redirectTo; if (ctx.accepts('html')) { ctx.flash('success', message); ctx.redirect(redirectTo); diff --git a/template/app/controllers/web/otp/index.js b/template/app/controllers/web/otp/index.js index ae9d0d82..8db3cabc 100644 --- a/template/app/controllers/web/otp/index.js +++ b/template/app/controllers/web/otp/index.js @@ -2,6 +2,5 @@ const disable = require('./disable'); const recovery = require('./recovery'); const setup = require('./setup'); const keys = require('./keys'); -const verify = require('./verify'); -module.exports = { disable, recovery, keys, setup, verify }; +module.exports = { disable, recovery, keys, setup }; diff --git a/template/app/controllers/web/otp/keys.js b/template/app/controllers/web/otp/keys.js index 328fad14..c89832e9 100644 --- a/template/app/controllers/web/otp/keys.js +++ b/template/app/controllers/web/otp/keys.js @@ -1,7 +1,7 @@ async function keys(ctx) { // this is like a migration, it will automatically add token + keys if needed await ctx.state.user.save(); - await ctx.render('otp/keys'); + await ctx.render('otp/setup'); } module.exports = keys; diff --git a/template/app/controllers/web/otp/recovery.js b/template/app/controllers/web/otp/recovery.js index c6ccb12d..b162390f 100644 --- a/template/app/controllers/web/otp/recovery.js +++ b/template/app/controllers/web/otp/recovery.js @@ -1,7 +1,7 @@ const config = require('../../../../config'); async function recovery(ctx) { - const redirectTo = `/${ctx.locale}/otp/recovery/verify`; + const redirectTo = `/${ctx.locale}/verify`; ctx.state.redirectTo = redirectTo; diff --git a/template/app/controllers/web/otp/setup.js b/template/app/controllers/web/otp/setup.js index 820dd566..99a727f4 100644 --- a/template/app/controllers/web/otp/setup.js +++ b/template/app/controllers/web/otp/setup.js @@ -40,7 +40,7 @@ async function setup(ctx) { ctx.state.user[config.passport.fields.otpToken] ); ctx.state.qrcode = await qrcode.toDataURL(ctx.state.otpTokenURI, opts); - return ctx.render('otp/setup'); + return ctx.render('otp/enable'); } ctx.state.user[config.passport.fields.otpEnabled] = true; @@ -73,7 +73,7 @@ async function setup(ctx) { ctx.state.user[config.passport.fields.otpToken] ); ctx.state.qrcode = await qrcode.toDataURL(ctx.state.otpTokenURI, opts); - return ctx.render('otp/setup'); + return ctx.render('otp/enable'); } module.exports = setup; diff --git a/template/app/controllers/web/otp/verify.js b/template/app/controllers/web/otp/verify.js deleted file mode 100644 index e50214c8..00000000 --- a/template/app/controllers/web/otp/verify.js +++ /dev/null @@ -1,124 +0,0 @@ -const Boom = require('@hapi/boom'); -const isSANB = require('is-string-and-not-blank'); -const { boolean } = require('boolean'); - -const { Inquiries } = require('../../../models'); -const bull = require('../../../../bull'); -const config = require('../../../../config'); - -// eslint-disable-next-line complexity -async function verify(ctx) { - let redirectTo = `/${ctx.locale}/login`; - - if (ctx.session && ctx.session.returnTo) { - redirectTo = ctx.session.returnTo; - delete ctx.session.returnTo; - } - - ctx.state.redirectTo = redirectTo; - - // allow user to click a button to request a new email after 60 seconds - // after their last attempt to get a verification email - const resend = ctx.method === 'GET' && boolean(ctx.query.resend); - - if ( - !ctx.state.user[config.userFields.verificationPin] || - !ctx.state.user[config.userFields.verificationPinExpiresAt] || - ctx.state.user[config.userFields.verificationPinHasExpired] || - resend - ) { - try { - ctx.state.user = await ctx.state.user.sendVerificationEmail(ctx); - } catch (err) { - // wrap with try/catch to prevent redirect looping - // (even though the koa redirect loop package will help here) - if (!err.isBoom) return ctx.throw(err); - ctx.logger.warn(err); - if (ctx.accepts('html')) { - ctx.flash('warning', err.message); - ctx.redirect(redirectTo); - } else { - ctx.body = { message: err.message }; - } - - return; - } - - const message = ctx.translate( - ctx.state.user[config.userFields.verificationPinHasExpired] - ? 'EMAIL_VERIFICATION_EXPIRED' - : 'EMAIL_VERIFICATION_SENT' - ); - - if (!ctx.accepts('html')) { - ctx.body = { message }; - return; - } - - ctx.flash('success', message); - } - - // if it's a GET request then render the page - if (ctx.method === 'GET' && !isSANB(ctx.query.pin)) - return ctx.render('verify'); - - // if it's a POST request then ensure the user entered the 6 digit pin - // otherwise if it's a GET request then use the ctx.query.pin - let pin = ''; - if (ctx.method === 'GET') pin = ctx.query.pin; - else pin = isSANB(ctx.request.body.pin) ? ctx.request.body.pin : ''; - - // convert to digits only - pin = pin.replace(/\D/g, ''); - - // ensure pin matches up - if ( - !ctx.state.user[config.userFields.verificationPin] || - pin !== ctx.state.user[config.userFields.verificationPin] - ) - return ctx.throw( - Boom.badRequest(ctx.translate('INVALID_VERIFICATION_PIN')) - ); - - try { - const body = {}; - body.email = ctx.state.user.email; - body.message = ctx.translate('SUPPORT_REQUEST_MESSAGE'); - body.is_email_only = true; - const inquiry = await Inquiries.create({ - ...body, - ip: ctx.ip - }); - - ctx.logger.debug('created inquiry', inquiry); - - const job = await bull.add('email', { - template: 'recovery', - message: { - to: ctx.state.user.email, - cc: config.email.message.from - }, - locals: { - locale: ctx.locale, - inquiry - } - }); - - ctx.logger.info('added job', bull.getMeta({ job })); - - const message = ctx.translate('PENDING_RECOVERY_VERIFICATION_SUCCESS'); - if (ctx.accepts('html')) { - ctx.flash('success', message); - ctx.redirect(redirectTo); - } else { - ctx.body = { message, redirectTo }; - } - } catch (err) { - ctx.logger.error(err); - throw Boom.badRequest(ctx.translate('SUPPORT_REQUEST_ERROR')); - } - - ctx.logout(); -} - -module.exports = verify; diff --git a/template/app/views/otp/enable.pug b/template/app/views/otp/enable.pug new file mode 100644 index 00000000..376aae18 --- /dev/null +++ b/template/app/views/otp/enable.pug @@ -0,0 +1,115 @@ + +extends ../layout + +block body + #authenticator-apps-modal(tabindex='-1', role='dialog').modal.fade + .modal-dialog.modal-lg(role='document') + .modal-content + .modal-header + h1.h4.modal-title= t('Authenticator Apps') + button(type='button', data-dismiss='modal', aria-label='Close').close + span(aria-hidden='true') × + .modal-body + .alert.alert-info= t('Our recommended apps are listed below. If you have feedback on this list, then please let us know.') + table.table + thead + tr + th(scope='col')= t('App') + th(scope='col')= t('Links') + th(scope='col').text-center= t('Open-Source') + tbody + tr + td.align-middle: a(href='https://freeotp.github.io/', rel='noopener', target='_blank') FreeOTP + td.align-middle + ul.list-inline.mb-0 + li.list-inline-item.mb-1.mb-md-0 + a(href='https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-google-play + = ' ' + = t('Google Play') + li.list-inline-item.mb-1.mb-md-0 + a(href='https://itunes.apple.com/us/app/freeotp-authenticator/id872559395?mt=8', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-app-store-ios + = ' ' + = t('App Store') + li.list-inline-item + a(href='https://f-droid.org/en/packages/org.fedorahosted.freeotp/', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-android + = ' ' + = t('F-Droid') + td.align-middle.text-center: i.fa.fa-check-circle.text-success + tr + td.align-middle: a(href='https://github.com/andOTP/andOTP', rel='noopener', target='_blank') andOTP + td.align-middle + ul.list-inline.mb-0 + li.list-inline-item.mb-1.mb-md-0 + a(href='https://f-droid.org/repo/org.shadowice.flocke.andotp_28.apk', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-google-play + = ' ' + = t('Google Play') + li.list-inline-item + a(href='https://f-droid.org/en/packages/org.shadowice.flocke.andotp/', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-android + = ' ' + = t('F-Droid') + td.align-middle.text-center: i.fa.fa-check-circle.text-success + tr + td.align-middle: a(href='https://authy.com/', rel='noopener', target='_blank') Authy + td.align-middle + ul.list-inline.mb-0 + li.list-inline-item.mb-1.mb-md-0 + a(href='https://play.google.com/store/apps/details?id=com.authy.authy', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-google-play + = ' ' + = t('Google Play') + li.list-inline-item + a(href='https://itunes.apple.com/us/app/authy/id494168017', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-app-store-ios + = ' ' + = t('App Store') + td.align-middle.text-center: i.fa.fa-times-circle.text-danger + tr + td.align-middle: a(href='https://support.google.com/accounts/answer/1066447', rel='noopener', target='_blank') Google Authenticator + td.align-middle + ul.list-inline.mb-0 + li.list-inline-item.mb-1.mb-md-0 + a(href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-google-play + = ' ' + = t('Google Play') + li.list-inline-item + a(href='https://apps.apple.com/us/app/google-authenticator/id388497605', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary + i.fab.fa-app-store-ios + = ' ' + = t('App Store') + td.align-middle.text-center: i.fa.fa-times-circle.text-danger + .container.py-3 + .row + .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3 + h1.my-3.py-3.text-center= t('Setup OTP') + form(action=ctx.path, method='POST').confirm-prompt + input(type="hidden", name="_csrf", value=ctx.csrf) + .form-group + label(for='otp-step-one') + != t('Step 1: Install and open an authenticator app.') + .form-group + label(for='otp-step-two') + != t('Step 2: Scan this QR code and enter its generated token:') + img(src=qrcode, width=250, height=250, alt="").d-block.my-3 + .form-group.floating-label + input(type='text', name='token', required, placeholder=' ').form-control.form-control-lg#input-token + label(for='input-token') Verification Token + .form-group + a.btn.btn-link.text-center.text-primary(href='#' data-toggle='collapse' data-target='#otp-copy') + = t('Can’t scan the QR code? Configure with this code') + = ' ' + i.fa.fa-angle-down + #otp-copy.collapse + .input-group.input-group-lg.floating-label.form-group + input(type='text', readonly, value=user[config.passport.fields.otpToken]).form-control#otp-token + .input-group-append + button(type='button', data-toggle="clipboard", data-clipboard-target="#otp-token").btn.btn-dark + i.fa.fa-clipboard + = ' ' + = t('Copy') + button(type='submit').btn.btn-lg.btn-block.btn-primary= t('Complete Setup') diff --git a/template/app/views/otp/keys.pug b/template/app/views/otp/keys.pug index a370b25d..cc6f3113 100644 --- a/template/app/views/otp/keys.pug +++ b/template/app/views/otp/keys.pug @@ -4,35 +4,15 @@ extends ../layout block body .container.py-3 .row - .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3.text-center - h1.my-3.py-3= t('Setup OTP') - .alert.alert-warning(role='alert') - i.fa.fa-exclamation-triangle - = ' ' - = t('Download your emergency recovery keys below.') - textarea(rows='5').form-control.text-monospace.text-center.rounded-bottom-0.border-dark#otp-recovery-keys - each key, i in user[config.userFields.otpRecoveryKeys] - = key - if i !== user[config.userFields.otpRecoveryKeys].length - 1 - if i % 2 - = '\n' - else - = ' ' - form(action=l('/my-account/recovery-keys'), method='POST') - input(type="hidden", name="_csrf", value=ctx.csrf) - .d-flex.btn-group(role='group') - button(type='submit').btn.btn-dark.rounded-top-0 - i.fa.fa-file-download - = ' ' - = t('Download') - button(type='button', data-toggle="clipboard", data-clipboard-text=user[config.userFields.otpRecoveryKeys].join('\r\n')).btn.btn-dark.rounded-top-0 - i.fa.fa-clipboard - = ' ' - = t('Copy') - form(action=ctx.path, method="POST", autocomplete="off", class=user[config.userFields.otpRecoveryKeys] ? '' : 'confirm-prompt') - input(type="hidden", name="_csrf", value=ctx.csrf) - if user[config.userFields.hasSetPassword] - .form-group.floating-label.mt-4 - input#input-password.form-control.form-control-lg(type="password", autocomplete="off", name="password" required) - label(for="input-password")= t('Confirm Password') - button.btn.btn-primary.btn-lg.btn-block.mt-2(type="submit")= t('Continue') + .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3 + .card + .card-body + .text-center + h1.card-title.h4= t('OTP Recovery') + p= t('Please enter an OTP recovery key to login.') + form(action=`${ctx.locale}/otp/keys`, method="POST", autocomplete="off").ajax-form + input(type="hidden", name="_csrf", value=ctx.csrf) + .form-group.floating-label + input#input-text.form-control.form-control-lg(type="text", autocomplete="off", name="recovery_passcode", placeholder=t('')) + label(for="recovery_passcode")= t('Recovery Passcode') + button.btn.btn-primary.btn-block.btn-lg(type="submit")= t('Submit Passcode') diff --git a/template/app/views/otp/login.pug b/template/app/views/otp/login.pug index 7471cf49..c64a3d81 100644 --- a/template/app/views/otp/login.pug +++ b/template/app/views/otp/login.pug @@ -34,7 +34,7 @@ block body .alert.alert-light.border.mt-3.text-center = t('Having trouble?') = ' ' - a(href=l('/otp/recovery/keys'))= t('Use a recovery key') + a(href=l('/otp/keys'))= t('Use a recovery key') p.mb-0.text-center small.text-muted = t('Lose your recovery keys?') diff --git a/template/app/views/otp/recovery.pug b/template/app/views/otp/recovery.pug deleted file mode 100644 index 8355e0bf..00000000 --- a/template/app/views/otp/recovery.pug +++ /dev/null @@ -1,18 +0,0 @@ - -extends ../layout - -block body - .container.py-3 - .row - .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3 - .card - .card-body - .text-center - h1.card-title.h4= t('OTP Recovery') - p= t('Please enter an OTP recovery key to login.') - form(action=`${ctx.locale}/otp/recovery/keys`, method="POST", autocomplete="off").ajax-form - input(type="hidden", name="_csrf", value=ctx.csrf) - .form-group.floating-label - input#input-text.form-control.form-control-lg(type="text", autocomplete="off", name="recovery_passcode", placeholder=t('')) - label(for="recovery_passcode")= t('Recovery Passcode') - button.btn.btn-primary.btn-block.btn-lg(type="submit")= t('Submit Passcode') diff --git a/template/app/views/otp/setup.pug b/template/app/views/otp/setup.pug index 376aae18..a370b25d 100644 --- a/template/app/views/otp/setup.pug +++ b/template/app/views/otp/setup.pug @@ -2,114 +2,37 @@ extends ../layout block body - #authenticator-apps-modal(tabindex='-1', role='dialog').modal.fade - .modal-dialog.modal-lg(role='document') - .modal-content - .modal-header - h1.h4.modal-title= t('Authenticator Apps') - button(type='button', data-dismiss='modal', aria-label='Close').close - span(aria-hidden='true') × - .modal-body - .alert.alert-info= t('Our recommended apps are listed below. If you have feedback on this list, then please let us know.') - table.table - thead - tr - th(scope='col')= t('App') - th(scope='col')= t('Links') - th(scope='col').text-center= t('Open-Source') - tbody - tr - td.align-middle: a(href='https://freeotp.github.io/', rel='noopener', target='_blank') FreeOTP - td.align-middle - ul.list-inline.mb-0 - li.list-inline-item.mb-1.mb-md-0 - a(href='https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-google-play - = ' ' - = t('Google Play') - li.list-inline-item.mb-1.mb-md-0 - a(href='https://itunes.apple.com/us/app/freeotp-authenticator/id872559395?mt=8', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-app-store-ios - = ' ' - = t('App Store') - li.list-inline-item - a(href='https://f-droid.org/en/packages/org.fedorahosted.freeotp/', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-android - = ' ' - = t('F-Droid') - td.align-middle.text-center: i.fa.fa-check-circle.text-success - tr - td.align-middle: a(href='https://github.com/andOTP/andOTP', rel='noopener', target='_blank') andOTP - td.align-middle - ul.list-inline.mb-0 - li.list-inline-item.mb-1.mb-md-0 - a(href='https://f-droid.org/repo/org.shadowice.flocke.andotp_28.apk', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-google-play - = ' ' - = t('Google Play') - li.list-inline-item - a(href='https://f-droid.org/en/packages/org.shadowice.flocke.andotp/', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-android - = ' ' - = t('F-Droid') - td.align-middle.text-center: i.fa.fa-check-circle.text-success - tr - td.align-middle: a(href='https://authy.com/', rel='noopener', target='_blank') Authy - td.align-middle - ul.list-inline.mb-0 - li.list-inline-item.mb-1.mb-md-0 - a(href='https://play.google.com/store/apps/details?id=com.authy.authy', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-google-play - = ' ' - = t('Google Play') - li.list-inline-item - a(href='https://itunes.apple.com/us/app/authy/id494168017', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-app-store-ios - = ' ' - = t('App Store') - td.align-middle.text-center: i.fa.fa-times-circle.text-danger - tr - td.align-middle: a(href='https://support.google.com/accounts/answer/1066447', rel='noopener', target='_blank') Google Authenticator - td.align-middle - ul.list-inline.mb-0 - li.list-inline-item.mb-1.mb-md-0 - a(href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-google-play - = ' ' - = t('Google Play') - li.list-inline-item - a(href='https://apps.apple.com/us/app/google-authenticator/id388497605', rel='noopener', target='_blank').btn.btn-sm.btn-outline-secondary - i.fab.fa-app-store-ios - = ' ' - = t('App Store') - td.align-middle.text-center: i.fa.fa-times-circle.text-danger .container.py-3 .row - .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3 - h1.my-3.py-3.text-center= t('Setup OTP') - form(action=ctx.path, method='POST').confirm-prompt + .col-xs-12.col-sm-12.col-md-6.offset-md-3.col-lg-6.offset-lg-3.text-center + h1.my-3.py-3= t('Setup OTP') + .alert.alert-warning(role='alert') + i.fa.fa-exclamation-triangle + = ' ' + = t('Download your emergency recovery keys below.') + textarea(rows='5').form-control.text-monospace.text-center.rounded-bottom-0.border-dark#otp-recovery-keys + each key, i in user[config.userFields.otpRecoveryKeys] + = key + if i !== user[config.userFields.otpRecoveryKeys].length - 1 + if i % 2 + = '\n' + else + = ' ' + form(action=l('/my-account/recovery-keys'), method='POST') input(type="hidden", name="_csrf", value=ctx.csrf) - .form-group - label(for='otp-step-one') - != t('Step 1: Install and open an authenticator app.') - .form-group - label(for='otp-step-two') - != t('Step 2: Scan this QR code and enter its generated token:') - img(src=qrcode, width=250, height=250, alt="").d-block.my-3 - .form-group.floating-label - input(type='text', name='token', required, placeholder=' ').form-control.form-control-lg#input-token - label(for='input-token') Verification Token - .form-group - a.btn.btn-link.text-center.text-primary(href='#' data-toggle='collapse' data-target='#otp-copy') - = t('Can’t scan the QR code? Configure with this code') + .d-flex.btn-group(role='group') + button(type='submit').btn.btn-dark.rounded-top-0 + i.fa.fa-file-download = ' ' - i.fa.fa-angle-down - #otp-copy.collapse - .input-group.input-group-lg.floating-label.form-group - input(type='text', readonly, value=user[config.passport.fields.otpToken]).form-control#otp-token - .input-group-append - button(type='button', data-toggle="clipboard", data-clipboard-target="#otp-token").btn.btn-dark - i.fa.fa-clipboard - = ' ' - = t('Copy') - button(type='submit').btn.btn-lg.btn-block.btn-primary= t('Complete Setup') + = t('Download') + button(type='button', data-toggle="clipboard", data-clipboard-text=user[config.userFields.otpRecoveryKeys].join('\r\n')).btn.btn-dark.rounded-top-0 + i.fa.fa-clipboard + = ' ' + = t('Copy') + form(action=ctx.path, method="POST", autocomplete="off", class=user[config.userFields.otpRecoveryKeys] ? '' : 'confirm-prompt') + input(type="hidden", name="_csrf", value=ctx.csrf) + if user[config.userFields.hasSetPassword] + .form-group.floating-label.mt-4 + input#input-password.form-control.form-control-lg(type="password", autocomplete="off", name="password" required) + label(for="input-password")= t('Confirm Password') + button.btn.btn-primary.btn-lg.btn-block.mt-2(type="submit")= t('Continue') diff --git a/template/routes/web/otp.js b/template/routes/web/otp.js index 45655178..f1fef3fe 100644 --- a/template/routes/web/otp.js +++ b/template/routes/web/otp.js @@ -10,13 +10,11 @@ router.use(policies.ensureLoggedIn); router .get('/login', render('otp/login')) .post('/login', web.auth.loginOtp) - .get('/setup', render('otp/keys')) + .get('/setup', render('otp/setup')) .post('/setup', web.otp.setup) .post('/disable', web.otp.disable) .post('/recovery', web.otp.recovery) - .get('/recovery/verify', web.otp.verify) - .post('/recovery/verify', web.otp.verify) - .get('/recovery/keys', render('otp/recovery')) - .post('/recovery/keys', web.auth.recoveryKey); + .get('/keys', render('otp/keys')) + .post('/keys', web.auth.recoveryKey); module.exports = router; diff --git a/test/snapshots/test.js.md b/test/snapshots/test.js.md index 8f9faa71..790fb845 100644 --- a/test/snapshots/test.js.md +++ b/test/snapshots/test.js.md @@ -41,7 +41,6 @@ Generated by [AVA](https://ava.li). 'app/controllers/web/otp/keys.js', 'app/controllers/web/otp/recovery.js', 'app/controllers/web/otp/setup.js', - 'app/controllers/web/otp/verify.js', 'app/controllers/web/support.js', 'app/models/index.js', 'app/models/inquiry.js', @@ -64,9 +63,9 @@ Generated by [AVA](https://ava.li). 'app/views/layout.pug', 'app/views/my-account/index.pug', 'app/views/my-account/security.pug', + 'app/views/otp/enable.pug', 'app/views/otp/keys.pug', 'app/views/otp/login.pug', - 'app/views/otp/recovery.pug', 'app/views/otp/setup.pug', 'app/views/privacy.pug', 'app/views/register-or-login.pug', diff --git a/test/snapshots/test.js.snap b/test/snapshots/test.js.snap index 696d998a..71875c4e 100644 Binary files a/test/snapshots/test.js.snap and b/test/snapshots/test.js.snap differ