Socket
Socket
Sign inDemoInstall

cansecurity

Package Overview
Dependencies
10
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.0 to 2.0.0

.jshintrc

153

lib/sessionManager.js

@@ -5,3 +5,5 @@ /*global module, require, Buffer, console */

tokenlib = require( './token' ),
getUser, publicMethods, validatePass, validate,
util = require('./util'),
now = util.now,
publicMethods, validate,
errors = require( './errors' ),

@@ -20,4 +22,3 @@ sender = require('./sender'),

var USERHEADER = constants.header.USER,
AUTHHEADER = constants.header.AUTH,
var AUTHHEADER = constants.header.AUTH,
AUTHMETHODHEADER = constants.header.AUTHMETHOD,

@@ -32,3 +33,3 @@ AUTHSESSION = AUTHHEADER,

/*jslint regexp:true */
var MSGRE = /^error=(.+)$/;
var MSGRE = /^error (.+)$/;
/*jslint regexp:false */

@@ -74,8 +75,11 @@

response.set( CORSHEADER, _.compact( existing.split( /,/ ) )
.concat( [ AUTHHEADER, USERHEADER ] )
.concat( [ AUTHHEADER ] )
.join( "," ) );
},
getAuthTokenFromHeaders = function ( req ) {
var authToken = req.headers[ AUTHHEADER ] || req.headers[ AUTHHEADER.toLowerCase() ];
var header = req.headers.authorization,
authToken = header && header.indexOf("Bearer ") === 0 ? header.split(' ')[1] : null;
return authToken;
/*
if ( !authToken ) {return null;}

@@ -97,2 +101,3 @@

return auth;
*/
};

@@ -115,9 +120,13 @@

},
setupSessionHeaders = function ( request, response, user, login, password, method, expiry ) {
var userAsJson;
setupSessionHeaders = function ( request, response, user, login, method, expiry ) {
var userAsJson, header;
request[ AUTHHEADER ] = user || {};
request[ AUTHMETHODHEADER ] = method;
userAsJson = JSON.stringify( request[ AUTHHEADER ] );
response.header( AUTHHEADER, "success=" + tokenlib.generate( login, password, expiry ) );
response.header( USERHEADER, encryptHeader ? tokenlib.cipher( userAsJson ) : userAsJson );
header = [
"success",
tokenlib.generate( login, userAsJson, expiry ),
login,
expiry
]; response.header( AUTHHEADER, header.join(" "));
},

@@ -131,3 +140,2 @@ removeSessionData = function ( request ) {

response.removeHeader( AUTHHEADER );
response.removeHeader( USERHEADER );
},

@@ -143,4 +151,3 @@ endSession = function ( request, response ) {

prepareErrorHeaders = function ( response, message ) {
response.header( AUTHHEADER, "error=" + message );
response.removeHeader( USERHEADER );
response.header( AUTHHEADER, "error " + message );
},

@@ -164,7 +171,7 @@ endSessionWithErrorMessage = function ( request, response, message ) {

res = config.res,
expiry = Date.now() + sessionExpiry;
expiry = now() + sessionExpiry;
appendCORSHeader( res );
setupSessionData( req, config.user, config.login, expiry );
setupSessionHeaders( req, res, config.user, config.login, config.password, config.method, expiry );
setupSessionHeaders( req, res, config.user, config.login, config.method, expiry );
};

@@ -196,64 +203,4 @@

}
},
validateLegacyCallback = function ( user, message, pass ) {
if ( user ) {
warn("Successfully validated "+user+" via basic auth legacy");
startSession( {
req: this.req,
res: this.res,
user: user,
login: this.creds.user,
password: pass,
method: constants.method.CREDENTIALS
} );
this.next();
} else {
warn("Failed to validate user via basic auth legacy");
endSessionWithErrorMessage( this.req, this.res, message );
sender(this.res, 401, errors.unauthenticated( message ) );
}
},
validateWithAuthTokenCallback = function ( success, user, login, pass ) {
var authToken = this.auth.token;
if ( success && tokenlib.validate( authToken, login, pass ) ) {
warn("Successfully validated "+user+" via auth token");
startSession( {
req: this.req,
res: this.res,
user: user,
login: login,
password: pass,
method: constants.method.TOKEN
} );
} else {
warn("Failed to validate "+user+" via auth token");
endSessionWithErrorMessage( this.req, this.res, errors.invalidtoken() );
}
this.next();
},
validateGetUserSuccessCallback = function ( user, login, password ) {
var authToken = this.auth.token;
if ( tokenlib.validate( authToken, login, password ) ) {
warn("Successfully validated "+user+" via getUser");
startSession( {
req: this.req,
res: this.res,
user: user,
login: login,
password: password,
method: constants.method.TOKEN
} );
} else {
warn("Failed to validate user via getUser");
endSessionWithErrorMessage( this.req, this.res, errors.invalidtoken() );
}
this.next();
},
validateGetUserFailureCallback = function () {
warn("Failed to validate user via authToken using getUser");
endSessionWithErrorMessage( this.req, this.res, errors.invalidtoken() );
this.next();
};
//////////////////////////

@@ -268,4 +215,2 @@ //// SESSION CREATION ////

warn("Basic Auth Creds found for "+creds.user);
var validateFunc = validate || validatePass;
var validateCallbackFunc = ( validate ? validateCallback : validateLegacyCallback );

@@ -278,3 +223,3 @@ var context = {

};
validateFunc( creds.user, creds.password, validateCallbackFunc.bind( context ) );
validate( creds.user, creds.password, validateCallback.bind( context ) );
return true;

@@ -290,3 +235,3 @@ }

