cansecurity
Advanced tools
Comparing version 3.0.1 to 3.1.0
@@ -301,3 +301,3 @@ /*global module, require, Buffer, console */ | ||
tokenlib.init( config.sessionKey || genRandomString( RANDOM_STRING_LENGTH ), encryptHeader ); | ||
tokenlib.init( {key: config.sessionKey || genRandomString( RANDOM_STRING_LENGTH ), encrypt: encryptHeader, privateKey: config.privateKey, publicKey: config.publicKey } ); | ||
@@ -304,0 +304,0 @@ return publicMethods; |
const crypto = require('crypto'), jwt = require('jsonwebtoken'), now = require('./util').now; | ||
let sessionKey = null, encryptHeader = false, warn = () => {}; | ||
let sessionKey = null, privKey = null, pubKey = null, encryptHeader = false, algorithm = null, | ||
usePublic = false, | ||
warn = () => {}; | ||
module.exports = { | ||
init : (key, encrypt) => { | ||
init : ({key = null, privateKey = null, publicKey = null, encrypt = false} = {}) => { | ||
sessionKey = key; | ||
encryptHeader = encrypt || false; | ||
privKey = privateKey; | ||
pubKey = publicKey; | ||
encryptHeader = encrypt; | ||
// which algorithm do we use? | ||
usePublic = privKey || pubKey ? true : false; | ||
algorithm = usePublic ? "RS256" : "HS256"; | ||
}, | ||
@@ -16,4 +23,3 @@ setWarn : (fn) => { | ||
generate : (name, user, expiry) => { | ||
const token = jwt.sign({sub:name,exp:expiry,"cs-user":user},sessionKey,{algorithm:"HS256"}); | ||
const token = jwt.sign({sub:name,exp:expiry,"cs-user":user},usePublic ? privKey : sessionKey,{algorithm:algorithm}); | ||
return(encryptHeader ? module.exports.cipher(token) : token); | ||
@@ -31,3 +37,3 @@ }, | ||
try { | ||
decoded = jwt.verify(token,sessionKey,{algorithms:"HS256"}); | ||
decoded = jwt.verify(token,usePublic ? pubKey : sessionKey,{algorithms:algorithm}); | ||
expiry = decoded.exp; | ||
@@ -34,0 +40,0 @@ warn(`token expiry ${expiry} now ${t}`); |
{ | ||
"name": "cansecurity", | ||
"description": "cansecurity is your all-in-one security library for user authentication, authorization and management in node expressjs apps", | ||
"version": "3.0.1", | ||
"version": "3.1.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "url": "http://github.com/deitch/cansecurity", |
@@ -108,3 +108,5 @@ # cansecurity | ||
* `sessionExpiry`: OPTIONAL. Integer in minutes how long sessions should last, default is `15`. Used both for expressjs sessions and CS sessions. Setting `sessionExpiry` will **only** affect how long a session is valid **for cansecurity**. It will **not** affect the underlying expressjs session itself. | ||
* `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. | ||
* `sessionKey`: OPTIONAL. String. Secret key shared between nodejs servers to provide single-sign-on. | ||
* `privateKey`: OPTIONAL. String. PEM-encoded RSA private key to use to encrypt the token. | ||
* `publicKey`: OPTIONAL. String. PEM-encoded RSA public key to use to validate the token. | ||
* `validate`: REQUIRED. Function that will get a user by username, and possibly validate their password, asynchronously. For more details, see below. | ||
@@ -116,2 +118,22 @@ * `encryptHeader`: OPTIONAL. With a value of true, the entire JWT is encrypted using `rc4-hmac-md5` algorithm. | ||
##### Token Signing Keys | ||
`cansecurity` supports both shared key token validation and public/private key token validation. Which it uses depends on what parameters you pass: | ||
* `sessionKey`: shared session key using `"HS256"` algorithm. | ||
* `privateKey` / `publicKey`: public/private key using `"RS256"` algorithm. | ||
* none of the above: will automatically generate a shared session key, a random 64-character string. | ||
Because the default, if no option is provided, is to auto-generate a shared session key, you **must** provide _either_ a public/private key pair _or_ a session if you want to share tokens between servers. | ||
The behaviour of `cansecurity` changes based on which parameters you provide: | ||
* No `sessionKey`, `publicKey` or `privateKey`: auto-generate a random shared session key, as described above. | ||
* Only `sessionKey`: sign and validate tokens using provided session key with `"HS256"` algorithm. | ||
* Only `privateKey`: sign tokens using provided private key with `"RS256"` algorithm. **Any attempt to validate tokens will fail.** | ||
* Only `publicKey`: validate tokens using provided public key with `"RS256"` algorithm. **Any attempt to sign tokens will fail.** | ||
* Both `privateKey` and `publicKey`: sign and validate tokens using provided private and public keys with `"RS256"` algorithm. | ||
* `sessionKey` and either `publicKey` or `privateKey`: launch-time error. | ||
Note that encrypted headers do not yet work with private/public keys, as they rely on the same session key to encrypt. | ||
#### Validation | ||
@@ -118,0 +140,0 @@ Validation is straightforward. Once you have set up cansecurity properly, it functions as standard expressjs middleware: |
@@ -33,4 +33,4 @@ /*jslint node:true, nomen:true, unused:vars */ | ||
module.exports = { | ||
init: function () { | ||
return cs.init( { | ||
init: function (param) { | ||
return cs.init( Object.assign({ | ||
validate: function ( login, pass, callback ) { | ||
@@ -66,86 +66,9 @@ var found = null; | ||
} | ||
}, | ||
sessionKey: SESSIONKEY | ||
} ); | ||
} | ||
}, param || {}) ); | ||
}, | ||
initEncrypted: function () { | ||
return cs.init( { | ||
validate: function ( login, pass, callback ) { | ||
var found = null; | ||
// search for our user | ||
_.each( user, function ( val, key ) { | ||
var ret = true; | ||
if ( val.name === login ) { | ||
found = val; | ||
ret = false; | ||
} | ||
return ( ret ); | ||
} ); | ||
if ( !found ) { | ||
callback( false, null, "invaliduser" ); | ||
} else if ( pass === undefined ) { | ||
callback( true, found, found.name, found.pass ); | ||
} else if ( pass === found.pass ) { | ||
callback( true, found, found.name, found.pass ); | ||
} else { | ||
callback( false, null, "invalidpass" ); | ||
} | ||
}, | ||
loader: { | ||
group: function ( req, res, next ) { | ||
req.cansecurity.item = 1; | ||
next(); | ||
} | ||
}, | ||
sessionKey: SESSIONKEY, | ||
encryptHeader: true | ||
} ); | ||
}, | ||
initTokenLib: function ( encrypt ) { | ||
tokenLib.init(SESSIONKEY, encrypt || false); | ||
tokenLib.init({key: SESSIONKEY, encrypt: encrypt || false}); | ||
return tokenLib; | ||
}, | ||
initLegacy: function () { | ||
return cs.init( { | ||
getUser: function ( login, success, failure ) { | ||
var found = null; | ||
// search for our user | ||
_.each( user, function ( val, key ) { | ||
var ret = true; | ||
if ( val.name === login ) { | ||
found = val; | ||
ret = false; | ||
} | ||
return ( ret ); | ||
} ); | ||
if ( found ) { | ||
success( found, found.name, found.pass ); | ||
} else { | ||
failure(); | ||
} | ||
}, | ||
validatePassword: function ( login, pass, cb ) { | ||
var p = null, | ||
message = "invaliduser", | ||
resuser = null; | ||
// search for our user | ||
_.each( user, function ( val, key ) { | ||
var ret = true; | ||
if ( val.name === login ) { | ||
ret = false; | ||
if ( val.pass === pass ) { | ||
message = null; | ||
resuser = val; | ||
p = pass; | ||
} else { | ||
message = "invalidpass"; | ||
} | ||
} | ||
return ( ret ); | ||
} ); | ||
cb( resuser, message, p ); | ||
}, | ||
sessionKey: SESSIONKEY | ||
} ); | ||
} | ||
}; | ||
}; |
@@ -337,3 +337,3 @@ /*jslint node:true, nomen:true, unused:vars */ | ||
app = express(); | ||
app.use(cookieParser()); | ||
app.use(cookieParser()); | ||
app.use(session({secret: "agf67dchkQ!",resave:false,saveUninitialized:false})); | ||
@@ -359,3 +359,1 @@ app.use(cansec.validate); | ||
}); | ||
@@ -110,3 +110,3 @@ /*jslint node:true, unused:vars */ | ||
before( function () { | ||
cansec = cs.initEncrypted(); | ||
cansec = cs.init({encryptHeader: true}); | ||
app = express(); | ||
@@ -128,3 +128,3 @@ app.use( cookieParser() ); | ||
before( function () { | ||
cansec = cs.initEncrypted(); | ||
cansec = cs.init({encryptHeader: true}); | ||
app = restify.createServer(); | ||
@@ -140,2 +140,2 @@ app.use( cansec.validate ); | ||
}); | ||
} ); | ||
} ); |
/*jslint node:true, unused:vars */ | ||
/*global before,it,describe */ | ||
var express = require( 'express' ), restify = require('restify'), | ||
fs = require('fs'), | ||
path = require('path'), | ||
jwt = require('jsonwebtoken'), | ||
@@ -9,2 +11,4 @@ app, | ||
cs = require( './resources/cs' ), | ||
privKey = fs.readFileSync(path.join(__dirname,'resources/rsa-private.pem')), | ||
pubKey = fs.readFileSync(path.join(__dirname,'resources/rsa-public.pem')), | ||
cookieParser = require('cookie-parser'), | ||
@@ -30,7 +34,7 @@ session = require('express-session'), | ||
decoded = jwt.decode(match[2]); | ||
// take JWT (match[2]), split on '.', base64 decode 2nd part (claims), | ||
// 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", | ||
path = "/public", | ||
alltests = function () { | ||
@@ -56,3 +60,3 @@ it( 'should reject invalid token', function ( done ) { | ||
var user = "john", | ||
token = tokenlib.generate( user, "1234", now() + 15 * 60 ); | ||
token = tokenlib.generate( user, "1234", now() + 15 * 60 ); | ||
async.waterfall( [ | ||
@@ -93,14 +97,46 @@ | ||
} | ||
], done ); | ||
], done ); | ||
} ); | ||
}; | ||
describe( 'authtoken', function () { | ||
describe('express', function(){ | ||
describe('sessionKey', function() { | ||
describe('express', function(){ | ||
before( function () { | ||
cansec = cs.init(); | ||
app = express(); | ||
app.use( cookieParser() ); | ||
app.use( session( { | ||
secret: "agf67dchkQ!",resave:false,saveUninitialized:false | ||
} ) ); | ||
app.use( cansec.validate ); | ||
app.get( path, function ( req, res, next ) { | ||
// send a 200 | ||
require('../lib/sender')(res,200); | ||
} ); | ||
r = request( app ); | ||
} ); | ||
alltests(); | ||
}); | ||
describe('restify', function(){ | ||
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(); | ||
app = restify.createServer(); | ||
app.use( cansec.validate ); | ||
app.get( path, function ( req, res, next ) { | ||
// send a 200 | ||
require('../lib/sender')(res,200); | ||
} ); | ||
r = request( app ); | ||
} ); | ||
alltests(); | ||
}); | ||
}); | ||
describe('publicKey', function() { | ||
before( function () { | ||
cansec = cs.init(); | ||
cansec = cs.init({privateKey: privKey, publicKey: pubKey}); | ||
app = express(); | ||
app.use( cookieParser() ); | ||
app.use( session( { | ||
secret: "agf67dchkQ!",resave:false,saveUninitialized:false | ||
} ) ); | ||
app.use( cansec.validate ); | ||
@@ -114,19 +150,22 @@ app.get( path, function ( req, res, next ) { | ||
alltests(); | ||
}); | ||
describe('restify', function(){ | ||
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(); | ||
app = restify.createServer(); | ||
app.use( cansec.validate ); | ||
app.get( path, function ( req, res, next ) { | ||
// send a 200 | ||
require('../lib/sender')(res,200); | ||
describe('mismatched keys', function() { | ||
before( function () { | ||
cansec = cs.init({privateKey: privKey, publicKey: "abcdefg"}); | ||
app = express(); | ||
app.use( cookieParser() ); | ||
app.use( cansec.validate ); | ||
app.get( path, function ( req, res, next ) { | ||
// send a 200 | ||
require('../lib/sender')(res,200); | ||
} ); | ||
r = request( app ); | ||
} ); | ||
r = request( app ); | ||
} ); | ||
alltests(); | ||
it( 'should reject mismatched token', function ( done ) { | ||
var user = "john", | ||
expiry = now() + 15 * 60, | ||
token = tokenlib.generate( user, "1234", expiry ); | ||
r.get( path ).set( "Authorization", "Bearer "+token ).expect( 401 ).expect( authHeader, 'error invalidtoken', done ); | ||
} ); | ||
}); | ||
}); | ||
} ); | ||
} ); |
220042
39
958
2094
4