cansecurity
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -7,3 +7,3 @@ /*global module, require, Buffer, console */ | ||
now = util.now, | ||
publicMethods, validate, | ||
publicMethods, validate, invalidTokenMessage, | ||
errors = require( './errors' ), | ||
@@ -248,3 +248,3 @@ sender = require('./sender'), | ||
var auth = getAuthTokenFromHeaders( req ), ret, token, login; | ||
warn("Try Session with Auth Token for "+req.url); | ||
warn("Try Session with Auth Token for "+req.url); | ||
if ( auth ) { | ||
@@ -255,3 +255,3 @@ warn("Auth Token found"); | ||
token = tokenlib.validate( auth ); | ||
// if succeeded, then we need to validate the user from the DB | ||
@@ -261,3 +261,3 @@ if ( token ) { | ||
warn("Successfully validated "+login+" via auth token"); | ||
if ( validate ) { | ||
@@ -281,4 +281,4 @@ warn("Trying to check user ..."); | ||
warn("Failed to validate "+login+" via auth token"); | ||
endSessionWithErrorMessage( req, res, errors.invalidtoken() ); | ||
sender(res, 401, errors.invalidtoken() ); | ||
endSessionWithErrorMessage( req, res, errors.invalidtoken( invalidTokenMessage ) ); | ||
sender(res, 401, errors.invalidtoken( invalidTokenMessage ) ); | ||
} | ||
@@ -330,2 +330,3 @@ ret = true; | ||
validate = fnOrNull( config.validate ); | ||
invalidTokenMessage = config.invalidTokenMessage || null; | ||
sessionExpiry = ( config.expiry || SESSIONEXPIRY ) * 60 * 1000; | ||
@@ -332,0 +333,0 @@ encryptHeader = ( config.encryptHeader || false ); |
{ | ||
"name": "cansecurity", | ||
"description": "cansecurity is your all-in-one security library for user authentication, authorization and management in node expressjs apps", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"url": "http://github.com/deitch/cansecurity", | ||
@@ -6,0 +6,0 @@ "author": "Avi Deitcher <avi@deitcher.net>", |
139
README.md
@@ -8,6 +8,4 @@ # cansecurity | ||
**As of version 0.7.0, we support both restify and express.** | ||
It's this simple: | ||
It's this simple: | ||
```Javascript | ||
@@ -17,8 +15,8 @@ var express = require('express'), app = express(), cs = require('cansecurity'), cansec = cs.init(/* init params */); | ||
app.user(app.router); | ||
// send200 is a shortcut route to send a 200 response | ||
// open route | ||
app.get("/public",send200); | ||
// only authorized if logged in, or as certain roles, or some combination | ||
@@ -31,3 +29,3 @@ app.get("/secure/loggedin",cansec.restrictToLoggedIn,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(); | ||
@@ -37,3 +35,3 @@ app.get("/secure/param",cansec.restrictToParam("searchParam"),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 | ||
@@ -46,3 +44,3 @@ app.get("/secure/field",cansec.restrictToField("owner",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 | ||
@@ -57,3 +55,3 @@ app.get("/secure/conditionalDirect",cansec.ifParam("private","true").restrictToLoggedIn,send200); | ||
// inside app.js: | ||
// instantiate the user validator | ||
@@ -63,3 +61,3 @@ app.use(cansec.validate); | ||
app.use(cansec.authorizer(pathToAuthConfigFile)); | ||
// inside "pathToAuthConfigFile" | ||
@@ -75,5 +73,5 @@ { | ||
["PUT","/api/user/:user/roles","user.roles.admin === true"] | ||
] | ||
] | ||
} | ||
``` | ||
@@ -85,6 +83,10 @@ | ||
### Authentication | ||
cansecurity will manage your user authentication, including managing stateless sessions. It can use either native express sessions and or its own **stateless** sessions. cansecurity stateless sessions can keep a user logged in automatically across multiple nodejs instances, essentially creating free single-sign-on. | ||
cansecurity will manage your user authentication, including cross-server stateless sessions. It can use either native express sessions and or its own **stateless** sessions. cansecurity stateless sessions can keep a user logged in automatically across multiple nodejs instances, essentially creating free single-sign-on. | ||
The flow looks like this: | ||
![swimlanes](./swimlane.png) | ||
### Authorization | ||
cansecurity will handle authorization in your requests, determining if a user should be allowed to perform a certain request, based on your rules. | ||
cansecurity will handle authorization in your requests, determining if a user should be allowed to perform a certain request, based on your rules. | ||
@@ -112,3 +114,3 @@ You can tell cansecurity to manage your authorization imperatively (via middleware code) or declaratively (using a config file). Whatever works better for you is just fine for cansecurity! | ||
The `initConfig` has six properties: | ||
The `initConfig` has the following properties: | ||
@@ -120,2 +122,3 @@ * `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. | ||
* `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`. | ||
* `invalidTokenMessage`: OPTIONAL. Custom message to send back if an authentication is attempted with an invalid token. | ||
* `debug`: OPTIONAL. Print debug messages about each authentication attempt to the console. It will **not** include the actual password. | ||
@@ -149,3 +152,3 @@ | ||
You can also determine *how* the current user was authorized, credentials (e.g. password) and token, by calling | ||
You can also determine *how* the current user was authorized, credentials (e.g. password) and token, by calling | ||
@@ -180,3 +183,3 @@ ```JavaScript | ||
`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. | ||
`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. | ||
@@ -198,7 +201,7 @@ 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. | ||
### Unauthenticated Errors | ||
When authentication fails, cansecurity will directly return 401 with the message "unauthenticated". | ||
When authentication fails, cansecurity will directly return 401 with the message "unauthenticated". | ||
* 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 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 authentication is required and fails, it will return `401` with the text message `unauthenticated` | ||
* If authentication is **not** required, it will jump to the next middleware | ||
* If authentication is **not** required, it will jump to the next middleware | ||
@@ -229,3 +232,3 @@ 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`. | ||
The `X-CS-Auth` response header contains error responses or success tokens. | ||
The `X-CS-Auth` response header contains error responses or success tokens. | ||
@@ -243,3 +246,3 @@ ##### Success | ||
`token`: a JSON Web token if `success`, or an error message if `error` | ||
`username`: the user's username if `success` | ||
`username`: the username if `success` | ||
`expiry`: when this token will expire, as a JavaScript (Unix) second timestamp, provided by `Math.floor(new Date().getTime()/1000)` | ||
@@ -255,3 +258,3 @@ | ||
* `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. | ||
* `cs-user`: the actual logged in user when authentication by any means was successful. | ||
@@ -290,55 +293,4 @@ | ||
### Example | ||
For a good example, see the test suite in test/test.js, specifically the section beginning cansec.init. It is reproduced below: | ||
See the directory `./example/` | ||
```JavaScript | ||
var express = require('express'), app = express(), cs = require('cansecurity'), cansec, | ||
// static database for testing | ||
user = {name:"john",pass:"1234",age:25}; | ||
cansec = cs.init({ | ||
validate: function(login,password,callback){ | ||
if (user.name !== login) { | ||
// no such user - ERROR | ||
callback(false,null,"invaliduser"); | ||
} else if (password === undefined) { | ||
// never asked to check a password, just send the user - GOOD | ||
callback(true,user,user.name); | ||
} else if (user.pass !== pass) { | ||
// asked to check password, but it didn't match - ERROR | ||
callback(false,null,"invalidpass"); | ||
} else { | ||
// user matches, password matches - GOOD | ||
callback(true,user,user.name); | ||
} | ||
}, | ||
sessionKey: SESSIONKEY | ||
}); | ||
app.configure(function(){ | ||
app.use(express.cookieParser()); | ||
app.use(express.session({secret: "agf67dchkQ!"})); | ||
app.use(cansec.validate); | ||
app.use(function(req,res,next){ | ||
// send a 200 | ||
sendResponse(req,res,200); | ||
}); | ||
}); | ||
app.user(function(err,req,res,next){ | ||
var data; | ||
if (err && err.status) { | ||
// one of ours | ||
data = err.message ? {message: err.message} : null; | ||
sendResponse(req,res,err.status,data); | ||
} else if (err && err.type && err.type === "unexpected_token") { | ||
// malformed data | ||
sendResponse(req,res,{message:err.type},400); | ||
} else { | ||
sendResponse(req,res,500); | ||
} | ||
}); | ||
app.listen(PORT); | ||
``` | ||
## Authorization | ||
@@ -387,3 +339,3 @@ Authorization is the process of checking if a user is **allowed** to perform a certain action (in our case, execute a certain route), assuming they have already been authenticated (or not). | ||
* params: OPTIONAL. Names of params passed as part of the expressjs route, and retrievable as this.params[param]. These params are used as part in some of the restrictTo* authorization middleware. There is currently one field: | ||
* * params.id: Param in which the user ID is normally stored, if none is provided, then "user" is used. For example, if params.id === "foo", then the route should have /user/:foo. | ||
* * params.id: Param in which the user ID is normally stored, if none is provided, then "user" is used. For example, if params.id === "foo", then the route should have /user/:foo. | ||
@@ -397,3 +349,3 @@ Initialization returns the object that has the restrictTo* middleware. | ||
// execute routeHandler() if user is logged in, else send 401 | ||
app.get("/some/route/:user",cansec.restrictToLoggedIn,routeHandler); | ||
app.get("/some/route/:user",cansec.restrictToLoggedIn,routeHandler); | ||
// execute routeHandler if req.param("user") === user[fields.id], where 'user' is as returned by validate(), else send 401 | ||
@@ -411,7 +363,7 @@ app.get("/my/data/:user",cansec.restrictToSelf,routeHandler); | ||
#### Unauthorized Errors | ||
cansecurity authorization will directly return a `403` and message `unauthorized` if authorization is required, i.e. a restrictTo* middleware is called, **and** fails. | ||
cansecurity authorization will directly return a `403` and message `unauthorized` if authorization is required, i.e. a restrictTo* middleware is called, **and** fails. | ||
Obviously, authentication comes before authorization, and if the user fails to authenticate, you may get a 401 from the authentication section without ever trying authorization. | ||
#### Middleware API | ||
#### Middleware API | ||
The following authorization middleware methods are available. Each one is followed by an example. There are two sections | ||
@@ -479,7 +431,7 @@ | ||
/* | ||
* Will work if the logged in user has a property "userid" (since init() set fields.id to "userid"), and the value of that property matches req.param("searchParam"). | ||
* Will work if the logged in user has a property "userid" (since init() set fields.id to "userid"), and the value of that property matches req.param("searchParam"). | ||
* Useful for using parameters in searches. | ||
*/ | ||
``` | ||
* 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. | ||
@@ -496,3 +448,3 @@ | ||
* Will work if one of the following is true: | ||
* 1) the logged in user has a property "userid" (since init() set fields.id to "userid"), and the value of that property matches req.param("searchParam"), or, in the second example, one of "searchParam" or "addParam". | ||
* 1) the logged in user has a property "userid" (since init() set fields.id to "userid"), and the value of that property matches req.param("searchParam"), or, in the second example, one of "searchParam" or "addParam". | ||
* 2) The logged in user has the role, as one of the array of strings of the property "roles", set to "admin" or "superadmin" (for the first example), or "admin" (for the second example). | ||
@@ -574,3 +526,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. | ||
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. | ||
@@ -602,3 +554,3 @@ In our example, sendDataFn also checks for that parameter. If it is not set, then it sends public data about the employee list; if it is set, it sends public and private data, trusting that cansecurity prevented someone from getting in with ?secret=true unless they are authorized. | ||
app.get("/public/page",send200); | ||
// lots more | ||
// lots more | ||
app.get("*",function(req,res,next){res.send(403);}); | ||
@@ -654,3 +606,3 @@ ``` | ||
["PUT","/api/user/:user/roles","user.roles.admin === true"] | ||
] | ||
] | ||
} | ||
@@ -744,3 +696,3 @@ ``` | ||
1. `req`: the actual express `req` object, normally found on each route whose signature is `function(req,res,next)`. | ||
1. `req`: the actual express `req` object, normally found on each route whose signature is `function(req,res,next)`. | ||
2. `request`: an alias for `req` | ||
@@ -752,3 +704,3 @@ 3. `user`: the user object if you used cansecurity authentication. This is the equivalent of calling `cansec.getUser(req)`. | ||
#### Loading Data | ||
You have the option, but not the requirement, to load data before passing your route through the declarative authorizer. | ||
You have the option, but not the requirement, to load data before passing your route through the declarative authorizer. | ||
@@ -826,3 +778,3 @@ | ||
fn1: function(req,res,next){}, | ||
fn2: function(req,res,next){} | ||
fn2: function(req,res,next){} | ||
}})) | ||
@@ -928,3 +880,3 @@ ``` | ||
#### Changes to version 2.0.0 | ||
2.0.0 is a major release with many breaking changes. | ||
2.0.0 is a major release with many breaking changes. | ||
@@ -994,3 +946,3 @@ ##### JWT instead of multiple headers | ||
#### Changes to version 0.5.0 | ||
These notes apply to anyone using cansecurity *prior* to v0.5.0. These changes may be breaking, so read carefully. | ||
These notes apply to anyone using cansecurity *prior* to v0.5.0. These changes may be breaking, so read carefully. | ||
@@ -1000,3 +952,3 @@ ##### express 3.x required | ||
##### validatePassword and getUser consolidated into | ||
##### validatePassword and getUser consolidated into | ||
In versions of cansecurity prior to 0.5.0, there were two functions passed to `init()`: | ||
@@ -1011,3 +963,3 @@ | ||
IF `validate()` is `undefined`, AND (`validatePassword()` and `getUser()`) are present, THEN cansecurity will use the old API. | ||
IF `validate()` is `undefined`, AND (`validatePassword()` and `getUser()`) are present, THEN cansecurity will use the old API. | ||
@@ -1017,2 +969,1 @@ IF `validate()` is defined, THEN (`validatePassword()` and `getUser()`) will be ignored, whether present or not. | ||
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 */ | ||
var express = require('express'), restify = require('restify'), app, request = require('supertest'), | ||
cansec, cs = require('./resources/cs'), errorHandler = require('./resources/error'), | ||
cansec, cs = require('./resources/cs'), errorHandler = require('./resources/error'), | ||
cookieParser = require('cookie-parser'), | ||
@@ -168,3 +168,3 @@ session = require('express-session'), | ||
app = express(); | ||
app.use(cookieParser()); | ||
app.use(cookieParser()); | ||
app.use(session({secret: "agf67dchkQ!",resave:false,saveUninitialized:false})); | ||
@@ -175,6 +175,6 @@ app.use(cansec.validate); | ||
app.use(errorHandler); | ||
// we just send 200 for all routes, if it passes authorization | ||
app.all('*',send200); | ||
r = request(app); | ||
@@ -188,3 +188,3 @@ }); | ||
app = express(); | ||
app.use(cookieParser()); | ||
app.use(cookieParser()); | ||
app.use(session({secret: "agf67dchkQ!",resave:false,saveUninitialized:false})); | ||
@@ -195,6 +195,6 @@ app.use(cansec.validate); | ||
app.use(errorHandler); | ||
// we just send 200 for all routes, if it passes authorization | ||
app.all('*',send200); | ||
r = request(app); | ||
@@ -208,3 +208,3 @@ }); | ||
app = express(); | ||
app.use(cookieParser()); | ||
app.use(cookieParser()); | ||
app.use(session({secret: "agf67dchkQ!",resave:false,saveUninitialized:false})); | ||
@@ -216,6 +216,6 @@ app.use(cansec.validate); | ||
app.use(errorHandler); | ||
// we just send 200 for all routes, if it passes authorization | ||
app.all('*',send200); | ||
r = request(app); | ||
@@ -244,3 +244,3 @@ }); | ||
app.head(/^.*$/,send200); | ||
r = request(app); | ||
@@ -261,3 +261,3 @@ }); | ||
app.use(cansec.authorizer(declareFile,{format:true})); | ||
// we just send 200 for all routes, if it passes authorization | ||
@@ -269,3 +269,3 @@ app.get(/^.*$/,send200); | ||
app.head(/^.*$/,send200); | ||
r = request(app); | ||
@@ -272,0 +272,0 @@ }); |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
216220
37
2211
936
2