warn("Session user found");
if ( session.auth.expiry > Date.now() ) {
if ( session.auth.expiry > now() ) {
warn("Session still valid");

@@ -314,30 +259,35 @@ startSession( {

tryStartSessionWithAuthToken = function ( req, res, next ) {
var auth = getAuthTokenFromHeaders( req ), ret;
var auth = getAuthTokenFromHeaders( req ), ret, token, login;
warn("Try Session with Auth Token for "+req.url);
if ( auth ) {
warn("Auth Token found");
if ( auth.valid ) {
warn("Previous session believed to be valid, checking...");
var context = {
req: req,
res: res,
next: next,
auth: auth
};
// first validate the token
token = tokenlib.validate( auth );
// if succeeded, then we need to validate the user from the DB
if ( token ) {
login = token.sub;
warn("Successfully validated "+login+" via auth token");
if ( validate ) {
warn("Trying with validate...");
validate( auth.user, undefined, validateWithAuthTokenCallback.bind( context ) );
} else if ( getUser ) {
warn("Trying with getUser");
getUser(
auth.user,
validateGetUserSuccessCallback.bind( context ),
validateGetUserFailureCallback.bind( context )
);
warn("Trying to check user ...");
validate( token.sub, undefined, function (success, user ) {
warn("Tried validation, success? "+success);
if (success) {
startSession( {
req: req,
res: res,
user: user,
login: login,
method: constants.method.TOKEN
} );
next();
}
} );
}
} else {
warn("Previous session not found valid");
warn("Failed to validate "+login+" via auth token");
endSessionWithErrorMessage( req, res, errors.invalidtoken() );
next();
sender(res, 401, errors.invalidtoken() );
}

@@ -362,3 +312,2 @@ ret = true;

"use strict";
return tryStartSessionWithBasicAuthCredentials( req, res, next ) ||

@@ -382,3 +331,3 @@ tryStartWithPreviousSessionData( req, res, next ) ||

if ( req ) {
return encryptHeader ? tokenlib.cipher( req[ AUTHHEADER ] ) : req[ AUTHHEADER ];
return req[ AUTHHEADER ];
}

@@ -392,4 +341,2 @@ return null;

validate = fnOrNull( config.validate );
getUser = fnOrNull( config.getUser );
validatePass = fnOrNull( config.validatePassword );
sessionExpiry = ( config.expiry || SESSIONEXPIRY ) * 60 * 1000;

@@ -396,0 +343,0 @@ encryptHeader = ( config.encryptHeader || false );

@@ -1,3 +0,2 @@

/*jslint node:true */
var crypto = require('crypto'), exports = module.exports;
var crypto = require('crypto'), exports = module.exports, jwt = require('jsonwebtoken');

@@ -9,2 +8,3 @@ var sessionKey = null;

};
var now = require('./util').now;

@@ -19,8 +19,5 @@ exports.init = function(key, encrypt) {

exports.generate = function(name, password, expiry) {
var token, sha1 = crypto.createHash('sha1');
token = [name,password,sessionKey,expiry].join(":");
sha1.update(token);
token = [sha1.digest('hex'),name,expiry].join(":");
exports.generate = function(name, user, expiry) {
var token = jwt.sign({sub:name,exp:expiry,"cs-user":user},sessionKey,{algorithm:"HS256"});
if(encryptHeader) {

@@ -33,2 +30,27 @@ token = exports.cipher(token);

exports.validate = function(token) {
warn("validating auth token "+token);
var valid = false, expiry, decoded, t = now();
token = token || "";
if (encryptHeader) {
token = exports.decipher(token);
}
try {
decoded = jwt.verify(token,sessionKey,{algorithms:"HS256"});
expiry = decoded.exp;
warn("token expiry "+expiry+" now "+t);
warn("token name "+decoded.sub);
if (!isNaN(expiry) && parseInt(expiry,10) > t) {
valid = decoded;
} else {
valid = false;
}
} catch (e) {
valid = false;
}
warn("token valid? "+valid);
return(decoded);
};
exports.cipher = function(token) {

@@ -48,18 +70,1 @@ var cipher = crypto.createCipher('rc4-hmac-md5', sessionKey);

exports.validate = function(token,name,password) {
warn("validating auth token '"+token+"' for "+name);
var p, sec, valid = false, expiry, sha1 = crypto.createHash('sha1');
token = token || "";
p = token.split(":");
expiry = p[2];
warn("token expiry "+expiry+" now "+new Date().getTime());
warn("token name "+p[1]);
if (p[1] === name && !isNaN(expiry) && parseInt(expiry,10) > new Date().getTime()) {
sec = [name,password,sessionKey,expiry].join(":");
sha1.update(sec);
sec = sha1.digest('hex');
valid = sec === p[0];
}
warn("token valid? "+valid);
return(valid);
};
{
"name": "cansecurity",
"description": "cansecurity is your all-in-one security library for user authentication, authorization and management in node expressjs apps",
"version": "1.0.0",
"version": "2.0.0",
"url": "http://github.com/deitch/cansecurity",

@@ -18,4 +18,5 @@ "author": "Avi Deitcher <avi@deitcher.net>",

"dependencies": {
"lodash": ">=1.3.1",
"async": "0.2.x"
"async": "0.2.x",
"jsonwebtoken": "^5.7.0",
"lodash": ">=1.3.1"
},

@@ -27,4 +28,4 @@ "devDependencies": {

"mocha": "1.x",
"restify": "3.x",
"supertest": "0.x"
"restify": "^4.0.4",
"supertest": "^1.2.0"
},

@@ -42,3 +43,3 @@ "keywords": [

"scripts": {
"test": "node_modules/.bin/mocha -R spec"
"test": "mocha -R spec -g '(?:restify)' && mocha -R spec -g restify"
},

@@ -45,0 +46,0 @@ "repository": {

@@ -12,61 +12,63 @@ # cansecurity

var express = require('express'), app = express(), cs = require('cansecurity'), cansec = cs.init(/* init params */);
app.use(cansec.validate);
app.user(app.router);
```Javascript
var express = require('express'), app = express(), cs = require('cansecurity'), cansec = cs.init(/* init params */);
app.use(cansec.validate);
app.user(app.router);
// send200 is a shortcut route to send a 200 response
// send200 is a shortcut route to send a 200 response
// open route
app.get("/public",send200);
// open route
app.get("/public",send200);
// only authorized if logged in, or as certain roles, or some combination
app.get("/secure/loggedin",cansec.restrictToLoggedIn,send200);
app.get("/secure/user/:user",cansec.restrictToSelf,send200);
app.get("/secure/roles/admin",cansec.restrictToRoles("admin"),send200);
app.get("/secure/roles/adminOrSuper",cansec.restrictToRoles(["admin","super"]),send200);
app.get("/secure/selfOrRoles/:user/admin",cansec.restrictToSelfOrRoles("admin"),send200);
app.get("/secure/selfOrRoles/:user/adminOrSuper",cansec.restrictToSelfOrRoles(["admin","super"]),send200);
// only authorized if logged in, or as certain roles, or some combination
app.get("/secure/loggedin",cansec.restrictToLoggedIn,send200);
app.get("/secure/user/:user",cansec.restrictToSelf,send200);
app.get("/secure/roles/admin",cansec.restrictToRoles("admin"),send200);
app.get("/secure/roles/adminOrSuper",cansec.restrictToRoles(["admin","super"]),send200);
app.get("/secure/selfOrRoles/:user/admin",cansec.restrictToSelfOrRoles("admin"),send200);
app.get("/secure/selfOrRoles/:user/adminOrSuper",cansec.restrictToSelfOrRoles(["admin","super"]),send200);
// only authorized if "searchParam" is set to the same value as the user ID field set in cs.init();
app.get("/secure/param",cansec.restrictToParam("searchParam"),send200);
app.get("/secure/paramOrRole",cansec.restrictToParamOrRoles("searchParam","admin"),send200);
app.get("/secure/paramOrMultipleRoles",cansec.restrictToParamOrRoles("searchParam",["admin","super"]),send200);
// only authorized if "searchParam" is set to the same value as the user ID field set in cs.init();
app.get("/secure/param",cansec.restrictToParam("searchParam"),send200);
app.get("/secure/paramOrRole",cansec.restrictToParamOrRoles("searchParam","admin"),send200);
app.get("/secure/paramOrMultipleRoles",cansec.restrictToParamOrRoles("searchParam",["admin","super"]),send200);
// only authorized if getCheckObject() returns an object, with field owner, that has a value matching the user ID field
app.get("/secure/field",cansec.restrictToField("owner",getCheckObject),send200);
app.get("/secure/fields",cansec.restrictToField(["owner","recipient"],getCheckObject),send200);
app.get("/secure/fieldOrRole",cansec.restrictToFieldOrRoles("owner","admin",getCheckObject),send200);
app.get("/secure/fieldOrRoles",cansec.restrictToFieldOrRoles("owner",["admin","super"],getCheckObject),send200);
app.get("/secure/fieldsOrRole",cansec.restrictToFieldOrRoles(["owner","recipient"],"admin",getCheckObject),send200);
app.get("/secure/fieldsOrRoles",cansec.restrictToFieldOrRoles(["owner","recipient"],["admin","super"],getCheckObject),send200);
// only authorized if getCheckObject() returns an object, with field owner, that has a value matching the user ID field
app.get("/secure/field",cansec.restrictToField("owner",getCheckObject),send200);
app.get("/secure/fields",cansec.restrictToField(["owner","recipient"],getCheckObject),send200);
app.get("/secure/fieldOrRole",cansec.restrictToFieldOrRoles("owner","admin",getCheckObject),send200);
app.get("/secure/fieldOrRoles",cansec.restrictToFieldOrRoles("owner",["admin","super"],getCheckObject),send200);
app.get("/secure/fieldsOrRole",cansec.restrictToFieldOrRoles(["owner","recipient"],"admin",getCheckObject),send200);
app.get("/secure/fieldsOrRoles",cansec.restrictToFieldOrRoles(["owner","recipient"],["admin","super"],getCheckObject),send200);
// only authorized if the request parameter "private" has the value "true", and then restrict to logged in
app.get("/secure/conditionalDirect",cansec.ifParam("private","true").restrictToLoggedIn,send200);
app.get("/secure/conditionalIndirect",cansec.ifParam("private","true").restrictToRoles(["admin","super"]),send200);
// only authorized if the request parameter "private" has the value "true", and then restrict to logged in
app.get("/secure/conditionalDirect",cansec.ifParam("private","true").restrictToLoggedIn,send200);
app.get("/secure/conditionalIndirect",cansec.ifParam("private","true").restrictToRoles(["admin","super"]),send200);
```
And if you prefer declarative authorization, even easier:
// inside app.js:
```Javascript
// inside app.js:
// instantiate the user validator
app.use(cansec.validate);
// instantiate the declarative authorizer
app.use(cansec.authorizer(pathToAuthConfigFile));
// instantiate the user validator
app.use(cansec.validate);
// instantiate the declarative authorizer
app.use(cansec.authorizer(pathToAuthConfigFile));
// inside "pathToAuthConfigFile"
{
"routes": [
// [verb,path,default,[test params,] test condition]
["GET","/api/user","user.roles.admin === true"],
["GET","/api/user/:user","user.roles.admin === true || user.id === req.param('user')"],
["GET","/api/user/:user",{"private":"true"},"user.roles.admin === true || user.id === req.param('user')"],
["PUT","/api/user/:user","user.roles.admin === true || user.id === req.param('user')"],
["GET","/api/user/:user/roles","user.roles.admin === true || user.id === req.param('user')"],
["PUT","/api/user/:user/roles","user.roles.admin === true"]
]
}
// inside "pathToAuthConfigFile"
{
"routes": [
// [verb,path,default,[test params,] test condition]
["GET","/api/user","user.roles.admin === true"],
["GET","/api/user/:user","user.roles.admin === true || user.id === req.param('user')"],
["GET","/api/user/:user",{"private":"true"},"user.roles.admin === true || user.id === req.param('user')"],
["PUT","/api/user/:user","user.roles.admin === true || user.id === req.param('user')"],
["GET","/api/user/:user/roles","user.roles.admin === true || user.id === req.param('user')"],
["PUT","/api/user/:user/roles","user.roles.admin === true"]
]
}
```
#### Changes

@@ -86,5 +88,6 @@ For any breaking changes, please see the end of this README.

npm install cansecurity
```
npm install cansecurity
```
## Authentication

@@ -97,6 +100,6 @@ ### Usage

````JavaScript
```JavaScript
var cs = require('cansecurity');
var cansec = cs.init(initConfig);
````
```

@@ -107,6 +110,5 @@ The `initConfig` has six properties:

* `sessionKey`: OPTIONAL. String. Secret key shared between nodejs servers to provide single-sign-on. This is a string. The default, if none is provided, is a random 64-character string. This is **required** if you want to take advantage of cansecurity's stateless sessions. Keep this very secret.
* `validate`: REQUIRED. Function that will get a user by username, and possibly validate a their password, asynchronously. For more details, see below.
* `encryptHeader`: OPTIONAL. With a value of true, the exposed headers (`X-CS-Auth` and `X-CS-User`) are encrypted using `rc4-hmac-md5` algorithm.
* `authHeader`: OPTIONAL. Replaces the Auth header `X-CS-Auth` for the specified header name.
* `userHeader`: OPTIONAL. Replaces the User header `X-CS-User` for the specified header name.
* `validate`: REQUIRED. Function that will get a user by username, and possibly validate their password, asynchronously. For more details, see below.
* `encryptHeader`: OPTIONAL. With a value of true, the entire JWT is encrypted using `rc4-hmac-md5` algorithm.
* `authHeader`: OPTIONAL. Replaces the header `X-CS-Auth` in which the server sends its token and user information back to the requestor/browser with the specified header name. `X-CS-Auth` is used *only* for sending the request from the server to the requestor (browser). The request to the server *always* is `Authentication`.
* `debug`: OPTIONAL. Print debug messages about each authentication attempt to the console. It will **not** include the actual password.

@@ -117,4 +119,6 @@

app.use(cansec.validate);
app.use(app.router);
```Javascript
app.use(cansec.validate);
app.use(app.router);
```

@@ -125,16 +129,19 @@ This should be done **before** your router.

req["X-CS-Auth"];
req.session["X-CS-AUTH"].user; // only if expressjs sessions have been enabled
```Javascript
req["X-CS-Auth"];
req.session["X-CS-AUTH"].user; // only if expressjs sessions have been enabled
```
However, for safety, you should retrieve it using the convenience method:
````JavaScript
```JavaScript
require('cansecurity').getUser(req);
// or simply
cs.getUser(req);
````
```
You can also determine *how* the current user was authorized, credentials (e.g. password) and token, by calling
````JavaScript
```JavaScript
require('cansecurity').getAuthMethod(req);

@@ -144,5 +151,5 @@ // returns "credentials" or "token"

cs.getAuthMethod(req);
````
```
This is very useful in cases when you need the existing password for an action. A common use case is changing a password or setting security parameters. You might be fine with the user logging in via token for most actions (happens all the time when you go back to Facebook or Twitter from the same browser as last time), but if they want to change their password, they need to send the password again (try changing your Facebook or Gmail password, or Gmail two-factor autnentication).
This is very useful in cases when you need the existing password for an action. A common use case is changing a password or setting security parameters. You might be fine with the user logging in via token for most actions (happens all the time when you go back to Facebook or Twitter from the same browser as last time), but if they want to change their password, they need to send the password again (try changing your Facebook or Gmail password, or Gmail two-factor authentication).

@@ -155,17 +162,20 @@

validate(username,password,callback);
```Javascript
validate(username,password,callback);
```
The `validate()` function is expected to retrieve user information from your preferred user store. It *may* validate a password for the user as well, and indicate to the callback if it succeeded or failed. The signature and expected parameters to the callback are as follows:
callback(success,user,message,pass);
```Javascript
callback(success,user,message);
```
Where:
`success`: boolean, if we succeeded in retrieving the user and, if requested, validating password credentials
`user` = the actual user object. This can be a function, a JavaScript object, anything you want. It will be placed inside the session and the request for you to use later. If retrieval/validation was successful, this must not be null/undefined.
`user`: the actual user object. This can be a function, a JavaScript object, anything you want. It will be placed inside the session and the request for you to use later. If retrieval/validation was successful, this must not be null/undefined.
`message` = the error message in case of retrieval/validation failure. This can be anything you want, and will be passed along with the 401 unauthenticated response.
`pass` = the user's password or any other unique per-user string, not easily guessable. Commonly, this would be a hash of a password.
If the user was already authenticated via session, token or some other method, then `validateUser()` will be called with `password` parameter set to `undefined`. If `password` is set to **anything** other than `undefined` (including a blank string), then `validateUser()` is expected to validate the password along with retrieving the user.
````JavaScript
```JavaScript
cansec.init({

@@ -183,3 +193,3 @@ validate: function(username,password,callback) {

### Unauthenticated Errors
When authnetication fails, cansecurity will directly return 401 with the message "unauthenticated".
When authentication fails, cansecurity will directly return 401 with the message "unauthenticated".

@@ -190,17 +200,10 @@ * If authentication is required and succeeds, it will set request["X-CS-Auth"], and request.session["X-CS-Auth"] if sessions are enabled, and then call next() to jump to the next middleware.

If the user has provided HTTP Basic Authentication credentials in the form of username/password **and** the authentication via `validate()` fails. In that case, cansecurity will call
If the user has provided HTTP Basic Authentication credentials in the form of username/password **and** the authentication via `validate()` fails. In that case, cansecurity will return a `401`.
### Why We Need the "Password" in the Validate() Callback
The callback to `validate()` expects you to return a "pass", or any user-unique string. Although this is never given to any other function, let alone to the client, why is the "pass" necessary?
In reality, this can be any unique string at all, as long as it is consistent for the same user. Normally, this would be a hashed password. This is used, along with the secret session key, to create the authtoken for cansecurity sessions. Without using the password or some other unique, non-guessable string, it would be theoretically possible to use one login to spoof another. With the unique non-guessable user string (hashed password or similar) as part of the hash input, this risk is mitigated. PLEASE *PLEASE* **PLEASE** do not pass cleartext passwords here. In reality, your app should never know cleartext passwords, rather storing them as SHA1 or similar hashes.
Thus, to create a unique authentication token that is useful for single-sign-on and cannot be spoofed to another user, we include the unique user string (e.g. a hashed password) as part of the input to the authentication token.
### How Authentication Works
With each request, the following algorithm is followed:
1. Was there an HTTP Basic authentication header? If so, validate using the credentials. If they succeed, the user is authenticated, else send back a 401 unauthenticated and include a response X-CS-Auth header of "error=invalidpass". If not, go to the next step.
2. Was there an X-CS-Auth header? If so, validate using the auth header. If they success, the user is authenticated, else they are not. The requests will continue, but the response will contain an X-CS-Auth header of "error=invalidtoken". If not, go to the next step.
1. Was there an HTTP Basic `Authentication` header? If so, validate using the credentials. If they succeed, the user is authenticated, else send back a `401` unauthenticated and include a response X-CS-Auth header of `"error invalidpass"`. If not, go to the next step.
2. Was there an HTTP Bearer `Authentication` header? If so, validate the JSON Web Token using the auth header. If they succeed, the user is authenticated, else they are not and return a `401` with `error=invalidtoken`. If not, go to the next step.
3. Is there a valid and non-expired expressjs session? If so, the user is authenticated. If not, go to the next step.

@@ -211,28 +214,64 @@ 4. The user is not authenticated.

### HTTP Response Headers
cansecurity passes details about success or failure of authentication in custom `X-` HTTP response headers. Of course, a failed authentication will return a `401`, but the *reason* for failure will be in the appropriate header listed in this section. Similarly, a successful authentication - by *any* means - will allow the request to go through returning a `200`, `201`, `404`, etc., depending on the app. cansecurity will, however, return the session token and logged in user via appropriate HTTP response headers.
To summarize:
#### X-CS-Auth Header
The X-CS-Auth response header contains error responses or success tokens. If authentication was successful, by any means, then a new header is generated with each request. This header is of the following format:
* Passing credentials in an `Authentication` header (`Bearer` with JWT or `Basic` with username/password) that fail, **always** will return a `401`.
* Passing no credentials but with a valid expressjs session will pass the request with the user as authenticated to the app.
* Passing no credentials with no valid expressjs sessions will pass the request with the user as unauthenticated to the app.
success=sha1hash:username:expiry
### HTTP Headers
#### HTTP Response Header
cansecurity passes details about success or failure of authentication in the custom `X-CS-Auth` HTTP response header. Of course, a failed authentication will return a `401`, but the *reason* for failure will be in the appropriate header listed in this section. Similarly, a successful authentication - by *any* means - will allow the request to go through returning a `200`, `201`, `403`, `404`, etc., depending on the app. cansecurity *will*, however, return the session token and logged in user via the `X-CS-Auth` HTTP response header.
The `X-CS-Auth` response header contains error responses or success tokens.
##### Success
A successful authentication provides the following format:
```
success token username expiry
```
Where:
`sha1hash` = a sha1 hash of the username, the expiry, the secret session key and the user's unique string (likely itself a hashed password).
`username` = the user's username
`expiry` = when this auth token will expire, as a JavaScript (Unix) millisecond timestamp, provided by Date().getTime().
Essentially, we are using a message validation algorithm to validate that the username and expiry are, indeed, valid.
`success`: result of the authentication, either `success` or `error`
`token`: a JSON Web token if `success`, or an error message if `error`
`username`: the user's username if `success`
`expiry`: when this token will expire, as a JavaScript (Unix) second timestamp, provided by `Math.floor(new Date().getTime()/1000)`
Because the auth header is created anew with each request, the expiry window is rolling - x minutes from the last valid request.
Because a new token is created anew with each request, the expiry window is rolling - x minutes from the last valid request.
#### X-CS-User Header
The X-CS-User response header contains the actual logged in user when authentication by any means was successful. Normally, it is a JSON-encoded string, but it really depends on what your `validate()` function returns in the `user` parameter of the `callback`.
Both the `username` and `expiry` are convenience methods. They are contained within the JSON Web Token provided in the response.
**Note**: You need to be **really** careful with what `validate()` returns. *Everything* in there goes into the `X-CS-User` response header. While it only goes in the header to the authenticated user, it still is sending out everything you send. You might not want the password - even hashed - in the header fields.
The JWT payload contains the following fields:
* `sub`: the subject of the JWT. This is identical to the `username` field in the header response.
* `exp`: the expiry of the token. This is identical to the `expiry` field in the header response.
* `cs-user`: the actual logged in user when authentication by any means was successful.
The `cs-user` is a "JST private claim". Normally, it is a JSON-encoded string, but it really depends on what your `validate()` function returns in the `user` parameter of the `callback`.
**Note**: You need to be **really** careful with what `validate()` returns. *Everything* in there goes into the `cs-user` field in the JWT. While it only goes in the header to the authenticated user, it still is sending out everything you send. You might not want the password - even hashed - in the header fields.
##### Failure
A failed authentication provides the following format:
```
error message
```
Where:
`result`: result of the authentication, either `success` or `error`
`message`: the error message, if any
#### CORS
Note for usage in CORS situations. cansecurity automatically adds the following header to every response:
Access-Control-Expose-Headers: X-CS-Auth,X-CS-User
```
Access-Control-Expose-Headers: X-CS-Auth
```

@@ -259,3 +298,3 @@ Of course, it does so intelligently, so it adds it to an existing list of headers (does not trounce them) or creates it.

// never asked to check a password, just send the user - GOOD
callback(true,user,user.name,shaHash(pass));
callback(true,user,user.name);
} else if (user.pass !== pass) {

@@ -266,3 +305,3 @@ // asked to check password, but it didn't match - ERROR

// user matches, password matches - GOOD
callback(true,user,user.name,shaHash(pass));
callback(true,user,user.name);
}

@@ -310,3 +349,3 @@ },

````JavaScript
```JavaScript
express = require('express'),

@@ -319,3 +358,3 @@ cansec = require('cansecurity').init({});

````
```

@@ -328,12 +367,12 @@ #### Usage

````JavaScript
```JavaScript
var cs = require('cansecurity'), cansec;
cansec = cs.init({});
````
```
or more simply:
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({});
````
```

@@ -353,3 +392,3 @@ In initialization, you set two key authorization parameters as properties of the config object passed to cs.init(). Both are objects and both are optional.

````JavaScript
```JavaScript
// execute routeHandler() if user is logged in, else send 401

@@ -366,3 +405,3 @@ app.get("/some/route/:user",cansec.restrictToLoggedIn,routeHandler);

````
```

@@ -385,9 +424,9 @@ #### Unauthorized Errors

````JavaScript
```JavaScript
app.get("/some/route/:user",cansec.restrictToLoggedIn,routeHandler);
````
```
* restrictToSelf - user must have logged in and the user ID in the user object from authentication (fields.id above) must equal some parameter in the URL or body (params.id)
````JavaScript
```JavaScript
var cs = require('cansecurity');

@@ -401,7 +440,7 @@ cansec = cs.init({

// note that the param in the route is ":user", which matches the params.id:"user" in cansec.init()
````
```
* restrictToRoles - user must have logged in and the user must have in his/her "roles" property (fields.roles) in the user object from authentication one of the named roles (one role as string or multiple in array). Roles argument to the function may be a string or an array of strings.
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -417,3 +456,3 @@ fields: {id: "userid", roles:"roles"},

````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -429,7 +468,7 @@ fields: {id: "userid", roles:"roles"},

*/
````
```
* restrictToParam - user must have logged in and some field in the user object (fields.id) from authentication must equal some parameter in the URL or body (params.id). Param argument to the function may be a string or an array of strings.
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -444,7 +483,7 @@ fields: {id: "userid", roles:"roles"},

*/
````
```
* restrictToParamOrRoles - user must have logged in and some field in the user object (fields.id) from authentication must equal some parameter in the URL or body (params.id) *or* user must have a specific role. Param argument and roles argument to the function may each be a string or an array of strings.
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -461,7 +500,7 @@ fields: {id: "userid", roles:"roles"},

*/
````
```
* restrictToField - user must have logged in and some field in the user object (fields.id) from authentication must equal the response to a given callback with a given field or fields parameter.
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -475,7 +514,7 @@ fields: {id: "userid", roles:"roles"},

*/
````
```
* restrictToFieldOrRoles - user must have logged in and some field in the user object (fields.id) from authentication must equal the response to a given callback with a given field or fields parameter, *or* the user must have a role or roles.
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -490,7 +529,7 @@ fields: {id: "userid", roles:"roles"},

app.get("/api/user/search",cansec.restrictToFieldOrRoles(["owner","recipient"],["admin","superadmin"],getOwnerFn),routeHandler);
````
```
A typical use case for restrictToField and its variant restrictToFieldOrRoles is that you may load an object, and want to restrict its access to the owner of an object. For example, let us assume that you are loading an employee record. For that, restrictToSelf would be fine, since the User ID from authentication is likely to match the ID for requesting the employee record. The following example shows this use case:
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -501,7 +540,7 @@ fields: {id: "userid", roles:"roles"},

app.get("/api/employee/:user",cansec.restrictToSelfOrRoles("admin"),sendDataFn);
````
```
However, what if you are retrieving a record whose authorization requirements are not known until it is loaded. For example, you are loading a paystub, whose URL is /api/paystubs/34567. Until you load the paystub, you don't actually know who the owner is. Of course, you might make it accessible only via a more detailed API as /api/employee/12345/paystubs/34567, but let us assume that you need to do it directly, with the first simplified API. Until you load the actual paystubs object, and see that the employee is, indeed, 12345, the one who logged in, you don't know whether or not to show it. The following example describes how to simply implement this use case:
````JavaScript
```JavaScript
var cansec = require('cansecurity').init({

@@ -513,7 +552,7 @@ fields: {id: "userid", roles:"roles"},

app.get("/api/paystub/:payid",payStubLoad,cansec.restrictToField("employee",getObjectFn),sendDataFn);
````
```
In this example, we load the paystub, but do not send it. The paystub object retrieved by payStubLoad looks like this:
````JavaScript
```JavaScript
{

@@ -525,3 +564,3 @@ id: "34567",

}
````
```
This is then stored in the request object. Now getObjectFn can return the same object, which has employee as "12345". This is then matched to User.userid, which will allow it to proceed.

@@ -537,5 +576,5 @@

````JavaScript
```JavaScript
app.get("/api/employee",cansec.ifParameter("secret","true").restrictToRoles("admin"),sendDataFn);
````
```

@@ -566,3 +605,3 @@ In the above example, anyone can do a GET on /api/employee, but if they pass the parameter ?secret=true, then they will have to be logged in and have the role "admin" defined.

````JavaScript
```JavaScript
app.get("/api/employee/:user",cansec.restrictToSelfOrRoles("admin"),sendDataFn);

@@ -572,3 +611,3 @@ app.get("/public/page",send200);

app.get("*",function(req,res,next){res.send(403);});
````
```

@@ -578,3 +617,3 @@ ### Declarative Authorization

````JavaScript
```JavaScript
app.get("/secure/loggedin",cansec.restrictToLoggedIn,send200);

@@ -603,3 +642,3 @@ app.get("/secure/user/:user",cansec.restrictToSelf,send200);

app.get("/secure/conditionalIndirect",cansec.ifParam("private","true").restrictToRoles(["admin","super"]),send200);
````
```

@@ -614,3 +653,3 @@ This is worlds better than before, when authorization was one of:

````JavaScript
```JavaScript
{

@@ -627,3 +666,3 @@ "routes": [

}
````
```

@@ -641,3 +680,3 @@ cansecurity provides you precisely this ability!

````JavaScript
```JavaScript
{

@@ -651,7 +690,9 @@ routes:[

}
````
```
Each route is an array of 4 or 5 parts, as follows:
[verb,route,[params,][loggedIn,][loader,]condition]
```
[verb,route,[params,][loggedIn,][loader,]condition]
```

@@ -667,3 +708,3 @@ * verb: string, one of GET PUT POST DELETE, and is case-insensitive

````JavaScript
```JavaScript
// when GET /api/user, send 403 unless user.roles.admin === true

@@ -696,3 +737,3 @@ ["GET","/api/user","user.roles.admin === true"],

["PUT","/api/user/:user/roles","roles","(user.roles.admin === true) || (item.name === 'me')"]
````
```

@@ -702,3 +743,3 @@ #### Deny All

````JavaScript
```JavaScript
// when PUT /api/user/:user/roles, send 403 unless user.roles.admin === true

@@ -712,3 +753,3 @@ ["PUT","/api/user/:user/roles","user.roles.admin === true"]

["GET","*","false"]
````
```

@@ -740,3 +781,3 @@

````JavaScript
```JavaScript
cansec.init({

@@ -752,7 +793,7 @@ loader: {

});
````
```
And the declarative:
````JavaScript
```JavaScript
{

@@ -763,3 +804,3 @@ routes: [

}
````
```

@@ -774,3 +815,3 @@

````JavaScript
```JavaScript
cansec.init({

@@ -784,7 +825,7 @@ loader: {

});
````
```
And the declarative part:
````JavaScript
```JavaScript
{

@@ -795,3 +836,3 @@ routes: [

}
````
```

@@ -801,3 +842,3 @@ ##### Local

````JavaScript
```JavaScript
// in your main server.js

@@ -807,20 +848,19 @@ app.use(cansec.authorizer(__dirname+'/path/to/decl.json',{loader: {

fn2: function(req,res,next){}
}}))`
````
}}))
```
Of course, you can always separate the loader functions into another file, like with the init file, and `require` it yourself:
````JavaScript
```JavaScript
// in your main server.js
app.use(cansec.authorizer(__dirname+'/path/to/decl.json',{loader: require(pathToLoader)}))`
````
app.use(cansec.authorizer(__dirname+'/path/to/decl.json',{loader: require(pathToLoader)}))
```
If you can do it globally, why bother with the local? Simple. You can have *multiple* declarative files. For example, we often separate the security authorization (user Jim is allowed to see his own account) from subscription authorization (user Jim already has 2 accounts and needs to upgrade his plan to get another).
````JavaScript
```JavaScript
// in your main server.js
app.use(cansec.authorizer(__dirname+'/path/to/security.json',{loader:securityConfig}))`
app.use(cansec.authorizer(__dirname+'/path/to/plans.json',{loader:plansConfig}))`
````
app.use(cansec.authorizer(__dirname+'/path/to/security.json',{loader:securityConfig}))
app.use(cansec.authorizer(__dirname+'/path/to/plans.json',{loader:plansConfig}))
```

@@ -859,5 +899,7 @@ If course, you might want to keep them together, in which case just use the global!

app.use(cansec.authorizer(pathToConfigFile,options));
app.use(app.router);
```javascript
app.use(cansec.authorizer(pathToConfigFile,options));
app.use(app.router);
```
Done!

@@ -869,5 +911,5 @@

````JavaScript
```JavaScript
app.get("/api/user/:user.:format?",fn);
````
```

@@ -878,10 +920,11 @@ This allows express to handle both `/api/user/10` and `/api/user/10.json`.

["GET","/api/user/:user.:format?",{"private":"true"},true,"user.roles.admin === true || user.id === req.param('user')"],
```javascript
["GET","/api/user/:user.:format?",{"private":"true"},true,"user.roles.admin === true || user.id === req.param('user')"],
```
But that can get tedious, if you have a lot of routes. To simplify things, one of the options when setting it up is `{format:true}` as follows:
````JavaScript
```JavaScript
app.use(cansec.authorizer(pathToConfigFile,{format:true}));
````
```

@@ -892,3 +935,3 @@ If `format` is set to `true`, then cansecurity will *automatically* add `.:format?` to every path that does not end in `.:format?` already, or in `/`.

## Testing
To run the tests, from the root directory, run `npm test` or more directly `mocha`.
To run the tests, from the root directory, run `npm test`.

@@ -899,5 +942,8 @@ **Note:** Tests are set up both for express and for restify. However, running them both causes one to trounce the other. Apparently restify grabs hold of the http module and its server munges the requests that express tries to read. One cannot really blame restify; it never intended to run in the same node instance with another server.

mocha -g express
mocha -g restify
```
mocha -g '(?:restify)'
mocha -g restify
```
`npm` is set up to do precisely this for you, if you run `npm test`.

@@ -907,2 +953,25 @@

#### Changes to version 2.0.0
2.0.0 is a major release with many breaking changes.
##### JWT instead of multiple headers
The `X-CS-Auth` header is deprecated from requests. All credentials should be passed in the `Authentication` header, either `Basic` for user/pass authentication, or `Bearer` for a JSON Web Token that was generated by cansecurity.
The `X-CS-User` header is deprecated from responses. All of the user information is now held as a field of the returned JWT.
* Response: `X-CS-User` contains the status and JWT, which includes the user information.
* Request: `Authorization` contains the credentials, either user/pass or JWT.
##### Separation of fields by space instead of colon
In a successful response, the fields are separated by whitespace instead of `:`:
X-CS-Auth: success token username expiry
Instead of
X-CS-Auth: success=token:username:expiry
##### Expiry in seconds instead of milliseconds
In keeping with the JWT standard, the expiry field - both in the header and in the JWT - are given in timestamp seconds since epoch, instead of milliseconds.
#### Changes to version 1.0.0

@@ -919,5 +988,6 @@ Express 4 support!

mocha -g express
mocha -g restify
```
mocha -g express
mocha -g restify
```

@@ -928,17 +998,21 @@

["GET","/secure/path",true,"allow","a === b"]
```javascript
["GET","/secure/path",true,"allow","a === b"]
```
Can just as easily be written as
["GET","/secure/path",true,"deny","a !== b"]
```javascript
["GET","/secure/path",true,"deny","a !== b"]
```
Or more simply
["GET","/secure/path",true,"a !== b"]
````javascript
["GET","/secure/path",true,"a !== b"]
````
#### Changes to version 0.6.0
Prior to version 0.6.0, cansecurity *sometimes* would send back a 401 or 403 as `res.send(401,"unauthenticated")` or `res.send(403,"unauthorized")`, and sometimes would just call `next({status:401,message:"unauthenticated"})` or `next({status:403,message:"unauthorized"})`.
Beginnign with version 0.6.0, cansecurity will **always** return 401 if authentication is required and not present / fails, and will **always** return a 403 if authorization is required and fails.
Beginning with version 0.6.0, cansecurity will **always** return 401 if authentication is required and not present / fails, and will **always** return a 403 if authorization is required and fails.

@@ -968,1 +1042,2 @@ This makes the results far more consistent.

Beginning with cansecurity 1.0, the old API will not function at all.
/*jslint node:true, nomen:true, unused:vars */
/*global before, it, describe, after */
/*global before, it, describe */
var express = require('express'), restify = require('restify'), app = express(), request = require('supertest'),

@@ -355,5 +355,2 @@ cansec, cs = require('./resources/cs'), errorHandler = require('./resources/error'),

});
after(function(){
app.close();
});
alltests();

@@ -360,0 +357,0 @@ });

/*jslint node:true, unused:vars */
/*global before,it,describe, after */
var express = require( 'express' ), restify = require('restify'),
/*global before,it,describe */
var express = require( 'express' ), restify = require('restify'), jwt = require('jsonwebtoken'),
app = express(),

@@ -14,3 +14,2 @@ async = require( 'async' ),

authHeader = "X-CS-Auth".toLowerCase(),
userHeader = "X-CS-User".toLowerCase(),
userInfo = JSON.stringify( {

@@ -23,2 +22,12 @@ name: "john",

} ),
successRe = /^success ((\S+)\s(\S+)\s(\S+))$/,
checkUserInfo = function (res) {
var match = res.headers[ authHeader ].match( successRe ), decoded;
// cannot decode until we decrypt
decoded = jwt.decode(tokenlib.decipher(match[2]));
return decoded["cs-user"] === userInfo;
},
now = function () {
return Math.floor(Date.now()/1000);
},
path = "/public",

@@ -28,18 +37,18 @@ alltests = function () {

r.get( path )
.set( authHeader, "blahblah" )
.expect( 200 )
.expect( authHeader, "error=invalidtoken", done );
.set( "Authorization", "Bearer blahblah" )
.expect( 401 )
.expect( authHeader, "error invalidtoken", done );
} );
it( 'should reject expired token', function ( done ) {
var token = tokenlib.generate( "john", "1234", Date.now() - ( 24 * 60 * 60 * 1000 ) );
var token = tokenlib.generate( "john", "1234", now() - ( 24 * 60 * 60 ) );
r.get( path )
.set( authHeader, token )
.expect( 200 )
.expect( authHeader, "error=invalidtoken", done );
.set( "Authorization", "Bearer "+token )
.expect( 401 )
.expect( authHeader, "error invalidtoken", done );
} );
it( 'should accept a valid token', function ( done ) {
var token = tokenlib.generate( "john", "1234", Date.now() + 15 * 60 * 1000 ),
re = /^success=/;
var token = tokenlib.generate( "john", "1234", now() + 15 * 60 ),
re = /^success /;
r.get( path )
.set( authHeader, token )
.set( "Authorization", "Bearer "+token )
.expect( 200 )

@@ -50,7 +59,7 @@ .expect( authHeader, re, done );

var user = "john",
expiry = Date.now() + 15 * 60 * 1000,
expiry = now() + 15 * 60,
token = tokenlib.generate( user, "1234", expiry ),
re = /^success=/;
re = /^success /;
r.get( path )
.set( authHeader, token )
.set( "Authorization", "Bearer "+token )
.expect( 200 )

@@ -61,4 +70,3 @@ .expect( authHeader, re, done );

var user = "john",
token = tokenlib.generate( user, "1234", Date.now() + 15 * 60 * 1000 ),
successRe = /^success=(.+)$/;
token = tokenlib.generate( user, "1234", now() + 15 * 60 );

@@ -69,6 +77,11 @@ async.waterfall( [

r.get( path )
.set( authHeader, token )
.set( "Authorization", "Bearer "+token )
.expect( 200 )
.expect( authHeader, successRe )
.expect( userHeader, tokenlib.cipher(userInfo), cb );
.expect(function (res) {
if (!checkUserInfo(res)) {
throw new Error("unmatched userInfo "+tokenlib.cipher(userInfo));
}
})
.end(cb);
},

@@ -78,12 +91,14 @@ function ( res, cb ) {

r.get( path )
.set( authHeader, match[ 1 ] )
.set( "Authorization", "Bearer "+match[ 2 ] )
.expect( 200 )
.expect( authHeader, successRe )
.expect( userHeader, tokenlib.cipher(userInfo), cb );
.expect(function (res) {
if (!checkUserInfo(res)) {
throw new Error("unmatched userInfo "+tokenlib.cipher(userInfo));
}
})
.end(cb);
},
function ( res, cb ) {
var match = res.headers[ authHeader ].match( successRe ),
decipherToken = /(([^:]*):([^:]*):([^:]*))$/;
match = tokenlib.decipher( match[ 1 ] ) .match( decipherToken );
var match = res.headers[ authHeader ].match( successRe );
if ( match[ 3 ] === user ) {

@@ -128,7 +143,4 @@ cb();

} );
after(function(){
app.close();
});
alltests();
});
} );
/*jslint node:true, unused:vars */
/*global before,it,describe,after */
/*global before,it,describe */
var express = require( 'express' ), restify = require('restify'),
jwt = require('jsonwebtoken'),
app,

@@ -13,4 +14,6 @@ async = require( 'async' ),

r,
now = function () {
return Math.floor(Date.now()/1000);
},
authHeader = "X-CS-Auth".toLowerCase(),
userHeader = "X-CS-User".toLowerCase(),
userInfo = JSON.stringify( {

@@ -23,35 +26,58 @@ name: "john",

} ),
successRe = /^success ((\S+)\s(\S+)\s(\S+))$/,
checkUserInfo = function (res) {
var match = res.headers[ authHeader ].match( successRe ),
decoded = jwt.decode(match[2]);
// take JWT (match[2]), split on '.', base64 decode 2nd part (claims),
// check that the 'cs-user' claim is identical to userInfo
return decoded["cs-user"] === userInfo;
},
path = "/public",
alltests = function () {
it( 'should reject invalid token', function ( done ) {
r.get( path ).set( authHeader, "blahblah" ).expect( 200 ).expect( authHeader, "error=invalidtoken", done );
r.get( path ).set( "Authorization", "Bearer blahblah" ).expect( 401 ).expect( authHeader, "error invalidtoken", done );
} );
it( 'should reject expired token', function ( done ) {
var token = tokenlib.generate( "john", "1234", new Date().getTime() - ( 24 * 60 * 60 * 1000 ) );
r.get( path ).set( authHeader, token ).expect( 200 ).expect( authHeader, "error=invalidtoken", done );
var token = tokenlib.generate( "john", "1234", now() - ( 24 * 60 * 60 ) );
r.get( path ).set( "Authorization", "Bearer "+token ).expect( 401 ).expect( authHeader, "error invalidtoken", done );
} );
it( 'should accept a valid token', function ( done ) {
var token = tokenlib.generate( "john", "1234", new Date().getTime() + 15 * 60 * 1000 ),
re = /^success=/;
r.get( path ).set( authHeader, token ).expect( 200 ).expect( authHeader, re, done );
var token = tokenlib.generate( "john", "1234", now() + 15 * 60 );
r.get( path ).set( "Authorization", "Bearer "+token ).expect( 200 ).expect( authHeader, successRe, done );
} );
it( 'should accept a valid token with user and date', function ( done ) {
var user = "john",
expiry = new Date().getTime() + 15 * 60 * 1000,
token = [ tokenlib.generate( user, "1234", expiry ), user, expiry ].join( ":" ),
re = /^success=/;
r.get( path ).set( authHeader, token ).expect( 200 ).expect( authHeader, re, done );
expiry = now() + 15 * 60,
token = tokenlib.generate( user, "1234", expiry );
r.get( path ).set( "Authorization", "Bearer "+token ).expect( 200 ).expect( authHeader, successRe, done );
} );
it( 'should allow to reuse a token', function ( done ) {
var user = "john",
token = tokenlib.generate( user, "1234", new Date().getTime() + 15 * 60 * 1000 ),
successRe = /^success=(([^:]*):([^:]*):([^:]*))$/;
token = tokenlib.generate( user, "1234", now() + 15 * 60 );
async.waterfall( [
function ( cb ) {
r.get( path ).set( authHeader, token ).expect( 200 ).expect( authHeader, successRe ).expect( userHeader, userInfo, cb );
r.get( path )
.set( "Authorization", "Bearer "+token )
.expect( 200 )
.expect( authHeader, successRe )
.expect(function (res) {
if (!checkUserInfo(res)) {
throw new Error("unmatched userInfo "+tokenlib.cipher(userInfo));
}
})
.end(cb);
},
function ( res, cb ) {
var match = res.headers[ authHeader ].match( successRe );
r.get( path ).set( authHeader, match[ 1 ] ).expect( 200 ).expect( authHeader, successRe ).expect( userHeader, userInfo, cb );
r.get( path )
.set( "Authorization", "Bearer "+match[ 2 ] )
.expect( 200 )
.expect( authHeader, successRe )
.expect(function (res) {
if (!checkUserInfo(res)) {
throw new Error("unmatched userInfo "+tokenlib.cipher(userInfo));
}
})
.end(cb);
},

@@ -66,3 +92,3 @@ function ( res, cb ) {

}
], done );
], done );
} );

@@ -90,2 +116,5 @@ };

before( function () {
// we need to handle the mess that restify creates
// we create a prototype chain for http.IncomingMessage, so when restify changes it, it changes the new middle layer,
// which we can restore
cansec = cs.init();

@@ -100,7 +129,4 @@ app = restify.createServer();

} );
after(function(){
app.close();
});
alltests();
});
} );

