seneca-user
Seneca toolkit
A user management plugin for theThis module is a plugin for the Seneca framework. It provides business logic for user management, such as:
- login
- logout
- registration
- password handling
There are two core concepts: user and login. A user, storing the user account details and encrypted passwords, and a login, representing an instance of a user that has been authenticated. A user can have multiple logins.
This module does not make any assumptions about the context it runs in. Use the seneca-auth plugin to handle web and social media authentication.
For a working example, see the Seneca user accounts example.
Support
If you're using this module, feel free to contact me on Twitter if you have any questions! :) @rjrodger
Current Version: 0.2.2
Tested on: Node 0.10.6, 0.8.7, Seneca 0.5.9
Quick example
var seneca = require('seneca')()
seneca.use('user')
seneca.ready(function(){
var userpin = seneca.pin({role:'user',cmd:'*'})
userpin.register( {name:"Flann O'Brien",email:'nincompoop@deselby.com',password:'blackair'},
function(err,out) {
userpin.login({email:'nincompoop@deselby.com',password:'bicycle'}, function(err,out){
console.log('login success: '+out.ok)
userpin.login({email:'nincompoop@deselby.com',password:'blackair'}, function(err,out){
console.log('login success: '+out.ok)
console.log('login instance: '+out.login)
})
})
})
})
This example, uses a pin for convenience: userpin.register( ... )
is the same as
seneca.act({role:'user',cmd:'register', ... )
.
In the example code, a user is registered, and then two login attempts are made. The first with an incorrect password, the second with the correct password. The successful login provides a login instance. The login.token property can be used to authenticate this login. For example, the seneca-auth plugin uses this token as a HTTP authentication cookie.
To run this example (and the other code in this README), try:
node test/readme.js
Deeper example
Take a look at the user accounts example.
Install
npm install seneca
npm install seneca-user
You'll need the seneca module to use this module - it's just a plugin.
Usage
To load the plugin:
seneca.use('user', { ... options ... })
The user and login data is persisted using seneca entities. These have names sys_user and sys_login by default, but can be changed in the options.
Passwords are not stored in plaintext, but using a salted SHA hash. In the context of password reminder functionality, this means you can generate new passwords, but cannot recover old ones. This is what you want.
There are separate actions to encrypt and verify passwords, so you can do things your own way if you like.
To support different use cases, users can be identified by either a nick or their email address. The nick property is the traditional 'username', but does not need to be used in this fashion (hence the name 'nick').
Options
- user: spec for user entity type, default: {zone:null, base:'sys', name:'user'}.
- login: spec for login entity type, default: {zone:null, base:'sys', name:'login'}.
Entities
User
The user entity has a default type of -/sys/user and standard properties:
- nick: Username, mostly. If not provided, will be set to email value.
- email: Email address. At least one of nick or email is required.
- name: Name of user. Just a text field. Cultural imperialism damages your conversions, ya know...!
- active: if true, user can log in, if false, user can't. Default: true.
- when: creation time, ISO String, like 2013-03-21T17:32:24.039Z
- salt: salt for encrypted password
- pass: encrypted password
You can add your own properties, but be careful not to use the standard property names.
Login
The login entity has a default type of -/sys/login and standard properties:
- token: authentication token
- nick: copied from user
- email: copied from user
- user: user.id string
- when: creation time, ISO String, like 2013-03-21T17:32:24.039Z
- active: if true, login against this token will succeed, otherwise not
You can add your own properties, but be careful not to use the standard property names.
Actions
All actions provide results via the standard callback format: function(error,data){ ... }
.
role:user, cmd:login
Login an existing user. Creates a new sys_login entry.
Arguments:
- nick: required if no email, identifies user
- email: required if no nick, identifies user
- password: password as entered by user
- auto: automatic login without password, default: false. Use this to generate login tokens.
Provides:
Object with properties:
- ok: true if login succeeded, false if not
- login: login entity, includes login.token property
- user: user entity
role:user, cmd:logout
Logout a user. Update sys_login entry to active:false. Adds login.ended field with ISOString date time.
Arguments:
- token: existing login.token, maybe from a cookie
Provides:
Same object format as login command result: {ok:true|false,user:,login:}
role:user, cmd:register
Register a new user. You'll probably call this after a user fills out a regstration form. Any additional action arguments are saved as user properties. The nick and email fields will be checked for uniqueness. The new user is not logged in, use the login action for that.
Arguments:
- nick: Username, mostly. If not provided, will be set to args.username value, if defined, otherwise args.email value.
- email: Email address. At least one of nick or email is required.
- username: a convenience - just an alias for nick.
- password: Plaintext password, supplied by user - will be encrypted.
- repeat: Password, repeated. Optional - if provided, must match password.
- name: Name of user. Just a text field.
- active: if true, user can log in, if false, user can't. Default: true.
Provides:
Object with properties:
- ok: true if registration succeeded, false if not
- user: new user entity
role:user, cmd:auth
Authenticate a login token, returning the associated login and user if the token is valid and active. Use this, for example, when handling HTTP requests with an authentication cookie, to get the user.
Arguments:
- token: existing login.token, maybe from a cookie
Provides:
Same object format as login command result: {ok:true|false,user:,login:}
role:user, cmd:update
Update the user's details, such as nick and/or password.
Arguments:
- orig_nick: original nick, so user can be found, or use orig_email
- orig_email: original email, so user can be found, or use orig_nick
- nick: new nick, optional
- email: new email, optional
- name: new name, optional
- password: new password, optional
- repeat: new password, repeated. Optional - if provided, must match password
Provides:
Object with properties:
- ok: true if update succeeded, false if not
- user: user entity
role:user, cmd:clean
Strips sensitive information from user entity. In particular, the pass, salt, and active properties.
Arguments:
None.
Provides:
User entity.
role:user, cmd:encrypt_password
Encrypts a plaintext password, providing the salt and ciphertext.
Arguments:
- password: plaintext password.
Provides:
Object with properties:
- salt: the salt string
- pass: the ciphertext string
role:user, cmd:verify_password
Verifies that a password matches a given salt and ciphertext.
Arguments:
- salt: the salt string to use, take this from user.salt
- pass: the pass string to use, take this from user.pass
- proposed: the proposed plaintext password to verify
Provides:
Object with properties:
- ok: true if password matches
role:user, cmd:entity
Provide an instance of the user or login entities. Used by other plugins to get the right entity type for additional user operations.
Arguments:
- kind: 'user' or 'login'
Provides:
Seneca entity object for user or login.
Logging
To see what this plugin is doing, try:
node your-app.js --seneca.log=plugin:user
This will print action logs and plugin logs for the user plugin. To skip the action logs, use:
node your-app.js --seneca.log=type:plugin,plugin:user
You can also set up the logging programmatically:
var seneca = require('seneca')({
log:{
map:[
{plugin:'user',handler:'print'}
]
}
})
For more on logging, see the seneca logging example.
Customization
As with all seneca plugins, you can customize behavior simply by overwriting actions.
For example, to add some custom fields when registering a user:
// override by using the same action pattern
seneca.add({role:'user',cmd:'register'},function(args,done){
// assign user to one of 10 random "teams"
args.team = Math.floor( 10 * Math.random() )
// this calls the original action, as provided by the user plugin
this.parent(args,done)
})
userpin.register( {name:"Brian O'Nolan",email:'brian@swim-two-birds.com',password:'na-gCopaleen'},
function(err,out) {
console.log('user has team: '+out.user.team)
})
Test
cd test
mocha user.test.js --seneca.log.print