seneca-user
Version:
User management plugin for Seneca
1,310 lines (1,116 loc) • 34 kB
JavaScript
/* Copyright (c) 2012-2018 Richard Rodger, MIT License */
'use strict'
var Crypto = require('crypto')
var _ = require('lodash')
var Uuid = require('uuid')
var Eraro = require('eraro')({
package: 'seneca-user'
})
// WARNING: this plugin is for *internal* use, DO NOT expose via an API.
// See the seneca-auth plugin for an example of an API that uses this plugin.
module.exports = function user(options) {
var seneca = this
var user_canon = 'sys/user'
var login_canon = 'sys/login'
var reset_canon = 'sys/reset'
// # Plugin options.
// These are the defaults. You can override using the _options_ argument.
// Example: `seneca.use("user",{mustrepeat:true})`.
var default_options = require('./default-options.json')
options = seneca.util.deepextend(default_options, options)
// You can change the _role_ value for the plugin patterns.
// Use this when you want to load multiple versions of the plugin
// and expose them via different patterns.
var role = options.role
var pepper = options.pepper
// # Action patterns
// These define the pattern interface for this plugin.
function conditionalExtend(user, args) {
var extra = _.omit(args, options.updateUser.omit)
_.map(extra, function(val, key) {
if (!key.match(/\$/)) {
user[key] = val
}
})
}
// ### Encrypt a plain text password string
// Pattern: _**role**:user, **cmd**:encrypt_password_
seneca.add(
{
role: role,
cmd: 'encrypt_password',
password: { type: 'string$' }, // password plain text string
repeat: { type: 'string$' } // password plain text string, repeated
},
cmd_encrypt_password
)
// ### Verify a password string
// Pattern: _**role**:user, **cmd**:verify_password_
// Has the user entered the correct password?
seneca.add(
{
role: role,
cmd: 'verify_password',
proposed: { required$: true, string$: true },
pass: { required$: true, string$: true },
salt: { required$: true, string$: true }
},
cmd_verify_password
)
// ### Change password
// Pattern: _**role**:user, **cmd**:change_password_
seneca.add(
{
role: role,
cmd: 'change_password',
// identify user, various options
atleastone$: ['nick', 'email', 'user', 'username'],
nick: { string$: true },
email: { string$: true },
username: { string$: true },
user: { type$: ['object', 'string'] },
password: { string$: true, required$: true }, // new password plain text string
repeat: { string$: true } // new password plain text string, repeated
},
resolve_user(cmd_change_password, true)
)
// ### Register a new user
// Pattern: _**role**:user, **cmd**:register_
seneca.add(
{
role: role,
cmd: 'register',
// identify user, various options
atleastone$: ['nick', 'email', 'username'],
nick: { string$: true },
email: { string$: true },
username: { string$: true },
password: { string$: true }, // password plain text string
repeat: { string$: true }, // password plain text string, repeated
name: { string$: true }, // full name, as one string
active: { boolean$: true }, // is user active?
confirm: { boolean$: true } // is user confirmed?
},
cmd_register
)
// ### Login a user
// Pattern: _**role**:user, **cmd**:login_
// Creates an entry in _sys/login_ and generates a login token
seneca.add(
{
role: role,
cmd: 'login',
// identify user, various options
atleastone$: ['nick', 'email', 'user', 'username'],
nick: { string$: true },
email: { string$: true },
username: { string$: true },
user: { object$: true },
password: { string$: true }, // password plain text
auto: { boolean$: true } // login without password
},
resolve_user(cmd_login, false)
)
// ### Confirm user
// Pattern: _**role**:user, **cmd**:confirm_
// Use confirmation code, provided to user by email (say), to confirm registration.
seneca.add(
{
role: role,
cmd: 'confirm',
code: { string$: true, required$: true } // confirmation code
},
cmd_confirm
)
// ### Authorize user
// Pattern: _**role**:user, **cmd**:auth_
// Validates that the token exists and is active
seneca.add(
{
role: role,
cmd: 'auth',
token: { required$: true, string$: true } // login token
},
cmd_auth
)
// ### Logout user
// Pattern: _**role**:user, **cmd**:logout_
// Mark token record in _sys/login_ as inactive
seneca.add(
{
role: role,
cmd: 'logout',
token: { required$: true, string$: true } // login token
},
cmd_logout
)
// ### Clean user data
// Pattern: _**role**:user, **cmd**:clean_
// Remove sensitive data fields such as password hash.
seneca.add(
{
role: role,
cmd: 'clean',
user: { required$: true, entity$: user_canon } // sys/user entity
},
cmd_clean
)
// ### Load user based on a query.
// Required by external seneca plugins such as seneca-auth
// Pattern: _**role**:user, **cmd**:load_user_
// Returns an entity based on a query
seneca.add(
{
role: role,
get: 'user'
},
cmd_load_user
)
// ### Create a password reset entry
// Pattern: _**role**:user, **cmd**:create_reset_
// Create an entry in _sys/reset_ that provides a reset token
seneca.add(
{ role: role, cmd: 'create_reset' },
resolve_user(cmd_create_reset, false)
)
// ### Load a password reset entry
// Pattern: _**role**:user, **cmd**:load_reset_
// Load a _sys/reset_ entry by reset token
seneca.add({ role: role, cmd: 'load_reset' }, cmd_load_reset)
// ### Execute a password reset
// Pattern: _**role**:user, **cmd**:execute_reset_
// Execute a _sys/reset_ entry by reset token, providing the new password.
seneca.add({ role: role, cmd: 'execute_reset' }, cmd_execute_reset)
// ### Update user details
// Pattern: _**role**:user, **cmd**:update_
seneca.add({ role: role, cmd: 'update' }, cmd_update)
// ### Unlock user
// Pattern: _**role**:user, **cmd**:unlock_
seneca.add({ role: role, cmd: 'unlock' }, cmd_unlock)
// ### Enable User - DEPRECATED
// Replaced with **activate** command
seneca.add(
{ role: role, cmd: 'enable' }, // keep this for backward compatibility
cmd_enable
)
// ### Activate user
// Pattern: _**role**:user, **cmd**:activate_
seneca.add({ role: role, cmd: 'activate' }, cmd_enable)
// ### Disable User - DEPRECATED
// Replaced with **deactivate** command
seneca.add(
{ role: role, cmd: 'disable' }, // keep this for backward compatibility
cmd_disable
)
// ### Deactivate user
// Pattern: _**role**:user, **cmd**:deactivate_
seneca.add({ role: role, cmd: 'deactivate' }, cmd_disable)
// ### Delete User - DEPRECATED
// Replaced with **remove** command
seneca.add(
{ role: role, cmd: 'delete' }, // keep this for backward compatibility
cmd_delete
)
// ### Remove user
// Pattern: _**role**:user, **cmd**:remove_
seneca.add({ role: role, cmd: 'remove' }, cmd_delete)
// ### DEPRECATED
seneca.add({ role: role, cmd: 'entity' }, function(args, done) {
var seneca = this
var loginent = seneca.make(login_canon)
var kind = args.kind || 'user'
var ent = ('user' === kind ? seneca.make(user_canon) : loginent).make$()
done(null, ent)
})
// Action Implementations
function cmd_load_user(args, done) {
var q = {}
if (args.id) q.id = args.id
if (args.email) q.email = args.email
if (args.nick) q.nick = args.nick
seneca.make(user_canon).load$(q, function(err, user) {
if (err) return done(err)
return done(null, { ok: true, user: user })
})
}
function resolve_user(cmd, fail) {
return function(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
if (args.user && args.user.entity$) {
return cmd.call(seneca, args, done)
} else {
var q = {}
var valid = false
if (args.email && args.nick) {
// email wins
if (args.email !== args.nick) {
q.email = args.email
valid = true
} else if (~args.email.indexOf('@')) {
q.email = args.email
valid = true
} else {
q.nick = args.nick
valid = true
}
} else if (args.email) {
q.email = args.email
valid = true
} else if (args.nick) {
q.nick = args.nick
valid = true
} else if (args.username) {
q.nick = args.username
valid = true
} else if (args.user && !args.user.entity$) {
q.id = args.user
valid = true
}
if (!valid) {
return done(null, { ok: false, why: 'nick_or_email_missing' })
}
userent.load$(q, function(err, user) {
if (err) return done(err)
if (!user) {
if (fail) {
return done(Eraro(seneca.fail('user/not-found', q)))
} else
return done(null, {
ok: false,
why: 'user-not-found',
nick: q.nick,
email: q.email
})
}
if (_.isBuffer(user.pass)) {
user.pass = user.pass.toString('utf8')
}
args.user = user
return cmd.call(seneca, args, done)
})
}
}
}
function hide(args, propnames) {
var outargs = _.extend({}, args)
for (var pn in propnames) {
outargs[pn] = '[HIDDEN]'
}
return outargs
}
function hasher(src, rounds, done) {
var out = src
var i = 0
// don't chew up the CPU
function round() {
i++
var shasum = Crypto.createHash('sha512')
shasum.update(out, 'utf8')
out = shasum.digest('hex')
if (rounds <= i) {
return done(out)
}
if (0 === i % 88) {
return process.nextTick(round)
}
round()
}
round()
}
// Encrypt password using a salt and multiple SHA512 rounds
// Override for password strength checking
// - password: password string
// - repeat: password repeat, optional
// Provides: {pass:,salt:,ok:,why:}
// use why if password too weak
function cmd_encrypt_password(args, done) {
// 128 bits of salt
var salt = args.salt || create_salt()
var password = args.password
hasher(pepper + password + salt, options.rounds, function(pass) {
done(null, { ok: true, pass: pass, salt: salt })
})
}
function prepare_password_data(args, done) {
var password = void 0 === args.password ? args.pass : args.password
var repeat = args.repeat
if (_.isUndefined(password)) {
if (options.autopass) {
password = Uuid()
} else
return done(null, {
ok: false,
code: 'user/no-password',
whence: args.whence
})
}
if (_.isUndefined(repeat)) {
if (options.mustrepeat) {
return done(null, {
ok: false,
code: 'user/no-password-repeat',
whence: args.whence
})
} else repeat = password
}
if (password !== repeat) {
return done(null, {
ok: false,
why: 'password_mismatch',
whence: args.whence
})
}
seneca.act(
'role: ' + role + ', cmd: encrypt_password',
{ password: password, salt: args.salt },
done
)
}
function create_salt() {
return Crypto.randomBytes(16).toString('ascii')
}
cmd_encrypt_password.descdata = function(args) {
return hide(args, { password: 1, repeat: 1 })
}
// Verify proposed password is correct, redoing SHA512 rounds
// - proposed: trial password string
// - pass: password hash
// - salt: password salt
// Provides: {ok:}
function cmd_verify_password(args, done) {
seneca.act(
{
role: role,
cmd: 'encrypt_password',
whence: 'verify_password/user=' + user.id + ',' + user.nick,
password: args.proposed,
salt: args.salt
},
function(err, out) {
if (err) {
return done(err)
}
if (!out.ok) {
return done(err, out)
}
var pass = out.pass
var ok = pass === args.pass
// for backwards compatibility with <= 0.2.3
if (!ok && options.oldsha) {
var shasum = Crypto.createHash('sha1')
shasum.update(args.proposed + args.salt)
pass = shasum.digest('hex')
ok = pass === args.pass
return done(null, { ok: ok })
} else return done(null, { ok: ok })
}
)
}
cmd_verify_password.descdata = function(args) {
return hide(args, { proposed: 1 })
}
// Change password using user's nick or email
// - nick, email: to resolve user
// - user: user entity
// - password: new password
// - repeat: password repeat, optional
// Provides: {ok:,user:}
function cmd_change_password(args, done) {
var seneca = this
var user = args.user
seneca.act(
{
role: role,
cmd: 'encrypt_password',
whence: 'change/user=' + user.id + ',' + user.nick,
password: args.password,
repeat: args.repeat,
salt: args.salt
},
function(err, out) {
if (err) {
return done(err)
}
if (!out.ok) {
return done(null, out)
}
user.failedLoginCount = 0
user.salt = out.salt
user.pass = out.pass
user.save$(function(err, user) {
if (err) {
return done(err)
}
user = user.data$(false)
done(null, { ok: true, user: user })
})
}
)
}
cmd_change_password.descdata = function(args) {
return hide(args, { password: 1, repeat: 1 })
}
// Register a new user
// - nick: username, data store should ensure unique, alias: username, email used if not present
// - email: primary email address, data store should ensure unique
// - name: full name of user
// - active: status of user, active==true means login succeeds
// - password: password text, alias: pass
// - confirmed: user already confirmed, default: false
// Generated fields:
// - when: date and time of registration
// - confirmcode: used for confirmation
// Provides:
// - success: {ok:true,user:}
// - failure: {ok:false,why:,nick:}
function cmd_register(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var user = userent.make$()
user.nick = args.nick || args.username || args.email
user.email = args.email
user.name = args.name || ''
user.active = void 0 === args.active ? true : args.active
user.when = new Date().toISOString()
user.failedLoginCount = 0
if (options.confirm) {
user.confirmed = args.confirmed || false
user.confirmcode = Uuid()
}
conditionalExtend(user, args)
return checknick(function() {
checkemail(function() {
saveuser()
})
})
// unsafe nick unique check, data store should also enforce !!
function checknick(next) {
if (user.nick) {
userent.load$({ nick: user.nick }, function(err, userfound) {
if (err) return done(err, { ok: false, user: user })
if (userfound)
return done(null, {
ok: false,
why: 'nick-exists',
nick: args.nick
})
next()
})
return
}
next()
}
// unsafe email unique check, data store should also enforce !!
function checkemail(next) {
if (user.email) {
userent.load$({ email: user.email }, function(err, userfound) {
if (err) return done(err, { ok: false, user: user })
if (userfound)
return done(null, {
ok: false,
why: 'email-exists',
nick: args.nick
})
next()
})
return
}
next()
}
function saveuser() {
prepare_password_data(args, function(err, data) {
if (err) {
return done(err)
}
if (!data.ok) {
return done(null, data)
}
user.salt = data.salt
user.pass = data.pass
// before saving user some cleanup should be done
cleanUser(user, function(err, user) {
if (err) {
return done(err)
}
user.save$(function(err, user) {
if (err) {
return done(err)
}
seneca.log.debug('register', user.nick, user.email, user)
done(null, { ok: true, user: user })
})
})
})
}
}
function cleanUser(user, done) {
delete user.repeat
done(null, user)
}
// Login an existing user - generates a login token. User must be active
// - nick, email: to resolve user
// - user: user entity
// - password: password text, alias: pass
// Provides:
// - success: {ok:true,user:,login:}
// - failure: {ok:false,why:,user:}
function cmd_login(args, done) {
var seneca = this
var user = args.user
var why
var loginent = seneca.make(login_canon)
if (!user.active) {
seneca.log.debug('login/fail', (why = 'not-active'), user)
return done(null, { ok: false, why: why, user: user })
}
if (
options.failedLoginCount &&
user.failedLoginCount >= options.failedLoginCount
) {
seneca.log.debug('login/fail', (why = 'locked-out'), user)
return done(null, { ok: false, why: why })
}
if (args.auto) {
return make_login(user, 'auto')
} else {
seneca.act(
{
role: role,
cmd: 'verify_password',
proposed: args.password,
pass: user.pass,
salt: user.salt
},
function(err, out) {
if (err) return done(err)
if (!out.ok) {
seneca.log.debug('login/fail', (why = 'invalid-password'), user)
cmd_increment_lock(seneca, user.id, false, function(err) {
if (err) return done(err)
done(null, { ok: false, why: why })
})
} else return make_login(user, 'password')
}
)
}
function make_login(user, why) {
var cleanargs = seneca.util.clean(_.clone(args))
var login = loginent.make$(
seneca.util.argprops(
{},
cleanargs,
{
id$: Uuid(),
nick: user.nick,
user: user.id,
when: new Date().toISOString(),
active: true,
why: why
},
'role,cmd,password'
)
)
login.token = login.id$ // DEPRECATED
login.save$(function(err, login) {
if (err) return done(err)
cmd_increment_lock(seneca, user.id, true, function(err) {
if (err) return done(err)
seneca.log.debug('login/ok', why, user, login)
done(null, { ok: true, user: user, login: login, why: why })
})
})
}
}
// Increments the number of failed login attempts
// - id, user id to which to increment the failed attempts
// - reset - boolean that says if the counter is reset to 0 or not
// Provides:
// - success: {ok:true}
// - failure: {ok:false}
function cmd_increment_lock(seneca, id, reset, done) {
var userent = seneca.make(user_canon)
userent.load$({ id: id }, function(err, user) {
if (err) return done(err, { ok: false, why: err })
if (reset) {
user.failedLoginCount = 0
} else {
user.failedLoginCount++
}
user.save$(function(err, user) {
if (err) return done(err, { ok: false, why: err })
seneca.log.debug(
'increment lock ',
user,
'failedLoginCount ',
user.failedLoginCount
)
done(null, { ok: true })
})
})
}
// Unlocks accounts that a user has been locked out of due to failed password attempts
// - id: user id to unlock
// Provides:
// - success: {ok:true}
// - failure: {ok:false}
function cmd_unlock(args, done) {
var seneca = this
cmd_increment_lock(seneca, args.id, true, function(err) {
if (err) return done(err, { ok: false, why: err })
done(null, { ok: true })
})
}
// Confirm an existing user - using confirm code sent to user
// - nick, email: to resolve user
// - code: confirmcode
// Provides:
// - success: {ok:true,user:}
// - failure: {ok:false,why:,nick:}
function cmd_confirm(args, done) {
var seneca = this
var why
var userent = seneca.make(user_canon)
userent.load$({ confirmcode: args.code }, function(err, user) {
if (err) return done(err)
if (user) {
if (user.active) {
user.confirmed = true
user.save$(function(err, user) {
if (err) return done(err)
done(null, { ok: true, user: user })
})
} else {
seneca.log.debug(
'confirm/fail',
(why = 'not-active'),
args.code,
user
)
return done(null, { ok: false, why: why, user: user })
}
} else {
seneca.log.debug(
'confirm/fail',
(why = 'invalid-code'),
args.code,
user
)
return done(null, { ok: false, why: why })
}
})
}
// Authorize an existing user - resolve using token
// - token: login token
// Provides:
// - success: {ok:true,user:,login:}
// - failure: {ok:false,why:,token:}
function cmd_auth(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var loginent = seneca.make(login_canon)
var q = { id: args.token }
loginent.load$(q, function(err, login) {
if (err) return done(err)
if (!login) {
return done(null, {
ok: false,
token: args.token,
why: 'login-not-found'
})
}
if (!login.active) {
return done(null, {
ok: false,
token: args.token,
why: 'login-inactive'
})
}
userent.load$({ id: login.user }, function(err, user) {
if (err) return done(err)
if (!user) {
return done(null, {
ok: false,
token: args.token,
user: login.user,
why: 'user-not-found'
})
}
done(null, { user: user, login: login, ok: true })
})
})
}
// Logout an existing user - resolve using token, idempotent
// - token: login token
// Provides:
// - success: {ok:true,user:,login:}
function cmd_logout(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var loginent = seneca.make(login_canon)
var q = { id: args.token }
loginent.load$(q, function(err, login) {
if (err) return done(err)
if (!login) {
return done(null, { ok: true })
}
login.active = false
login.ended = new Date().getTime()
login.save$(function(err, login) {
if (err) {
return done(err)
}
userent.load$({ id: login.user }, function(err, user) {
if (err) {
return done(err)
}
seneca.log.debug('logout', user, login)
done(null, { user: user, login: login, ok: true })
})
})
})
}
// Create a password reset token
// - nick, email: to resolve user
// - user: user entity
// Provides: {ok:,reset:,user:}
function cmd_create_reset(args, done) {
var seneca = this
var user = args.user
var why
if (!user.active) {
seneca.log.debug('create-reset/fail', (why = 'not-active'), user)
return done(null, { ok: false, why: why, user: user })
}
var resetent = seneca.make(reset_canon)
var token = Uuid()
resetent
.make$({
id$: token,
token: token,
nick: user.nick,
user: user.id,
when: new Date().toISOString(),
active: true
})
.save$(function(err, reset) {
if (err) {
return done(null, { ok: false, why: err })
}
done(null, {
reset: reset.data$(false),
user: user.data$(false),
ok: true
})
})
}
// Load a password reset token
// - token: reset token string
// Provides: {ok:,reset:,user:}
function cmd_load_reset(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var resetent = seneca.make(reset_canon)
var q = { token: args.token }
resetent.load$(q, function(err, reset) {
if (err) {
return done(null, { ok: false, why: err })
}
if (!reset) {
return done(null, {
ok: false,
token: args.token,
why: 'reset-not-found'
})
}
reset = reset.data$(false)
userent.load$({ id: reset.user }, function(err, user) {
if (err) {
return done(null, { ok: false, why: err })
}
if (!user) {
return done(null, {
ok: false,
token: args.token,
user: reset.user,
why: 'user-not-found'
})
}
user = user.data$(false)
done(null, { user: user, reset: reset, ok: true })
})
})
}
// Execute a password change using a reset token
// - token: reset token string
// - password: new password
// - repeat: password repeat, optional
// Provides: {ok:,reset:,user:}
function cmd_execute_reset(args, done) {
var seneca = this
var resetent = seneca.make(reset_canon)
var userent = seneca.make(user_canon)
var q = { id: args.token }
resetent.load$(q, function(err, reset) {
if (err) {
return done(err)
}
if (!reset) {
return done(null, {
ok: false,
token: args.token,
why: 'reset-not-found'
})
}
if (!reset.active) {
return done(null, {
ok: false,
token: args.token,
why: 'reset-not-active'
})
}
if (new Date() < new Date(reset.when) + options.resetperiod) {
return done(null, { ok: false, token: args.token, why: 'reset-stale' })
}
userent.load$({ id: reset.user }, function(err, user) {
if (err) {
return done(null, { ok: false, token: args.token, why: err })
}
if (!user) {
return done(null, { ok: false, why: 'user-not-found' })
}
seneca.act(
{
role: role,
cmd: 'change_password',
user: user,
password: args.password,
repeat: args.repeat,
salt: args.salt
},
function(err, out) {
if (err) {
return done(err)
}
if (!out.ok) {
return done(null, out)
}
reset.active = false
reset.save$(function(err, reset) {
if (err) {
return done(null, { ok: false, why: err })
}
reset = reset.data$(false)
user = user.data$(false)
seneca.log.debug(
'reset',
reset.id,
user.id,
user.nick,
user,
reset
)
done(null, { user: user, reset: reset, ok: true })
})
}
)
})
})
}
function cmd_delete(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var loginent = seneca.make(login_canon)
var resetent = seneca.make(reset_canon)
var user = userent.make$()
var q = {}
if (args.nick) {
q.nick = args.nick
} else if (args.email) {
q.email = args.email
} else {
return done({ err: 'cannot identify user to delete' })
}
user.load$(q, function(err, user) {
if (err) return done(err, { ok: false })
if (!user) return done(null, { ok: false, exists: false })
var nick = user.nick
q = { nick: nick }
// delete from login
var login = loginent.make$()
login.remove$(q, function(err) {
if (err) return seneca.log.warn(err)
// delete now from resetent
var reset = resetent.make$()
reset.remove$(q, function(err) {
if (err) return seneca.log.warn(err)
user.remove$(q, function(err) {
if (err) return done(err, { ok: false })
done(null, { ok: true })
})
})
})
})
}
function cmd_update(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var user = userent.make$()
var q = {}
args.orig_nick = args.orig_nick || args.nick
if (args.orig_nick) {
q.nick = args.orig_nick
} else {
q.email = args.orig_email || args.email
}
user.load$(q, function(err, user) {
if (err) return done(err, { ok: false, why: err })
if (!user) return done(null, { ok: false, exists: false })
var pwd = args.password || ''
var pwd2 = args.repeat || ''
if (pwd) {
if (pwd === pwd2 && 1 < pwd.length) {
seneca.act(
'role: ' + role + ', cmd: change_password',
_.extend({}, q, { password: pwd }),
function(err, userpwd) {
if (err) return done(err, { ok: false, why: err })
user = userpwd
}
)
} else {
return done(null, { ok: false, why: 'user/password_mismatch' })
}
}
delete args.orig_nick
delete args.orig_email
return checknick(function() {
checkemail(function() {
updateuser(user)
})
})
// unsafe nick unique check, data store should also enforce !!
function checknick(next) {
if (args.nick) {
userent.list$({ nick: args.nick }, function(err, users) {
if (err) return done(err, { ok: false, user: user })
for (var i = 0; i < users.length; i++) {
var each = users[i]
if (each.id !== user.id) {
return done(null, {
ok: false,
why: 'nick-exists',
nick: args.nick
})
}
}
next()
})
return
}
next()
}
// unsafe email unique check, data store should also enforce !!
function checkemail(next) {
if (args.email) {
userent.list$({ email: args.email }, function(err, users) {
if (err) return done(err, { ok: false, user: user })
for (var i = 0; i < users.length; i++) {
var each = users[i]
if (each.id !== user.id) {
return done(null, {
ok: false,
why: 'email-exists',
nick: args.nick
})
}
}
next()
})
return
}
next()
}
function updateuser(pwdupdate) {
user.nick = args.nick || pwdupdate.nick
user.name = args.name || pwdupdate.name
user.email = args.email || pwdupdate.email
user.salt = pwdupdate.salt
user.pass = pwdupdate.pass
if ('' === user.nick) {
done(null, { ok: false, why: 'empty_nick' })
} else if ('' === user.email) {
done(null, { ok: false, why: 'empty_email' })
} else {
conditionalExtend(user, args)
// before saving user some cleanup should be done
cleanUser(user, function(err, user) {
if (err) {
return done(null, { ok: false, why: err })
}
user.save$(function(err, user) {
done(err, { ok: !err, user: user.data$(false) })
})
})
}
}
})
}
function cmd_disable(args, done) {
cmd_change_active.call(this, args, false, done)
}
function cmd_enable(args, done) {
cmd_change_active.call(this, args, true, done)
}
function cmd_change_active(args, active, done) {
var seneca = this
var userent = seneca.make(user_canon)
var user = userent.make$()
var q = {}
if (args.nick) {
q.nick = args.nick
} else if (args.email) {
q.email = args.email
}
if (!q.nick && !q.email) {
return done(null, { ok: false, why: 'cannot-identify-user' })
}
user.load$(q, function(err, user) {
if (err) return done(err, { ok: false })
if (!user) return done(null, { ok: false, exists: false })
user.active = active
user.save$(function(err, user) {
done(err, { ok: !err, user: user })
})
})
}
// DEPRECATED - do this in seneca-auth
function cmd_clean(args, done) {
var user = args.user.data$()
delete user.pass
delete user.salt
delete user.active
delete user.$
done(null, user)
}
seneca.add({ init: role }, function(args, done) {
var seneca = this
var userent = seneca.make(user_canon)
var loginent = seneca.make(login_canon)
var resetent = seneca.make(reset_canon)
this.act(
'role:util, cmd:define_sys_entity',
{
list: [
{ entity: userent.entity$, fields: options.user.fields },
{ entity: loginent.entity$, fields: options.login.fields },
{ entity: resetent.entity$, fields: options.reset.fields }
]
},
done
)
})
return {
name: role
}
}