@@ -0,5 +1,5 @@

/*globals describe, it */
var constants = require('../lib/constants'),
assert = require('assert'),
AUTHHEADER = 'X-CS-Auth',
USERHEADER = 'X-CS-User';
AUTHHEADER = 'X-CS-Auth';

@@ -11,3 +11,2 @@ describe ("constants", function(){

assert.equal(c.header.AUTH, AUTHHEADER, "header.AUTH should be "+AUTHHEADER + " but found " +c.header.AUTH);
assert.equal(c.header.USER, USERHEADER, "header.USER should be "+USERHEADER + " but found " +c.header.USER);
});

@@ -23,3 +22,2 @@

assert.equal(c.header.AUTH, AUTHHEADER, "header.AUTH should be "+AUTHHEADER + " but found " +c.header.AUTH);
assert.equal(c.header.USER, USERHEADER, "header.USER should be "+USERHEADER + " but found " +c.header.USER);
});

@@ -29,4 +27,3 @@

var headers = {
authHeader: "test-auth-header",
userHeader: "test-user-header"
authHeader: "test-auth-header"
};

@@ -38,4 +35,3 @@

assert.equal(c.header.AUTH, headers.authHeader, "header.AUTH should be " + headers.authHeader + " but found " +c.header.AUTH);
assert.equal(c.header.USER, headers.userHeader, "header.USER should be " + headers.userHeader + " but found " +c.header.USER);
});
});
/*jslint node:true, nomen:true, unused:vars */
/*global before,it,describe,after */
/*global before,it,describe */
var express = require( 'express' ), restify = require('restify'),
jwt = require('jsonwebtoken'),
app,

