/**
* This is an authentication and authorization middleware for a REST server with data encryption and signing.
* You can use it with [restify][1] or other server compatible with a generic middleware: `function (req, res, next) {...}`.
* See more details in README file.
*
* @class node_modules.authorify
* @uses node_modules.authorify.authentication
* @uses node_modules.authorify.authorization
*
* @author Marcello Gesmundo
*
* # License
*
* Copyright (c) 2012-2014 Yoovant by Marcello Gesmundo. All rights reserved.
*
* This program is released under a GNU Affero General Public License version 3 or above, which in summary means:
*
* - You __can use__ this program for __no cost__.
* - You __can use__ this program for __both personal and commercial reasons__.
* - You __do not have to share your own program's code__ which uses this program.
* - You __have to share modifications__ (e.g bug-fixes) you've made to this program.
* - For more convoluted language, see the LICENSE file.
*
* [1]: http://mcavage.me/node-restify
*
*/
module.exports = function(app) {
'use strict';
// dependencies
var _ = require('underscore'),
fs = require('fs'),
async = require('async'),
path = require('path'),
forge = require('node-forge'),
math = require('mathjs');
// namespace
var my = {};
my.config = {
/**
* @ignore
* @private
*/
name: 'authorify',
/**
* @cfg {Object} [logger = console] The logger. It MUST have
* log, error, warn, info, debug methods
*/
logger: console, // for best logging please use winston: npm install winston
/**
* @cfg {Boolean} debug=true true to enable verbose log
*/
debug: false,
/**
* @cfg {Boolean} persist=false Persistence of token
* If persist is false, the token (and it's session) will expire after the tokenTtl config.
* If persist is true, the token will never expire and tokenTtl config will be ignored.
*/
persist: false,
/**
* @cfg {Integer} sidTtl=300 The time to live for the handshake and authentication session in seconds
*/
sidTtl: 300, // 5min
/**
* @cfg {Integer} tokenTtl=3600 The time to live for the token and it's authorized session in seconds
*/
tokenTtl: 3600, // 60min
/**
* @cfg {Integer} sidLength=40 The number of characters to create the session ID.
*/
sidLength: 40,
/**
* @cfg {connection} connection REDIS connection information
* @cfg {Number} connection.port=6379 REDIS port
* @cfg {String} connection.host='127.0.0.1' REDIS host
* @cfg {String} [connection.db] REDIS database for sessions
* @cfg {String} [connection.user] REDIS user
* @cfg {String} [connection.pass] REDIS password
*/
connection: {
port: 6379,
host: '127.0.0.1',
db : 'session',
user: undefined,
pass: undefined
},
/**
* @cfg {Object} crypto=node-forge Cryptographic engine
*/
crypto: forge,
/**
* @cfg {String} cert The server X.509 certificate in pem format
*/
cert: fs.readFileSync(path.join(__dirname,'cert/serverCert.cer'), 'utf8'),
/**
* @cfg {String} key The server private RSA key in pem format
*/
key: fs.readFileSync(path.join(__dirname,'cert/serverCert.key'), 'utf8'),
/**
* @cfg {String} ca The Certification Authority certificate in pem format
*/
ca: fs.readFileSync(path.join(__dirname,'cert/serverCA.cer'), 'utf8'),
/**
* @cfg {Object} sessionStore The store for the sessions
*/
sessionStore: undefined,
/**
* @cfg {String} SECRET The secret key used in hash operations
*/
SECRET: 'secret', // use your own SECRET!
/**
* @cfg {String} SECRET_CLIENT The key used in conjunction with SECRET to verify handshake token. This key must be the same on both the server and the client.
*/
SECRET_CLIENT: 'secret_client', // use your own SECRET_CLIENT,
/**
* @cfg {String} SECRET_SERVER The key used in conjunction with SECRET to create authorization token
*/
SECRET_SERVER: 'secret_server', // use your own SECRET_SERVER
/**
* @cfg {String} encryptedBodyName = 'ncryptdbdnm' The property name for the encrypted body value
*/
encryptedBodyName: 'ncryptdbdnm',
/**
* @cfg {String} encryptedSignatureName = 'ncryptdsgnnm' The property name for the signature value of the body
*/
encryptedSignatureName: 'ncryptdsgnnm',
/**
* @cfg {Boolean} signBody = true Sign the body when it is sent encrypted
*/
signBody: true,
/**
* @cfg {Boolean} encryptQuery = true Encrypt the values in url query string
*/
encryptQuery: true,
* @cfg {String} authHeader='Authorization' The header used for authentication and authorization
*/
authHeader: 'Authorization',
/**
* @cfg {String} handshakePath='/handshake' The route exposed by the server for the handshake phase
*/
handshakePath: '/handshake',
/**
* @cfg {String} authPath='/auth' The route exposed by the server for the authentication/authorization phases
*/
authPath: '/auth',
/**
* @cfg {String} logoutPath='/logout' The route exposed by the server for the logout
*/
logoutPath: '/logout',
/**
* @cfg {Integer} clockSkew=0 Max age (in seconds) of the request/reply.
* Every request must have a valid response within clockSkew seconds.
* Note: you must enable a NTP server both on client and server. Set 0 to disable date check or 300 like Kerberos.
*/
clockSkew: 0,
/**
* The login handler. You MUST define your own login strategy.
*
* ## Example
*
* var fs = require('fs'),
* authorify = require('auhtorify')({
* key: fs.readFileSync('serverCert.key'), 'utf8'),
* cert: fs.readFileSync('serverCert.cer'), 'utf8'),
* ca: fs.readFileSync('serverCA.cer'), 'utf8'),
* login: function(id, app, username, password, callback) {
* // manage id and app as your needs
* if (username === 'username' && password === 'password') {
* callback(1, ['admin']);
* } else if (username === 'user' && password === 'pass') {
* callback(2, ['user']);
* } else {
* callback(new Error('user and/or password wrong'));
* }
* }
* });
*
* @cfg {Function} login
* @param {String} id The id (uuid) assigned to the client
* @param {String} app The app (uuid) assigned to the application that the client want to use
* @param {String} username The username for the browser login
* @param {String} password The password for the browser login
* @param {Function} callback Function called when the identifier is created
* @return {callback(err, userId, admins)} The callback to execute as result
* @param {String/Error} callback.err Error if occurred
* @param {String} callback.userId The user identifier
* @param {String/Array} callback.admins The role/s of the user
*
*/
login: function(id, app, username, password, callback) {
throw new Error('you must implement your own login method');
},
/**
*
* Enable routes without Authorization header. When false the authorization header is mandatory for every route even if the route does not have a specific authorization. Set true to try to manage a route if it does not have an authorization handler.
*
* ## Example 1
*
* // dependencies
* var restify = require('restify'),
* authorify = require('auhtorify')({
* freeRoutes: true
* // other options
* });
*
* // create the server
* var server = restify.createServer();
*
* // add middlewares
* server.use(restify.queryParser({ mapParams: false }));
* server.use(restify.bodyParser());
* server.use(authorify.authentication);
*
* // last route handler
* var ok = function(req, res, next){
* res.send({ success: true });
* };
*
* // routes
* // in the route below 'ok' can handled if the request is with or without the Authorization header
* server.get('/free', ok);
* // in the route below if the request doesn't have the Authorization header, the sec.isLoggedIn()
* // handler is performed, but fails and the 'ok' handler isn't reached
* server.get('/secure', sec.isLoggedIn(), ok);
*
* ## Example 2
*
* // dependencies
* var restify = require('restify'),
* authorify = require('auhtorify')({
* freeRoutes: false
* // other options
* });
*
* // create the server
* var server = restify.createServer();
*
* // add middlewares
* server.use(restify.queryParser({ mapParams: false }));
* server.use(restify.bodyParser());
* server.use(authorify.authentication);
*
* // last route handler
* var ok = function(req, res, next){
* res.send({ success: true });
* };
*
* // routes
* // in the route below 'ok' can handled only if the request has the Authorization header
* server.get('/free', ok);
* // in then route below if the request doesn't have the Authorization header, the sec.isLoggedIn() handler
* // isn't reached and the request fails; if the request have the Authorization header but the user
* // isn't logged, the 'ok' handler ins't performed (e.g.: session expired)
* server.get('/secure', sec.isLoggedIn(), ok);
*
*
* @cfg {Boolean} freeRoutes=true
*
*/
freeRoutes: true,
/**
* The field name used for the user identifier into the routes.
*
* ## Example
*
* var restify = require('restify'),
* authorify = require('auhtorify')({
* userIdFieldName: 'myuser'
* // other options
* });
* server.get('/secure/user/:myuser', sec.isSelf(), ok);
*
*
* @cfg {String} userIdFieldName='user'
*/
userIdFieldName: 'user'
};
// merge app.config with default config
app.config = _.extend(my.config, app.config);
app.name = app.config.name;
if (!app.config.sessionStore) {
app.config.sessionStore = require('restify-session')({
ttl: app.config.sidTtl,
persist: false,
debug: app.config.debug,
logger: app.config.logger,
sidLength: app.config.sidLength,
connection: app.config.connection
});
}
app._ = _;
app.async = async;
app.config.sessionStore.name = app.name + ' session store';
app.sessionStore = app.config.sessionStore;
app.mathParser = math().parser();
app.logger = app.config.logger;
app.client = require('authorify-client')(app.config);
/**
* Load a plugin module to add some functionality.
*
* ## Example
*
* var authorify = require('auhtorify')({
* // add your options
* });
* authorify.load('pluginname', 'shortname', opts); // opts is an optional object to configure the plugin
* var loadedPlugin = authorify.plugin['shortname'];
* // below you can use all methods/properties exported by the plugin (loadedPlugin)
*
* @param {String} name The name of the plugin. THe plugin must be installed into the
* application folder that uses the authorify module.
* @param {String} [shortname] An optional short name for the plugin loaded as property in the root application
* (authorify.plugin['shortname']).
* @param {Object} [opts] The options required by the plugin.
*/
app.load = function(name, shortname, opts) {
if (_.isObject(shortname)) {
opts = shortname;
shortname = name;
} else if (!shortname) {
shortname = name;
}
opts = opts || {};
app.plugin = app.plugin || {};
var plugin = require(name)(app, opts);
if (plugin) {
app.logger.info('%s plugin %s loaded with name %s', app.name, name, shortname);
app.plugin[shortname] = plugin;
}
};
app.logger.info('%s setup completed', app.name);
return app;
};
/**
* {@link node_modules.authorify.authentication The authentication middleware.}
*
* @property {function (req, res, next)} authentication
* @param {IncomingMessage} req Request
* @param {ServerResponse} res Response
* @param {Function} next The callback
* @param {Error} next.err The error instance if occurred
*/
/**
* {@link node_modules.authorify.authorization The authorization middleware.}
*
* @property {Object} authorization
*
*/