access-manager
© WeeHorse 2018, MIT license
Authentication, Sessions and ACL
Access Manager is a one-stop solution for implementing authenticated and anonymous sessions with user handling and whitelisted ACL. Keeps the same session regardless of authenticated state. Attaches itself to an express app as a middleware.
Install
$ npm install access-manager
Install ACL data
If you want some example data or wish to import your ACL from file, use the --import-acl switch when you start your app with access manager (for the first time). Note that your app will shut down once the import is done.
Use example data: (see below)
$ node app --import-acl
Or provide your own file:
$ node app --import-acl=file.json
The ACL data installs into the acl collection. Obviously you're free to populate the acl collection anyway you see fit.
The example ACL data:
[
{"path":"/rest/admin*", "roles":[ {"role": "admin", "methods": ["ALL"]}, {"role": "super", "methods": ["ALL"]} ]},
{"path":"/rest/login", "roles":[ {"role": "anonymous", "methods": ["POST"]} ]},
{"path":"/rest/logout", "roles":[ {"role": "user", "methods": ["GET","POST"]} ]},
{"path":"/rest/news*", "roles":[ {"role": "*", "methods": ["GET"]} ]},
{"path":"/rest/messages*", "roles":[ {"role": "user", "methods": ["GET","POST","DELETE"]} ]},
{"path":"/rest/user", "roles":[ {"role": "user", "methods": ["GET"]} ]},
{"path":"/rest/register", "roles":[ {"role": "anonymous", "methods": ["POST"]}, {"role": "super", "methods": ["POST"]} ]}
]
It works like this: Only the paths detailed above are valid paths together with the correct user role and method, all other paths will be blocked with 403 Forbidden. You may end any path with a wildcard *, letting traffic through on all subpaths.
Examples of access-manager usage:
Typical init with basic dependencies
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/some_database');
app.use(express.static('../client/'));
const AccessManager = require('access-manager');
const accessManager = new AccessManager({
mongoose: mongoose,
expressApp: app
});
Configuration
You can optionally add your own schemas (Schema Objects) for users, sessions and acl, at the properties: userSchema, sessionSchema, aclSchema
Some properties in the schemas are required by the access-manager. Those details can be found at the bottom of this document.
You will propably want to supply your own userSchema, an example of doing that:
const accessManager = new AccessManager({
mongoose: mongoose,
expressApp: app,
userSchema: {
firstName: {type: String, required:true},
lastName: {type: String, required:true},
email: {type: String, required:true, unique:true},
password: {type: String, required:true},
roles: [String]
}
The models access manager uses are then avaliable from access manager:
const User = accessManager.models.user;
Now access manager will do its work seamlessly in the background,
but we need a user, so here's a registration route:
The example ACL will only allow anonymous users and super users create accounts
app.post('/rest/register', async (req, res)=>{
req.body.password = await bcrypt.hash(req.body.password, saltRounds);
let user = await new User(req.body);
await user.save();
res.json({msg:'Registered'});
});
And login:
The example ACL will prevent this route if you are already logged in
app.post('/rest/login', async (req, res)=>{
let user = await User.findOne({email: req.body.email});
if(user && await bcrypt.compare(req.body.password, user.password)){
req.session.user = user._id;
req.session.loggedIn = true;
await req.session.save();
res.json({msg:'Logged in'});
}else{
res.json({msg:'Failed login'});
}
});
To logout:
The example ACL will prevent this route if you are already logged out
app.all('/rest/logout', async (req, res)=>{
req.user = {};
req.session.loggedIn = false;
await req.session.save();
res.json({msg:'Logged out'});
});
A restricted example route:
The example ACL will only allow this route on logged in users
app.get('/rest/messages', async (req, res)=>{
res.json({msg:'Here are your messages'});
});
The current user route:
The example ACL will only allow this route on logged in users
app.get('/rest/user', (req, res)=>{
let response;
if(req.user._id){
response = req.user;
response.password = '******';
}else{
response = {message: 'Not logged in'};
}
res.json(response);
});
Wildcard route (that takes any method) so we can test that the ACL blocks anything not allowed):
app.all('*', (req, res)=>{
res.json({params: req.params, body: req.body});
});
Don't forget...
app.listen(3000,()=>{
console.log("Remember Mystery science theatre 3000!");
});
Access manager schemas requirements
The schemas used in access manager must contain the properties detailed below. If you don't supply your own schemas these are the defaults:
The mininum required userSchema:
{
email: {type: String, required:true, unique:true},
password: {type: String, required:true},
roles: [String]
}
The mininum required sessionSchema:
{
loggedIn: {type:Boolean, default:false},
user: { type: this.mongoose.Schema.Types.ObjectId, ref: 'User' }
}
The mininum required aclSchema:
{
path: {type: String, unique: true},
roles: [
new this.mongoose.Schema({
role: String,
methods: [{type: String, enum: ['GET', 'POST', 'PUT', 'DELETE', 'ALL']}]
})
]
}