@@ -13,4 +14,3 @@ cs = require( './resources/cs' ),

authHeader = "X-CS-Auth".toLowerCase(),
userHeader = "X-CS-User".toLowerCase(),
userInfo = JSON.stringify( {
userInfo = JSON.stringify( {
name: "john",

@@ -22,5 +22,10 @@ pass: "1234",

} ),
successRe = /^success=(([^:]*):([^:]*):([^:]*))$/,
successRe = /^success ((\S+)\s(\S+)\s(\S+))$/,
user = "john",
pass = "1234",
checkUserInfo = function (res) {
var match = res.headers[ authHeader ].match( successRe ),
decoded = jwt.decode(match[2]);
return decoded["cs-user"] === userInfo;
},
alltests = function () {

@@ -34,3 +39,7 @@ it( 'should work with a local cookie', function ( done ) {

var cookie = res.headers[ "set-cookie" ][ 0 ];
r.get( path ).set( "cookie", cookie ).expect( 200 ).expect( authHeader, successRe ).expect( userHeader, userInfo, cb );
r.get( path ).set( "cookie", cookie ).expect( 200 ).expect( authHeader, successRe ).expect(function (res) {
if (!checkUserInfo(res)) {
cb("Bad user info in JWT");
}
}).end(cb);
}

@@ -51,6 +60,8 @@ ], done );

cb("Missing user in authHeader");
} else if (!checkUserInfo(res)) {
cb("Bad user info in JWT");
} else {
cookie = cookie.split( ";" )[ 0 ];
r.get( path ).set( "cookie", cookie ).expect( 200 ).expect( authHeader, successRe ).expect( userHeader, userInfo, cb );
}
cookie = cookie.split( ";" )[ 0 ];
r.get( path ).set( "cookie", cookie ).expect( 200 ).expect( authHeader, successRe, cb );
}
},

