Add support for session traps; make mfa challenge session trap; remove DMZ middleware
This commit is contained in:
parent
8701df1acc
commit
64356d42d0
@ -2,6 +2,7 @@
|
|||||||
- Forgot password handling
|
- Forgot password handling
|
||||||
- Admin password reset mechanism -> flag users as needing PW resets
|
- Admin password reset mechanism -> flag users as needing PW resets
|
||||||
- OAuth2 -> support refresh tokens
|
- OAuth2 -> support refresh tokens
|
||||||
- Traps -> support session traps; convert MFA challenge to use session trap
|
- Traps
|
||||||
- Allow setting user trap from web UI
|
- Allow setting user trap from web UI
|
||||||
- Don't allow external logins if trap is set
|
- Don't allow external logins if trap is set
|
||||||
|
- Trust token page -> force username of current user
|
||||||
|
@ -481,8 +481,7 @@ class AuthController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( user.mfa_enabled && !req.session.mfa_remember ) {
|
if ( user.mfa_enabled && !req.session.mfa_remember ) {
|
||||||
req.session.auth.in_dmz = true
|
await req.trap.begin('mfa_challenge', { session_only: true })
|
||||||
destination = '/auth/mfa/challenge'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( req.session?.auth?.message )
|
if ( req.session?.auth?.message )
|
||||||
@ -496,7 +495,7 @@ class AuthController extends Controller {
|
|||||||
// Trust re-verification is granted,
|
// Trust re-verification is granted,
|
||||||
// but the user might still need to verify MFA
|
// but the user might still need to verify MFA
|
||||||
const next = req.trust.end()
|
const next = req.trust.end()
|
||||||
if ( req.session.auth.in_dmz ) {
|
if ( req.trap.has_trap('mfa_challenge') ) {
|
||||||
req.session.auth.flow = next
|
req.session.auth.flow = next
|
||||||
} else {
|
} else {
|
||||||
destination = next
|
destination = next
|
||||||
@ -550,7 +549,8 @@ class AuthController extends Controller {
|
|||||||
|
|
||||||
let next_destination = undefined
|
let next_destination = undefined
|
||||||
if ( is_valid ) {
|
if ( is_valid ) {
|
||||||
req.session.auth.in_dmz = false
|
if ( req.trap.has_trap('mfa_challenge') )
|
||||||
|
await req.trap.end()
|
||||||
next_destination = req.session.auth.flow || this.configs.get('auth.default_login_route')
|
next_destination = req.session.auth.flow || this.configs.get('auth.default_login_route')
|
||||||
delete req.session.auth.flow
|
delete req.session.auth.flow
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class MFAController extends Controller {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !req.session.auth.in_dmz ) {
|
if ( !req.trap.has_trap('mfa_challenge') ) {
|
||||||
return res.redirect(req.session.auth.flow)
|
return res.redirect(req.session.auth.flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
const Middleware = [
|
const Middleware = [
|
||||||
"auth:Utility",
|
"auth:Utility",
|
||||||
|
"Traps",
|
||||||
"auth:TrustTokenUtility",
|
"auth:TrustTokenUtility",
|
||||||
"SAMLUtility",
|
"SAMLUtility",
|
||||||
"Traps",
|
|
||||||
|
|
||||||
// 'MiddlewareName',
|
// 'MiddlewareName',
|
||||||
|
|
||||||
|
@ -3,34 +3,49 @@ const { Middleware } = require('libflitter')
|
|||||||
class TrapUtility {
|
class TrapUtility {
|
||||||
constructor(req, res, configs) {
|
constructor(req, res, configs) {
|
||||||
this.request = req
|
this.request = req
|
||||||
|
this.session = req.session
|
||||||
this.response = res
|
this.response = res
|
||||||
this.user = req.user
|
this.user = req.user
|
||||||
this.configs = configs
|
this.configs = configs
|
||||||
}
|
}
|
||||||
|
|
||||||
async begin(trap_name) {
|
async begin(trap_name, { session_only = false }) {
|
||||||
|
if ( session_only || !this.user ) {
|
||||||
|
this.session.trap = trap_name
|
||||||
|
} else {
|
||||||
this.user.trap = trap_name
|
this.user.trap = trap_name
|
||||||
this.request.trust.assume()
|
|
||||||
await this.user.save()
|
await this.user.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( this.config().assume_trust )
|
||||||
|
this.request.trust.assume()
|
||||||
|
}
|
||||||
|
|
||||||
redirect() {
|
redirect() {
|
||||||
|
if ( this.config().assume_trust )
|
||||||
this.request.trust.assume()
|
this.request.trust.assume()
|
||||||
return this.response.redirect(this.config().redirect_to)
|
return this.response.redirect(this.config().redirect_to)
|
||||||
}
|
}
|
||||||
|
|
||||||
async end() {
|
async end() {
|
||||||
this.user.trap = ''
|
if ( this.config().assume_trust )
|
||||||
this.request.trust.unassume()
|
this.request.trust.unassume()
|
||||||
|
if ( this.user ) {
|
||||||
|
this.user.trap = ''
|
||||||
await this.user.save()
|
await this.user.save()
|
||||||
}
|
}
|
||||||
|
this.session.trap = ''
|
||||||
|
}
|
||||||
|
|
||||||
has_trap() {
|
has_trap(name = '') {
|
||||||
return !!this.user.trap
|
if ( name )
|
||||||
|
return (this.user && this.user.trap === name) || this.session.trap === name
|
||||||
|
return (this.user && this.user.trap) || this.session.trap
|
||||||
}
|
}
|
||||||
|
|
||||||
get_trap() {
|
get_trap() {
|
||||||
return this.user.trap
|
if ( this.session.trap ) return this.session.trap
|
||||||
|
else if ( this.user ) return this.user.trap
|
||||||
}
|
}
|
||||||
|
|
||||||
config() {
|
config() {
|
||||||
@ -49,7 +64,6 @@ class TrapsMiddleware extends Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async test(req, res, next, args = {}) {
|
async test(req, res, next, args = {}) {
|
||||||
if ( !req?.user ) return next()
|
|
||||||
req.trap = new TrapUtility(req, res, this.configs.get('traps.types'))
|
req.trap = new TrapUtility(req, res, this.configs.get('traps.types'))
|
||||||
|
|
||||||
if ( !req.trap.has_trap() ) return next()
|
if ( !req.trap.has_trap() ) return next()
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
const Middleware = require('libflitter/middleware/Middleware')
|
|
||||||
class DMZOnly extends Middleware {
|
|
||||||
|
|
||||||
async test(req, res, next, args = {}){
|
|
||||||
|
|
||||||
if ( req.is_auth ) return next()
|
|
||||||
else {
|
|
||||||
// If not signed in, save the target url so we can redirect back here after auth
|
|
||||||
req.session.auth.flow = req.originalUrl
|
|
||||||
return res.redirect('/auth/login')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DMZOnly
|
|
@ -12,11 +12,7 @@ class UserOnly extends Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async test(req, res, next, args = {}){
|
async test(req, res, next, args = {}){
|
||||||
if ( req.is_auth && !req.session.auth.in_dmz ) return next()
|
if ( req.is_auth ) return next()
|
||||||
else if ( req.is_auth ) { // Need an MFA challenge
|
|
||||||
if ( !req.session.auth.flow ) req.session.auth.flow = req.originalUrl
|
|
||||||
return res.redirect('/auth/mfa/challenge')
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
// If not signed in, save the target url so we can redirect back here after auth
|
// If not signed in, save the target url so we can redirect back here after auth
|
||||||
req.session.auth.flow = req.originalUrl
|
req.session.auth.flow = req.originalUrl
|
||||||
|
@ -58,7 +58,7 @@ const auth_routes = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
'/mfa/attempt': [
|
'/mfa/attempt': [
|
||||||
'middleware::auth:DMZOnly',
|
'middleware::auth:UserOnly',
|
||||||
'controller::api:v1:Auth.attempt_mfa'
|
'controller::api:v1:Auth.attempt_mfa'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ const index = {
|
|||||||
|
|
||||||
'/:provider/logout': [
|
'/:provider/logout': [
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:DMZOnly',
|
'middleware::auth:UserOnly',
|
||||||
'controller::auth:Forms.logout_provider_clean_session',
|
'controller::auth:Forms.logout_provider_clean_session',
|
||||||
|
|
||||||
// Note, this separation is between when the auth action has happened properly
|
// Note, this separation is between when the auth action has happened properly
|
||||||
@ -62,7 +62,7 @@ const index = {
|
|||||||
],
|
],
|
||||||
'/logout': [
|
'/logout': [
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:DMZOnly',
|
'middleware::auth:UserOnly',
|
||||||
'controller::auth:Forms.logout_provider_clean_session',
|
'controller::auth:Forms.logout_provider_clean_session',
|
||||||
'controller::auth:Forms.logout_provider_present_success',
|
'controller::auth:Forms.logout_provider_present_success',
|
||||||
],
|
],
|
||||||
@ -100,13 +100,13 @@ const index = {
|
|||||||
],
|
],
|
||||||
'/:provider/logout': [
|
'/:provider/logout': [
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:DMZOnly',
|
'middleware::auth:UserOnly',
|
||||||
'controller::auth:Forms.logout_provider_clean_session',
|
'controller::auth:Forms.logout_provider_clean_session',
|
||||||
'controller::auth:Forms.logout_provider_present_success',
|
'controller::auth:Forms.logout_provider_present_success',
|
||||||
],
|
],
|
||||||
'/logout': [
|
'/logout': [
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:DMZOnly',
|
'middleware::auth:UserOnly',
|
||||||
'controller::auth:Forms.logout_provider_clean_session',
|
'controller::auth:Forms.logout_provider_clean_session',
|
||||||
'controller::auth:Forms.logout_provider_present_success',
|
'controller::auth:Forms.logout_provider_present_success',
|
||||||
],
|
],
|
||||||
|
@ -2,25 +2,21 @@ const mfa_routes = {
|
|||||||
prefix: '/auth/mfa',
|
prefix: '/auth/mfa',
|
||||||
|
|
||||||
middleware: [
|
middleware: [
|
||||||
|
'auth:UserOnly',
|
||||||
],
|
],
|
||||||
|
|
||||||
get: {
|
get: {
|
||||||
'/setup': [
|
'/setup': [
|
||||||
'middleware::auth:UserOnly',
|
|
||||||
['middleware::auth:RequireTrust', { scope: 'mfa.enable' }],
|
['middleware::auth:RequireTrust', { scope: 'mfa.enable' }],
|
||||||
'controller::auth:MFA.setup',
|
'controller::auth:MFA.setup',
|
||||||
],
|
],
|
||||||
'/challenge': [
|
'/challenge': [
|
||||||
'middleware::auth:DMZOnly',
|
|
||||||
'controller::auth:MFA.challenge',
|
'controller::auth:MFA.challenge',
|
||||||
],
|
],
|
||||||
'/disable': [
|
'/disable': [
|
||||||
'middleware::auth:UserOnly',
|
|
||||||
'controller::auth:MFA.get_disable',
|
'controller::auth:MFA.get_disable',
|
||||||
],
|
],
|
||||||
'/disable/process': [
|
'/disable/process': [
|
||||||
'middleware::auth:UserOnly',
|
|
||||||
['middleware::auth:RequireTrust', { scope: 'mfa.disable' }],
|
['middleware::auth:RequireTrust', { scope: 'mfa.disable' }],
|
||||||
'controller::auth:MFA.do_disable',
|
'controller::auth:MFA.do_disable',
|
||||||
],
|
],
|
||||||
|
@ -9,6 +9,14 @@ const traps_config = {
|
|||||||
'/auth/logout',
|
'/auth/logout',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
mfa_challenge: {
|
||||||
|
redirect_to: '/auth/mfa/challenge',
|
||||||
|
allowed_routes: [
|
||||||
|
'/auth/mfa/challenge',
|
||||||
|
'/api/v1/auth/mfa/attempt',
|
||||||
|
'/auth/logout',
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user