SAML; Dashboard

This commit is contained in:
garrettmills
2020-05-03 20:16:54 -05:00
parent e3ecfb0d37
commit c389e151b5
1778 changed files with 148410 additions and 82 deletions

View File

@@ -0,0 +1,28 @@
const { Model } = require('flitter-orm')
const bcrypt = require('bcrypt')
const uuid = require('uuid/v4')
class AppPasswordModel extends Model {
static get schema() {
return {
hash: String,
created: { type: Date, default: () => new Date },
expires: Date,
active: { type: Boolean, default: true },
name: String,
uuid: { type: String, default: uuid },
}
}
async set_hash(password) {
this.hash = await bcrypt.hash(password, 10)
if ( !this.uuid ) this.uuid = uuid()
return this
}
async verify(password) {
return bcrypt.compare(password, this.hash)
}
}
module.exports = exports = AppPasswordModel

View File

@@ -0,0 +1,24 @@
const { Model } = require('flitter-orm')
const bcrypt = require('bcrypt')
class PasswordResetModel extends Model {
static get schema() {
return {
reset_on: { type: Date, default: () => new Date },
old_hash: String,
hash: String,
reason: { type: String, default: 'user' }, // 'user' | 'lockout'
}
}
async set_hash(password) {
this.hash = await bcrypt.hash(password, 10)
return this
}
async check(password) {
return await bcrypt.compare(password, this.hash)
}
}
module.exports = exports = PasswordResetModel

View File

@@ -3,6 +3,9 @@ const LDAP = require('ldapjs')
const ActiveScope = require('../scopes/ActiveScope')
const MFAToken = require('./MFAToken.model')
const PasswordReset = require('./PasswordReset.model')
const AppPassword = require('./AppPassword.model')
const uuid = require('uuid/v4')
/*
* Auth user model. This inherits fields and methods from the default
@@ -11,7 +14,7 @@ const MFAToken = require('./MFAToken.model')
*/
class User extends AuthUser {
static get services() {
return [...super.services, 'auth', 'ldap_server', 'ldap_dn_format']
return [...super.services, 'auth', 'ldap_server', 'configs', 'models', 'app']
}
static get schema() {
@@ -19,11 +22,15 @@ class User extends AuthUser {
// other schema fields here
first_name: String,
last_name: String,
tagline: String,
email: String,
ldap_visible: {type: Boolean, default: true},
active: {type: Boolean, default: true},
mfa_token: MFAToken,
password_resets: [PasswordReset],
app_passwords: [AppPassword],
mfa_enabled: {type: Boolean, default: false},
mfa_enable_date: Date,
}}
}
@@ -40,6 +47,22 @@ class User extends AuthUser {
return true
}
async sessions() {
const Session = require('../Session')
this.app.di().inject(Session)
return Session.find({ 'session.auth.user_id': this.id })
}
async kickout() {
// TODO handle SAML session participants
const sessions = await this.sessions()
for ( const session of sessions ) {
delete session.session.auth
delete session.session.mfa_remember
await session.save()
}
}
// Prefer soft delete because of the active scope
async delete() {
this.active = false
@@ -50,7 +73,45 @@ class User extends AuthUser {
return this.get_provider().check_user_auth(this, password)
}
to_ldap() {
async check_app_password(password) {
for ( const pw of this.app_passwords ) {
if ( await pw.verify(password) ) return true
}
return false
}
async reset_password(new_password, reason = 'user') {
const reset = new PasswordReset({
reason,
old_hash: this.password,
}, this)
await reset.set_hash(new_password)
this.password = reset.hash
this.password_resets.push(reset)
return reset
}
async app_password(name) {
const gen = uuid().replace(/-/g, '')
const pw = new AppPassword({ name }, this)
await pw.set_hash(gen)
this.app_passwords.push(pw)
return { password: gen, record: pw }
}
async ldap_groups() {
const Group = this.models.get('ldap:Group')
return await Group.find({
$or: [
{ user_ids: this.id },
{ role: { $in: this.roles } },
],
})
}
async to_ldap() {
const ldap_data = {
uid: this.uid,
uuid: this.uuid,
@@ -59,20 +120,27 @@ class User extends AuthUser {
gecos: `${this.first_name} ${this.last_name}`,
mail: this.email,
objectClass: 'inetOrgPerson',
dn: this.dn.format(this.ldap_dn_format),
}
if ( this.tagline ) ldap_data.extras_tagline = this.tagline
const addl_data = JSON.parse(this.data)
for ( const key in addl_data ) {
if ( !addl_data.hasOwnProperty(key) || !key.startsWith('ldap_') ) continue
ldap_data[`data${key.substr(4)}`] = `${addl_data[key]}`
}
const ldap_groups = await this.ldap_groups()
if ( ldap_groups.length > 0 ) {
ldap_data.memberOf = ldap_groups.map(x => x.dn.format(this.configs.get('ldap:server.format')))
ldap_data.memberof = ldap_groups.map(x => x.dn.format(this.configs.get('ldap:server.format')))
}
return ldap_data
}
get dn() {
return LDAP.parseDN(`uid=${this.uid},${this.ldap_server.auth_dn().format(this.ldap_dn_format)}`)
return LDAP.parseDN(`uid=${this.uid},${this.ldap_server.auth_dn().format(this.configs.get('ldap:server.format'))}`)
}
}