@@ -61,6 +72,8 @@ function ( res, cb ) {

cb("Missing user in header");
} else if (!checkUserInfo(res)) {
cb("Bad user info in JWT");
} else {
cb();
cb();
}
}
}
], done );

@@ -98,5 +111,2 @@ } );

});
after(function(){
app.close();
});
// restify does not have native sessions

@@ -103,0 +113,0 @@ //alltests();

@@ -1,3 +0,3 @@

/*global debugger,before,after */
/*jslint debug:true, node:true */
/*global before */
/*jslint debug:true */

@@ -4,0 +4,0 @@ // call the debugger in case we are in debug mode

@@ -1,3 +0,3 @@

/*jslint node:true, unused:vars */
/*global before,it,describe,after */
/*jslint unused:vars */
/*global before,it,describe */
var express = require( 'express' ),

@@ -27,6 +27,6 @@ restify = require( 'restify'),

it( 'should have error header for bad credentials', function ( done ) {
r.get( path ).auth( "john", "ABCD" ).expect( 401 ).expect( authHeader, "error=invalidpass", done );
r.get( path ).auth( "john", "ABCD" ).expect( 401 ).expect( authHeader, "error invalidpass", done );
} );
it( 'should have correct header for good credentials', function ( done ) {
var re = /^success=/;
var re = /^success /;
r.get( path ).auth( "john", "1234" ).expect( 200 ).expect( authHeader, re, done );

@@ -66,7 +66,4 @@ } );

});
after(function(){
app.close();
});
alltests();
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc