Add MFA support
This commit is contained in:
@@ -28,9 +28,11 @@ class Home extends Controller {
|
||||
}
|
||||
|
||||
async tmpl(req, res) {
|
||||
return res.page('tmpl', this.Vue.data({
|
||||
login_message: 'Please sign-in to continue.'
|
||||
}))
|
||||
return this.Vue.auth_message(res, {
|
||||
message: 'This is a test message. Hello, baby girl; I love you very very much!.',
|
||||
next_destination: '/auth/login',
|
||||
button_text: 'Continue',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const { Controller } = require('libflitter')
|
||||
|
||||
class AuthController extends Controller {
|
||||
static get services() {
|
||||
return [...super.services, 'models', 'auth']
|
||||
return [...super.services, 'models', 'auth', 'MFA', 'output']
|
||||
}
|
||||
|
||||
async validate_username(req, res, next) {
|
||||
@@ -50,9 +50,14 @@ class AuthController extends Controller {
|
||||
await flitter.session(req, user)
|
||||
|
||||
let destination = this.configs.get('auth.default_login_route')
|
||||
if ( req?.session?.auth?.flow ) {
|
||||
if ( req.session.auth.flow ) {
|
||||
destination = req.session.auth.flow
|
||||
req.session.auth.flow = false
|
||||
}
|
||||
|
||||
// TODO remember-device feature
|
||||
if ( user.mfa_enabled && !req.session.mfa_remember ) {
|
||||
req.session.auth.in_dmz = true
|
||||
destination = '/auth/mfa/challenge'
|
||||
}
|
||||
|
||||
return res.api({
|
||||
@@ -62,6 +67,72 @@ class AuthController extends Controller {
|
||||
})
|
||||
}
|
||||
|
||||
async generate_mfa_key(req, res, next) {
|
||||
if ( req.user.mfa_enabled )
|
||||
return res.status(400)
|
||||
.message(`MFA already configured for user. Cannot fetch key.`)
|
||||
.api()
|
||||
|
||||
const MFAToken = this.models.get('auth:MFAToken')
|
||||
const secret = await this.MFA.secret(req.user)
|
||||
|
||||
req.user.mfa_token = new MFAToken({
|
||||
secret: secret.base32,
|
||||
otpauth_url: secret.otpauth_url
|
||||
}, req.user)
|
||||
await req.user.save()
|
||||
|
||||
return res.api({
|
||||
success: true,
|
||||
secret: secret.base32,
|
||||
otpauth_url: secret.otpauth_url,
|
||||
qr_code: await this.MFA.qr_code(secret)
|
||||
})
|
||||
}
|
||||
|
||||
async attempt_mfa(req, res, next) {
|
||||
if ( !req.user.mfa_token )
|
||||
return res.status(400)
|
||||
.message(`The user does not have MFA configured.`)
|
||||
.api()
|
||||
|
||||
const code = req.body.verify_code
|
||||
const token = req.user.mfa_token
|
||||
const is_valid = token.verify(code)
|
||||
|
||||
let next_destination = undefined
|
||||
if ( is_valid ) {
|
||||
req.session.auth.in_dmz = false
|
||||
next_destination = req.session.auth.flow || this.configs.get('auth.default_login_route')
|
||||
delete req.session.auth.flow
|
||||
}
|
||||
|
||||
req.session.mfa_remember = true
|
||||
|
||||
return res.api({
|
||||
success: true,
|
||||
verify_code: code,
|
||||
is_valid,
|
||||
next_destination,
|
||||
})
|
||||
}
|
||||
|
||||
async enable_mfa(req, res, next) {
|
||||
if ( !req.user.mfa_token )
|
||||
return res.status(400)
|
||||
.message(`The user does not have an MFA token configured.`)
|
||||
.api()
|
||||
|
||||
req.user.mfa_enabled = true
|
||||
req.user.save()
|
||||
|
||||
// TODO invalidate existing tokens and other logins
|
||||
const flitter = await this.auth.get_provider('flitter')
|
||||
await flitter.logout(req)
|
||||
|
||||
return res.api({success: true, mfa_enabled: req.user.mfa_enabled})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = exports = AuthController
|
||||
|
||||
@@ -17,6 +17,13 @@ class Forms extends FormController {
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
async logout_provider_present_success(req, res, next) {
|
||||
return this.Vue.auth_message(res, {
|
||||
message: 'You have been successfully logged out.',
|
||||
next_destination: '/',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Forms
|
||||
|
||||
44
app/controllers/auth/MFA.controller.js
Normal file
44
app/controllers/auth/MFA.controller.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { Controller } = require('libflitter')
|
||||
|
||||
class MFAController extends Controller {
|
||||
static get services() {
|
||||
return [...super.services, 'Vue', 'configs']
|
||||
}
|
||||
|
||||
async setup(req, res, next) {
|
||||
if ( req.user.mfa_enabled ) {
|
||||
// Already set up!
|
||||
return this.Vue.auth_message(res, {
|
||||
message: 'It looks like your account is already set up for multi-factor authentication. Unable to continue with MFA setup.',
|
||||
next_destination: '/', // TODO update this
|
||||
button_text: 'Okay',
|
||||
})
|
||||
}
|
||||
|
||||
// Display the token setup page
|
||||
return res.page('auth:mfa:setup', {
|
||||
...this.Vue.data()
|
||||
})
|
||||
}
|
||||
|
||||
async challenge(req, res, next) {
|
||||
if ( !req.user.mfa_enabled ) {
|
||||
return this.Vue.auth_message(res, {
|
||||
message: 'Your account is not configured to use multi-factor authentication. Would you like to configure it now?',
|
||||
next_destination: '/auth/mfa/setup',
|
||||
button_text: 'Setup MFA',
|
||||
})
|
||||
}
|
||||
|
||||
if ( !req.session.auth.in_dmz ) {
|
||||
return res.redirect(req.session.auth.flow)
|
||||
}
|
||||
|
||||
// Display the MFA challenge page
|
||||
return res.page('auth:mfa:challenge', {
|
||||
...this.Vue.data()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = MFAController
|
||||
Reference in New Issue
Block a user