305 lines
9.0 KiB
JavaScript
305 lines
9.0 KiB
JavaScript
const AuthUser = require('flitter-auth/model/User')
|
|
const LDAP = require('ldapjs')
|
|
|
|
const ActiveScope = require('../scopes/ActiveScope')
|
|
const MFAToken = require('./MFAToken.model')
|
|
const PasswordReset = require('./PasswordReset.model')
|
|
const AppAuthorization = require('./AppAuthorization.model')
|
|
const AppPassword = require('./AppPassword.model')
|
|
const uuid = require('uuid').v4
|
|
const NotifyConfig = require('../system/NotifyConfig.model')
|
|
|
|
/*
|
|
* Auth user model. This inherits fields and methods from the default
|
|
* flitter-auth/model/User model, however you can override methods and
|
|
* properties here as you need.
|
|
*/
|
|
class User extends AuthUser {
|
|
static get services() {
|
|
return [...super.services, 'auth', 'ldap_server', 'configs', 'models', 'app']
|
|
}
|
|
|
|
static get schema() {
|
|
return {...super.schema, ...{
|
|
// 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],
|
|
app_authorizations: [AppAuthorization],
|
|
mfa_enabled: {type: Boolean, default: false},
|
|
mfa_enable_date: Date,
|
|
create_date: {type: Date, default: () => new Date},
|
|
photo_file_id: String,
|
|
trap: String,
|
|
notify_config: NotifyConfig,
|
|
uid_number: Number,
|
|
login_shell: String,
|
|
}}
|
|
}
|
|
|
|
async get_uid_number() {
|
|
if ( !this.uid_number ) {
|
|
const Setting = this.models.get('Setting')
|
|
let last_uid = await Setting.get('ldap.last_alloc_uid')
|
|
if ( last_uid < 1 ) {
|
|
last_uid = this.configs.get('ldap:server.schema.start_uid')
|
|
}
|
|
|
|
this.uid_number = last_uid + 1
|
|
await Setting.set('ldap.last_alloc_uid', this.uid_number)
|
|
await this.save()
|
|
}
|
|
|
|
return this.uid_number
|
|
}
|
|
|
|
async photo() {
|
|
const File = this.models.get('upload::File')
|
|
return File.findById(this.photo_file_id)
|
|
}
|
|
|
|
has_authorized(client) {
|
|
return this.app_authorizations.some(x => x.client_id === client.id)
|
|
}
|
|
|
|
get_authorization(client) {
|
|
for ( const auth of this.app_authorizations ) {
|
|
if ( auth.client_id === client.id )
|
|
return auth
|
|
}
|
|
}
|
|
|
|
authorize(client) {
|
|
if ( !this.has_authorized(client) ) {
|
|
const client_rec = new AppAuthorization({
|
|
client_id: client.id,
|
|
api_scopes: client.api_scopes,
|
|
}, this)
|
|
|
|
this.app_authorizations.push(client_rec)
|
|
} else {
|
|
const client_rec = this.get_authorization(client)
|
|
client_rec.api_scopes = client.api_scopes
|
|
}
|
|
}
|
|
|
|
async to_api() {
|
|
return {
|
|
id: this.id,
|
|
uid: this.uid,
|
|
first_name: this.first_name,
|
|
last_name: this.last_name,
|
|
email: this.email,
|
|
tagline: this.tagline,
|
|
trap: this.trap,
|
|
group_ids: (await this.groups()).map(x => x.id),
|
|
}
|
|
}
|
|
|
|
static scopes = [
|
|
new ActiveScope({})
|
|
]
|
|
|
|
static async ldap_directory() {
|
|
return this.find({ldap_visible: true})
|
|
}
|
|
|
|
// TODO just in case we need this later
|
|
get can_login() {
|
|
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
|
|
await this.save()
|
|
}
|
|
|
|
async check_password(password) {
|
|
return this.get_provider().check_user_auth(this, password)
|
|
}
|
|
|
|
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 groups() {
|
|
const Group = this.models.get('auth:Group')
|
|
return Group.find({ active: true, user_ids: this.id })
|
|
}
|
|
|
|
async oidc_sessions() {
|
|
const Session = this.models.get('openid:Session')
|
|
return Session.find({ 'payload.account': this.id })
|
|
}
|
|
|
|
async logout(request) {
|
|
for ( const session of (await this.oidc_sessions()) ) {
|
|
await session.delete()
|
|
}
|
|
|
|
this.get_provider().logout(request)
|
|
}
|
|
|
|
async has_sudo() {
|
|
const groups = await this.groups()
|
|
return groups.some(group => group.grants_sudo)
|
|
}
|
|
|
|
async to_sudo() {
|
|
return {
|
|
objectClass: ['sudoRole'],
|
|
cn: `sudo_${this.uid.toLowerCase()}`,
|
|
sudoUser: this.uid.toLowerCase(),
|
|
sudoHost: 'ALL',
|
|
sudoRunAs: 'ALL',
|
|
sudoCommand: 'ALL',
|
|
}
|
|
}
|
|
|
|
async to_ldap(iam_targets = []) {
|
|
const Policy = this.models.get('iam:Policy')
|
|
|
|
const uid_number = await this.get_uid_number()
|
|
const shell = this.login_shell || this.configs.get('ldap:server.schema.default_shell')
|
|
const domain = this.configs.get('ldap:server.schema.base_dc').split(',').map(x => x.replace('dc=', '')).join('.')
|
|
|
|
const group_ids = []
|
|
for ( const group of await this.groups() ) {
|
|
group_ids.push(await group.get_gid_number())
|
|
}
|
|
|
|
const ldap_data = {
|
|
uid: this.uid.toLowerCase(),
|
|
uuid: this.uuid,
|
|
cn: this.first_name,
|
|
sn: this.last_name,
|
|
gecos: `${this.first_name} ${this.last_name}`,
|
|
mail: this.email,
|
|
objectClass: ['inetOrgPerson', 'person', 'posixaccount'],
|
|
objectclass: ['inetOrgPerson', 'person', 'posixaccount'],
|
|
entryuuid: this.uuid,
|
|
entryUUID: this.uuid,
|
|
objectGuid: this.uuid,
|
|
objectguid: this.uuid,
|
|
uidNumber: uid_number,
|
|
gidNumber: String(await this.get_uid_number()), // group_ids.map(x => String(x)),
|
|
loginShell: shell,
|
|
homeDirectory: `/home/${this.uid}@${domain}`
|
|
}
|
|
|
|
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 groups = await this.groups()
|
|
if ( groups.length > 0 ) {
|
|
const group_data = groups.map(x => x.dn.format(this.configs.get('ldap:server.format')))
|
|
ldap_data.memberOf = group_data
|
|
ldap_data.memberof = group_data
|
|
}
|
|
|
|
const iamtarget = []
|
|
for ( const target_id of iam_targets ) {
|
|
if ( await Policy.check_user_access(this, target_id) ) {
|
|
iamtarget.push(target_id)
|
|
}
|
|
}
|
|
|
|
ldap_data.iamtarget = iamtarget
|
|
|
|
return ldap_data
|
|
}
|
|
|
|
get dn() {
|
|
return LDAP.parseDN(`uid=${this.uid.toLowerCase()},${this.ldap_server.auth_dn().format(this.configs.get('ldap:server.format'))}`)
|
|
}
|
|
|
|
get sudo_dn() {
|
|
return LDAP.parseDN(`cn=sudo_${this.uid.toLowerCase()},${this.ldap_server.sudo_dn().format(this.configs.get('ldap:server.format'))}`)
|
|
}
|
|
|
|
// The following are used by OpenID connect
|
|
|
|
async claims(use, scope) {
|
|
return {
|
|
sub: this.id,
|
|
email: this.email,
|
|
email_verified: true, // TODO
|
|
family_name: this.last_name,
|
|
given_name: this.first_name,
|
|
locale: 'en_US', // TODO
|
|
name: `${this.first_name} ${this.last_name}`,
|
|
preferred_username: this.uid.toLowerCase(),
|
|
username: this.uid.toLowerCase(),
|
|
}
|
|
}
|
|
|
|
static async findByLogin(login) {
|
|
return this.findOne({
|
|
active: true,
|
|
uid: login.toLowerCase(),
|
|
})
|
|
}
|
|
|
|
static async findAccount(ctx, id, token) {
|
|
return this.findById(id)
|
|
}
|
|
|
|
get accountId() {
|
|
return this.id
|
|
}
|
|
}
|
|
|
|
module.exports = exports = User
|