Add traps; user registration
This commit is contained in:
parent
7663cea2ea
commit
ea77402750
@ -1,7 +1,6 @@
|
|||||||
- App setup wizard
|
- App setup wizard
|
||||||
- SAML IAM handling
|
- SAML IAM handling
|
||||||
- LDAP IAM handling
|
- LDAP IAM handling
|
||||||
- User registration
|
|
||||||
- Cobalt form JSON field type - Setting resource
|
- Cobalt form JSON field type - Setting resource
|
||||||
- MFA recovery codes handling
|
- MFA recovery codes handling
|
||||||
- Forgot password handling
|
- Forgot password handling
|
||||||
@ -14,3 +13,6 @@
|
|||||||
- IAM manage user API scopes
|
- IAM manage user API scopes
|
||||||
- Eliminate LDAP group model, make LDAP server use standard auth group
|
- Eliminate LDAP group model, make LDAP server use standard auth group
|
||||||
- OAuth2 -> support refresh tokens
|
- OAuth2 -> support refresh tokens
|
||||||
|
- Traps -> support session traps; convert MFA challenge to use session trap
|
||||||
|
- Allow setting user trap from web UI
|
||||||
|
- Don't allow external logins if trap is set
|
||||||
|
@ -37,6 +37,10 @@ const template = `
|
|||||||
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
||||||
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
||||||
<div class="buttons text-right">
|
<div class="buttons text-right">
|
||||||
|
<small
|
||||||
|
class="mr-3"
|
||||||
|
v-if="!step_two && !loading && registration_enabled"
|
||||||
|
><a href="#" class="text-secondary" @click="on_register_click">Need an account?</a></small>
|
||||||
<button type="button" class="btn btn-primary" :disabled="loading" v-if="step_two" v-on:click="back_click">Back</button>
|
<button type="button" class="btn btn-primary" :disabled="loading" v-if="step_two" v-on:click="back_click">Back</button>
|
||||||
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
|
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -49,7 +53,7 @@ const template = `
|
|||||||
export default class AuthLoginForm extends Component {
|
export default class AuthLoginForm extends Component {
|
||||||
static get selector() { return 'coreid-login-form' }
|
static get selector() { return 'coreid-login-form' }
|
||||||
static get props() { return [
|
static get props() { return [
|
||||||
'app_name', 'login_message', 'additional_params', 'no_session',
|
'app_name', 'login_message', 'additional_params', 'no_session', 'registration_enabled',
|
||||||
] }
|
] }
|
||||||
static get template() { return template }
|
static get template() { return template }
|
||||||
|
|
||||||
@ -126,5 +130,11 @@ export default class AuthLoginForm extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on_register_click() {
|
||||||
|
this.loading = true
|
||||||
|
this.other_message = 'Okay! Let\'s get started...'
|
||||||
|
location_service.redirect('/auth/register', 1500) // TODO get this dynamically
|
||||||
|
}
|
||||||
|
|
||||||
do_nothing() {}
|
do_nothing() {}
|
||||||
}
|
}
|
||||||
|
217
app/assets/app/auth/register/Form.component.js
Normal file
217
app/assets/app/auth/register/Form.component.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
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 = `
|
||||||
|
<div class="coreid-login-form col-lg-6 col-md-8 col-sm-10 col-xs-12 offset-lg-3 offset-md-2 offset-sm-1 offset-xs-0 text-left">
|
||||||
|
<div class="coreid-login-form-inner">
|
||||||
|
<div class="coreid-login-form-header font-weight-light">{{ app_name }}</div>
|
||||||
|
<div class="coreid-login-form-message">{{ message }}</div>
|
||||||
|
<form class="coreid-form" v-on:submit.prevent="do_nothing">
|
||||||
|
<div class="form-group">
|
||||||
|
<input
|
||||||
|
v-if="step === 1"
|
||||||
|
type="text"
|
||||||
|
id="coreid-registration-form-first"
|
||||||
|
name="first_name"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="First Name"
|
||||||
|
v-model="first_name"
|
||||||
|
autofocus
|
||||||
|
@keyup="on_key_up"
|
||||||
|
:disabled="loading || step > 1"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input
|
||||||
|
v-if="step === 1"
|
||||||
|
type="text"
|
||||||
|
id="coreid-registration-form-last"
|
||||||
|
name="last_name"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Last Name"
|
||||||
|
v-model="last_name"
|
||||||
|
autofocus
|
||||||
|
@keyup="on_key_up"
|
||||||
|
:disabled="loading || step > 1"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input
|
||||||
|
v-if="step === 2 || step === 3"
|
||||||
|
type="text"
|
||||||
|
id="coreid-registration-form-username"
|
||||||
|
name="username"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Username"
|
||||||
|
v-model="username"
|
||||||
|
autofocus
|
||||||
|
@keyup="on_key_up"
|
||||||
|
:disabled="loading || step != 2"
|
||||||
|
ref="input_username"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input
|
||||||
|
v-if="step === 3"
|
||||||
|
type="email"
|
||||||
|
id="coreid-registration-form-email"
|
||||||
|
name="email"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="E-Mail"
|
||||||
|
v-model="email"
|
||||||
|
autofocus
|
||||||
|
@keyup="on_key_up"
|
||||||
|
:disabled="loading || step != 3"
|
||||||
|
ref="input_email"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
||||||
|
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
||||||
|
<div class="buttons text-right">
|
||||||
|
<small
|
||||||
|
class="mr-3"
|
||||||
|
v-if="step === 1 && !loading"
|
||||||
|
><a href="#" class="text-secondary" @click="on_login_click">Already have an account?</a></small>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="loading"
|
||||||
|
v-if="step > 1"
|
||||||
|
v-on:click="back_click"
|
||||||
|
>Back</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="loading || btn_disabled"
|
||||||
|
v-on:click="step_click"
|
||||||
|
>{{ button_text }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
// Required: First Name, Last Name, Username, E-Mail, Password (Confirm)
|
||||||
|
export default class RegistrationFormComponent extends Component {
|
||||||
|
static get selector() { return 'coreid-registration-form' }
|
||||||
|
static get template() { return template }
|
||||||
|
static get props() { return ['app_name'] }
|
||||||
|
|
||||||
|
loading = false
|
||||||
|
step = 1
|
||||||
|
other_message = ''
|
||||||
|
error_message = ''
|
||||||
|
message = ''
|
||||||
|
btn_disabled = true
|
||||||
|
button_text = 'Continue'
|
||||||
|
|
||||||
|
first_name = ''
|
||||||
|
last_name = ''
|
||||||
|
username = ''
|
||||||
|
email = ''
|
||||||
|
|
||||||
|
async vue_on_create() {
|
||||||
|
this.message = 'Create an account to continue:'
|
||||||
|
}
|
||||||
|
|
||||||
|
async step_click() {
|
||||||
|
this.loading = true
|
||||||
|
this.error_message = ''
|
||||||
|
this.other_message = ''
|
||||||
|
if ( this.step === 1 ) {
|
||||||
|
if ( !this.first_name.trim() || !this.last_name.trim() ) {
|
||||||
|
this.error_message = 'Please provide your first and last name.'
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.message = `Hi, ${this.first_name.trim()}. Now, you need to choose a username:`
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.input_username.focus()
|
||||||
|
})
|
||||||
|
} else if ( this.step === 2 ) {
|
||||||
|
if ( await auth_api.username_taken(this.username) ) {
|
||||||
|
this.error_message = 'That username is already taken.'
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(function() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.input_email.focus()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if ( this.step === 3 ) {
|
||||||
|
if ( !(await auth_api.validate_email(this.email)) ) {
|
||||||
|
this.error_message = 'Please provide a valid e-mail address.'
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( await auth_api.email_taken(this.email) ) {
|
||||||
|
this.error_message = 'That e-mail address is already taken.'
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await auth_api.register_user({
|
||||||
|
first_name: this.first_name,
|
||||||
|
last_name: this.last_name,
|
||||||
|
uid: this.username,
|
||||||
|
email: this.email,
|
||||||
|
})
|
||||||
|
|
||||||
|
if ( !user ) this.error_message = 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
||||||
|
else this.other_message = 'Welcome! Let\'s get your password set up...'
|
||||||
|
this.btn_disabled = true
|
||||||
|
return location_service.redirect('/dash', 2000)
|
||||||
|
} catch (e) {
|
||||||
|
this.error_message = e.message || 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
||||||
|
this.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.step < 3 ) this.step += 1
|
||||||
|
this.loading = false
|
||||||
|
this.btn_disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async back_click() {
|
||||||
|
this.error_message = ''
|
||||||
|
this.other_message = ''
|
||||||
|
this.step -= 1
|
||||||
|
this.on_key_up()
|
||||||
|
|
||||||
|
if ( this.step === 1 ) {
|
||||||
|
this.message = 'Create an account to continue:'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on_key_up(event) {
|
||||||
|
if ( this.step === 1 ) {
|
||||||
|
this.btn_disabled = !(this.first_name.trim() && this.last_name.trim())
|
||||||
|
} else if ( this.step === 2 ) {
|
||||||
|
this.btn_disabled = !this.username.trim() || !this.username.match(/^([A-Z]|[a-z]|[0-9]|_|-|\.)+$/)
|
||||||
|
} else if ( this.step === 3 ) {
|
||||||
|
this.btn_disabled = !this.email.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( event.keyCode === 13 ) {
|
||||||
|
// Enter was pressed
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if ( !this.btn_disabled ) return this.step_click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on_login_click() {
|
||||||
|
this.loading = true
|
||||||
|
this.other_message = 'Okay! We\'ll have you login instead...'
|
||||||
|
location_service.redirect('/auth/login', 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
do_nothing() {}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import MFAChallengePage from './auth/MFAChallenge.component.js'
|
|||||||
import MFADisableComponent from './auth/MFADisable.component.js'
|
import MFADisableComponent from './auth/MFADisable.component.js'
|
||||||
import PasswordResetComponent from './auth/PasswordReset.component.js'
|
import PasswordResetComponent from './auth/PasswordReset.component.js'
|
||||||
import InvokeActionComponent from './InvokeAction.component.js'
|
import InvokeActionComponent from './InvokeAction.component.js'
|
||||||
|
import RegistrationFormComponent from './auth/register/Form.component.js'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
AuthLoginForm,
|
AuthLoginForm,
|
||||||
@ -14,6 +15,7 @@ const components = {
|
|||||||
MFADisableComponent,
|
MFADisableComponent,
|
||||||
PasswordResetComponent,
|
PasswordResetComponent,
|
||||||
InvokeActionComponent,
|
InvokeActionComponent,
|
||||||
|
RegistrationFormComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
export { components }
|
export { components }
|
||||||
|
@ -4,6 +4,21 @@ class AuthAPI {
|
|||||||
return result && result.data && result.data.data && result.data.data.is_valid
|
return result && result.data && result.data.data && result.data.data.is_valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validate_email(email) {
|
||||||
|
const result = await axios.post('/api/v1/auth/validate/email', { email })
|
||||||
|
return result && result.data && result.data.data && result.data.data.is_valid
|
||||||
|
}
|
||||||
|
|
||||||
|
async username_taken(username) {
|
||||||
|
const result = await axios.post('/api/v1/auth/validate/user_exists', { username })
|
||||||
|
return result && result.data && result.data.data && result.data.data.username_taken
|
||||||
|
}
|
||||||
|
|
||||||
|
async email_taken(email) {
|
||||||
|
const result = await axios.post('/api/v1/auth/validate/user_exists', { email })
|
||||||
|
return result && result.data && result.data.data && result.data.data.email_taken
|
||||||
|
}
|
||||||
|
|
||||||
async attempt({ username, password, create_session, ...others }) {
|
async attempt({ username, password, create_session, ...others }) {
|
||||||
try {
|
try {
|
||||||
const result = await axios.post('/api/v1/auth/attempt', {
|
const result = await axios.post('/api/v1/auth/attempt', {
|
||||||
@ -56,6 +71,11 @@ class AuthAPI {
|
|||||||
async delete_app_password(uuid) {
|
async delete_app_password(uuid) {
|
||||||
await axios.delete(`/api/v1/password/app_passwords/${uuid}`)
|
await axios.delete(`/api/v1/password/app_passwords/${uuid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async register_user({ first_name, last_name, uid, email }) {
|
||||||
|
const result = await axios.post('/api/v1/auth/registration', { first_name, last_name, uid, email })
|
||||||
|
if ( result && result.data && result.data.data ) return result.data.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth_api = new AuthAPI()
|
const auth_api = new AuthAPI()
|
||||||
|
@ -1,11 +1,70 @@
|
|||||||
const { Controller } = require('libflitter')
|
const { Controller } = require('libflitter')
|
||||||
const zxcvbn = require('zxcvbn')
|
const zxcvbn = require('zxcvbn')
|
||||||
|
const email_validator = require('email-validator')
|
||||||
|
|
||||||
class AuthController extends Controller {
|
class AuthController extends Controller {
|
||||||
static get services() {
|
static get services() {
|
||||||
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs', 'utility']
|
return [...super.services, 'models', 'auth', 'MFA', 'output', 'configs', 'utility']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registration(req, res, next) {
|
||||||
|
const User = this.models.get('auth:User')
|
||||||
|
const required_fields = ['first_name', 'last_name', 'uid', 'email']
|
||||||
|
const unique_fields = ['uid', 'email']
|
||||||
|
|
||||||
|
for ( const field of required_fields ) {
|
||||||
|
if ( !req.body[field] )
|
||||||
|
return res.status(400)
|
||||||
|
.message(`Missing required field: ${field}`)
|
||||||
|
.api()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !req.body.uid.match(/^([A-Z]|[a-z]|[0-9]|_|-|\.)+$/) )
|
||||||
|
return res.status(400)
|
||||||
|
.message('Invalid field: uid (should be alphanumeric with "_", "-", and "." allowed)')
|
||||||
|
.api()
|
||||||
|
|
||||||
|
if ( !email_validator.validate(req.body.email) )
|
||||||
|
return res.status(400)
|
||||||
|
.message('Invalid field: email')
|
||||||
|
.api()
|
||||||
|
|
||||||
|
for ( const field of unique_fields ) {
|
||||||
|
const params = {}
|
||||||
|
params[field] = req.body[field]
|
||||||
|
const match_user = await User.findOne(params)
|
||||||
|
if ( match_user )
|
||||||
|
return res.status(400)
|
||||||
|
.message(`A user already exists with that ${field}.`)
|
||||||
|
.api()
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = new User({
|
||||||
|
first_name: req.body.first_name,
|
||||||
|
last_name: req.body.last_name,
|
||||||
|
uid: req.body.uid,
|
||||||
|
email: req.body.email,
|
||||||
|
trap: 'password_reset', // Force user to reset password
|
||||||
|
})
|
||||||
|
|
||||||
|
user.promote('base_user')
|
||||||
|
await user.save()
|
||||||
|
|
||||||
|
// Log in the user automatically
|
||||||
|
await this.auth.get_provider().session(req, user)
|
||||||
|
return res.api(await user.to_api())
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate_email(req, res, next) {
|
||||||
|
let is_valid = !!req.body.email
|
||||||
|
|
||||||
|
if ( is_valid ) {
|
||||||
|
is_valid = email_validator.validate(req.body.email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.api({ is_valid })
|
||||||
|
}
|
||||||
|
|
||||||
async get_users(req, res, next) {
|
async get_users(req, res, next) {
|
||||||
const User = this.models.get('auth:User')
|
const User = this.models.get('auth:User')
|
||||||
const users = await User.find()
|
const users = await User.find()
|
||||||
@ -346,6 +405,34 @@ class AuthController extends Controller {
|
|||||||
return res.api({ is_valid })
|
return res.api({ is_valid })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async user_exists(req, res, next) {
|
||||||
|
const User = this.models.get('auth:User')
|
||||||
|
|
||||||
|
if ( !req.body.username && !req.body.email )
|
||||||
|
return res.status(400)
|
||||||
|
.message('Please provide one of: username, email')
|
||||||
|
.api()
|
||||||
|
|
||||||
|
const data = {}
|
||||||
|
if ( req.body.username ) {
|
||||||
|
const existing_user = await User.findOne({
|
||||||
|
uid: req.body.username,
|
||||||
|
})
|
||||||
|
|
||||||
|
data.username_taken = !!existing_user
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( req.body.email ) {
|
||||||
|
const existing_user = await User.findOne({
|
||||||
|
email: req.body.email,
|
||||||
|
})
|
||||||
|
|
||||||
|
data.email_taken = !!existing_user
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.api(data)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO XSRF Token
|
// TODO XSRF Token
|
||||||
/*
|
/*
|
||||||
* Request Params:
|
* Request Params:
|
||||||
|
@ -86,6 +86,7 @@ class PasswordController extends Controller {
|
|||||||
// Create the password reset
|
// Create the password reset
|
||||||
const reset = await req.user.reset_password(req.body.password)
|
const reset = await req.user.reset_password(req.body.password)
|
||||||
await req.user.save()
|
await req.user.save()
|
||||||
|
if ( req.trap.has_trap() && req.trap.get_trap() === 'password_reset' ) await req.trap.end()
|
||||||
|
|
||||||
// invalidate existing tokens and other logins
|
// invalidate existing tokens and other logins
|
||||||
const flitter = await this.auth.get_provider('flitter')
|
const flitter = await this.auth.get_provider('flitter')
|
||||||
|
@ -7,13 +7,22 @@ const FormController = require('flitter-auth/controllers/Forms')
|
|||||||
*/
|
*/
|
||||||
class Forms extends FormController {
|
class Forms extends FormController {
|
||||||
static get services() {
|
static get services() {
|
||||||
return [...super.services, 'Vue']
|
return [...super.services, 'Vue', 'models']
|
||||||
|
}
|
||||||
|
|
||||||
|
async registration_provider_get(req, res, next) {
|
||||||
|
return res.page('auth:register', {
|
||||||
|
...this.Vue.data({})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async login_provider_get(req, res, next) {
|
async login_provider_get(req, res, next) {
|
||||||
|
const Setting = this.models.get('Setting')
|
||||||
|
|
||||||
return res.page('auth:login', {
|
return res.page('auth:login', {
|
||||||
...this.Vue.data({
|
...this.Vue.data({
|
||||||
login_message: req.session?.auth?.message || 'Please sign-in to continue.'
|
login_message: req.session?.auth?.message || 'Please sign-in to continue.',
|
||||||
|
registration_enabled: await Setting.get('auth.allow_registration')
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class SettingModel extends Model {
|
|||||||
|
|
||||||
static async get(key) {
|
static async get(key) {
|
||||||
const inst = await this.findOne({ key })
|
const inst = await this.findOne({ key })
|
||||||
return inst.get()
|
return inst?.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
static async set(key, value) {
|
static async set(key, value) {
|
||||||
|
@ -35,6 +35,7 @@ class User extends AuthUser {
|
|||||||
mfa_enable_date: Date,
|
mfa_enable_date: Date,
|
||||||
create_date: {type: Date, default: () => new Date},
|
create_date: {type: Date, default: () => new Date},
|
||||||
photo_file_id: String,
|
photo_file_id: String,
|
||||||
|
trap: String,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ const Middleware = [
|
|||||||
"auth:Utility",
|
"auth:Utility",
|
||||||
"auth:TrustTokenUtility",
|
"auth:TrustTokenUtility",
|
||||||
"SAMLUtility",
|
"SAMLUtility",
|
||||||
|
"Traps",
|
||||||
|
|
||||||
// 'MiddlewareName',
|
// 'MiddlewareName',
|
||||||
|
|
||||||
|
61
app/routing/middleware/Traps.middleware.js
Normal file
61
app/routing/middleware/Traps.middleware.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const { Middleware } = require('libflitter')
|
||||||
|
|
||||||
|
class TrapUtility {
|
||||||
|
constructor(req, res, configs) {
|
||||||
|
this.request = req
|
||||||
|
this.response = res
|
||||||
|
this.user = req.user
|
||||||
|
this.configs = configs
|
||||||
|
}
|
||||||
|
|
||||||
|
async begin(trap_name) {
|
||||||
|
this.user.trap = trap_name
|
||||||
|
this.request.trust.assume()
|
||||||
|
await this.user.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect() {
|
||||||
|
this.request.trust.assume()
|
||||||
|
return this.response.redirect(this.config().redirect_to)
|
||||||
|
}
|
||||||
|
|
||||||
|
async end() {
|
||||||
|
this.user.trap = ''
|
||||||
|
this.request.trust.unassume()
|
||||||
|
await this.user.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
has_trap() {
|
||||||
|
return !!this.user.trap
|
||||||
|
}
|
||||||
|
|
||||||
|
get_trap() {
|
||||||
|
return this.user.trap
|
||||||
|
}
|
||||||
|
|
||||||
|
config() {
|
||||||
|
return this.configs[this.get_trap()]
|
||||||
|
}
|
||||||
|
|
||||||
|
allows(route) {
|
||||||
|
const config = this.config()
|
||||||
|
return route.startsWith('/assets') || config.allowed_routes.includes(route.toLowerCase().trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrapsMiddleware extends Middleware {
|
||||||
|
static get services() {
|
||||||
|
return [...super.services, 'models', 'configs']
|
||||||
|
}
|
||||||
|
|
||||||
|
async test(req, res, next, args = {}) {
|
||||||
|
if ( !req?.user ) return next()
|
||||||
|
req.trap = new TrapUtility(req, res, this.configs.get('traps.types'))
|
||||||
|
|
||||||
|
if ( !req.trap.has_trap() ) return next()
|
||||||
|
else if ( req.trap.allows(req.path) ) return next()
|
||||||
|
else return req.trap.redirect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = TrapsMiddleware
|
@ -3,6 +3,8 @@ const moment = require('moment')
|
|||||||
const uuid = require('uuid/v4')
|
const uuid = require('uuid/v4')
|
||||||
|
|
||||||
class TrustManager {
|
class TrustManager {
|
||||||
|
assume_trust = false
|
||||||
|
|
||||||
constructor(request, response) {
|
constructor(request, response) {
|
||||||
this.request = request
|
this.request = request
|
||||||
this.response = response
|
this.response = response
|
||||||
@ -18,6 +20,19 @@ class TrustManager {
|
|||||||
this.request.session.trust_tokens = this.request.session.trust_tokens.filter(x => {
|
this.request.session.trust_tokens = this.request.session.trust_tokens.filter(x => {
|
||||||
return moment(new Date(x.expires)) > now
|
return moment(new Date(x.expires)) > now
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.assume_trust = !!this.request.session.trust_assume_trust
|
||||||
|
}
|
||||||
|
|
||||||
|
assume() {
|
||||||
|
this.request.session.trust_assume_trust = true
|
||||||
|
this.assume_trust = true
|
||||||
|
}
|
||||||
|
|
||||||
|
unassume() {
|
||||||
|
this.request.session.trust_assume_trust = false
|
||||||
|
this.assume_trust = false
|
||||||
|
this.purge()
|
||||||
}
|
}
|
||||||
|
|
||||||
init_flow(scope, next) {
|
init_flow(scope, next) {
|
||||||
@ -66,7 +81,7 @@ class TrustManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
has(scope) {
|
has(scope) {
|
||||||
return this.request.session.trust_tokens.some(x => x.scope === scope)
|
return this.assume_trust || this.request.session.trust_tokens.some(x => x.scope === scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
grant(scope) {
|
grant(scope) {
|
||||||
|
19
app/routing/middleware/util/Setting.middleware.js
Normal file
19
app/routing/middleware/util/Setting.middleware.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const { Middleware, HTTPError } = require('libflitter')
|
||||||
|
|
||||||
|
class SettingMiddleware extends Middleware {
|
||||||
|
static get services() {
|
||||||
|
return [...super.services, 'models']
|
||||||
|
}
|
||||||
|
|
||||||
|
async test(req, res, next, { key, value = true }) {
|
||||||
|
const Setting = this.models.get('Setting')
|
||||||
|
const actual_value = await Setting.get(key)
|
||||||
|
|
||||||
|
if ( actual_value !== value )
|
||||||
|
throw new HTTPError(404)
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = SettingMiddleware
|
@ -40,6 +40,14 @@ const auth_routes = {
|
|||||||
'controller::api:v1:Auth.validate_username'
|
'controller::api:v1:Auth.validate_username'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'/validate/user_exists': [
|
||||||
|
'controller::api:v1:Auth.user_exists',
|
||||||
|
],
|
||||||
|
|
||||||
|
'/validate/email': [
|
||||||
|
'controller::api:v1:Auth.validate_email',
|
||||||
|
],
|
||||||
|
|
||||||
'/attempt': [
|
'/attempt': [
|
||||||
'controller::api:v1:Auth.attempt'
|
'controller::api:v1:Auth.attempt'
|
||||||
],
|
],
|
||||||
@ -77,6 +85,12 @@ const auth_routes = {
|
|||||||
['middleware::api:Permission', { check: 'v1:auth:users:create' }],
|
['middleware::api:Permission', { check: 'v1:auth:users:create' }],
|
||||||
'controller::api:v1:Auth.create_user',
|
'controller::api:v1:Auth.create_user',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'/registration': [
|
||||||
|
['middleware::util:Setting', { key: 'auth.allow_registration' }],
|
||||||
|
'middleware::auth:GuestOnly',
|
||||||
|
'controller::api:v1:Auth.registration',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
patch: {
|
patch: {
|
||||||
|
@ -24,12 +24,14 @@ const index = {
|
|||||||
|
|
||||||
get: {
|
get: {
|
||||||
'/:provider/register': [
|
'/:provider/register': [
|
||||||
|
['middleware::util:Setting', { key: 'auth.allow_registration' }],
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:GuestOnly',
|
'middleware::auth:GuestOnly',
|
||||||
'middleware::auth:ProviderRegistrationEnabled',
|
'middleware::auth:ProviderRegistrationEnabled',
|
||||||
'controller::auth:Forms.registration_provider_get',
|
'controller::auth:Forms.registration_provider_get',
|
||||||
],
|
],
|
||||||
'/register': [
|
'/register': [
|
||||||
|
['middleware::util:Setting', { key: 'auth.allow_registration' }],
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:GuestOnly',
|
'middleware::auth:GuestOnly',
|
||||||
'middleware::auth:ProviderRegistrationEnabled',
|
'middleware::auth:ProviderRegistrationEnabled',
|
||||||
@ -67,7 +69,8 @@ const index = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
post: {
|
post: {
|
||||||
'/:provider/register': [
|
/*'/:provider/register': [
|
||||||
|
['middleware::util:Setting', { key: 'auth.allow_registration' }],
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:GuestOnly',
|
'middleware::auth:GuestOnly',
|
||||||
'middleware::auth:ProviderRegistrationEnabled',
|
'middleware::auth:ProviderRegistrationEnabled',
|
||||||
@ -75,12 +78,13 @@ const index = {
|
|||||||
'controller::auth:Forms.registration_provider_present_user_created',
|
'controller::auth:Forms.registration_provider_present_user_created',
|
||||||
],
|
],
|
||||||
'/register': [
|
'/register': [
|
||||||
|
['middleware::util:Setting', { key: 'auth.allow_registration' }],
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
'middleware::auth:GuestOnly',
|
'middleware::auth:GuestOnly',
|
||||||
'middleware::auth:ProviderRegistrationEnabled',
|
'middleware::auth:ProviderRegistrationEnabled',
|
||||||
'controller::auth:Forms.registration_provider_create_user',
|
'controller::auth:Forms.registration_provider_create_user',
|
||||||
'controller::auth:Forms.registration_provider_present_user_created',
|
'controller::auth:Forms.registration_provider_present_user_created',
|
||||||
],
|
],*/
|
||||||
|
|
||||||
'/:provider/login': [
|
'/:provider/login': [
|
||||||
'middleware::auth:ProviderRoute',
|
'middleware::auth:ProviderRoute',
|
||||||
|
@ -4,4 +4,4 @@ block append style
|
|||||||
link(rel='stylesheet' href='/style-asset/form.css')
|
link(rel='stylesheet' href='/style-asset/form.css')
|
||||||
|
|
||||||
block vue
|
block vue
|
||||||
coreid-login-form(v-bind:app_name="app_name" v-bind:login_message="login_message")
|
coreid-login-form(v-bind:app_name="app_name" v-bind:login_message="login_message" v-bind:registration_enabled="registration_enabled")
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
extends ./form
|
extends ../theme/public/base
|
||||||
|
|
||||||
block form
|
block append style
|
||||||
.form-label-group
|
link(rel='stylesheet' href='/style-asset/form.css')
|
||||||
input#inputUsername.form-control(type='text' name='username' value=(form_data ? form_data.username : '') required placeholder='Username' autofocus)
|
|
||||||
label(for='inputUsername') Username
|
block vue
|
||||||
.form-label-group
|
coreid-registration-form(v-bind:app_name="app_name")
|
||||||
input#inputPassword.form-control(type='password' name='password' required placeholder='Password')
|
|
||||||
label(for='inputPassword') Password
|
|
||||||
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Register
|
|
||||||
.text-center
|
|
||||||
span.small Already registered?
|
|
||||||
a(href='./login') Log-in here.
|
|
||||||
.text-center
|
|
||||||
span.small(style="color: #999999;") Provider: #{provider_name}
|
|
||||||
|
|
||||||
|
15
config/traps.config.js
Normal file
15
config/traps.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const traps_config = {
|
||||||
|
types: {
|
||||||
|
password_reset: {
|
||||||
|
redirect_to: '/password/reset',
|
||||||
|
assume_trust: true,
|
||||||
|
allowed_routes: [
|
||||||
|
'/password/reset',
|
||||||
|
'/api/v1/password/resets',
|
||||||
|
'/auth/logout',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = traps_config
|
Loading…
Reference in New Issue
Block a user