Files
cloudron-box/src/routes/accesscontrol.js

164 lines
6.3 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
scope: scope,
2018-06-15 17:31:28 -07:00
websocketAuth: websocketAuth,
initialize: initialize,
uninitialize: uninitialize
};
var accesscontrol = require('../accesscontrol.js'),
assert = require('assert'),
2018-06-15 17:31:28 -07:00
BasicStrategy = require('passport-http').BasicStrategy,
BearerStrategy = require('passport-http-bearer').Strategy,
clients = require('../clients.js'),
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
ClientsError = clients.ClientsError,
DatabaseError = require('../databaseerror.js'),
HttpError = require('connect-lastmile').HttpError,
2018-06-15 17:31:28 -07:00
LocalStrategy = require('passport-local').Strategy,
passport = require('passport'),
tokendb = require('../tokendb'),
users = require('../users.js'),
UsersError = users.UsersError;
function initialize(callback) {
assert.strictEqual(typeof callback, 'function');
// serialize user into session
passport.serializeUser(function (user, callback) {
callback(null, user.id);
});
// deserialize user from session
passport.deserializeUser(function(userId, callback) {
users.get(userId, function (error, result) {
if (error) return callback(error);
callback(null, result);
});
});
// used when username/password is sent in request body. used in CLI tool login route
passport.use(new LocalStrategy(function (username, password, callback) {
if (username.indexOf('@') === -1) {
users.verifyWithUsername(username, password, function (error, result) {
if (error && error.reason === UsersError.NOT_FOUND) return callback(null, false);
if (error && error.reason === UsersError.WRONG_PASSWORD) return callback(null, false);
if (error) return callback(error);
if (!result) return callback(null, false);
callback(null, result);
});
} else {
users.verifyWithEmail(username, password, function (error, result) {
if (error && error.reason === UsersError.NOT_FOUND) return callback(null, false);
if (error && error.reason === UsersError.WRONG_PASSWORD) return callback(null, false);
if (error) return callback(error);
if (!result) return callback(null, false);
callback(null, result);
});
}
}));
// Used to authenticate a OAuth2 client which uses clientId and clientSecret in the Authorization header
passport.use(new BasicStrategy(function (clientId, clientSecret, callback) {
clients.get(clientId, function (error, client) {
if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
if (client.clientSecret !== clientSecret) return callback(null, false);
callback(null, client);
});
}));
// Used to authenticate a OAuth2 client which uses clientId and clientSecret in the request body (client_id, client_secret)
passport.use(new ClientPasswordStrategy(function (clientId, clientSecret, callback) {
clients.get(clientId, function(error, client) {
if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false);
if (error) { return callback(error); }
if (client.clientSecret !== clientSecret) { return callback(null, false); }
callback(null, client);
});
}));
// used for "Authorization: Bearer token" or access_token query param authentication
passport.use(new BearerStrategy(accessTokenAuth));
callback(null);
}
function uninitialize(callback) {
assert.strictEqual(typeof callback, 'function');
callback(null);
}
function accessTokenAuth(accessToken, callback) {
assert.strictEqual(typeof accessToken, 'string');
assert.strictEqual(typeof callback, 'function');
tokendb.get(accessToken, function (error, token) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
users.get(token.identifier, function (error, user) {
if (error && error.reason === UsersError.NOT_FOUND) return callback(null, false);
if (error) return callback(error);
// scopes here can define what capabilities that token carries
// passport put the 'info' object into req.authInfo, where we can further validate the scopes
const userScope = user.admin ? '*' : 'profile';
var scope = accesscontrol.intersectScope(userScope, token.scope);
// these clients do not require password checks unlike UI
const skipPasswordVerification = token.clientId === 'cid-sdk' || token.clientId === 'cid-cli';
var info = { scope: scope, skipPasswordVerification: skipPasswordVerification };
2018-06-15 17:31:28 -07:00
callback(null, user, info);
});
});
}
// The scope middleware provides an auth middleware for routes.
//
// It is used for API routes, which are authenticated using accesstokens.
// Those accesstokens carry OAuth scopes and the middleware takes the required
// scope as an argument and will verify the accesstoken against it.
//
// See server.js:
// var profileScope = routes.oauth2.scope('profile');
//
2018-06-14 16:32:24 -07:00
function scope(requiredScope) {
assert.strictEqual(typeof requiredScope, 'string');
2018-06-14 16:32:24 -07:00
var requiredScopes = requiredScope.split(',');
return [
passport.authenticate(['bearer'], { session: false }),
function (req, res, next) {
2018-06-14 16:32:24 -07:00
var error = accesscontrol.hasScopes(req.authInfo || null, requiredScopes);
if (error) return next(new HttpError(403, error.message));
next();
}
];
}
2018-06-14 16:32:24 -07:00
function websocketAuth(requiredScopes, req, res, next) {
assert(Array.isArray(requiredScopes));
if (typeof req.query.access_token !== 'string') return next(new HttpError(401, 'Unauthorized'));
2018-06-15 17:31:28 -07:00
accessTokenAuth(req.query.access_token, function (error, user, info) {
if (error) return next(new HttpError(500, error.message));
if (!user) return next(new HttpError(401, 'Unauthorized'));
req.user = user;
req.authInfo = info;
2018-06-14 16:32:24 -07:00
var e = accesscontrol.hasScopes(req.authInfo, requiredScopes);
if (e) return next(new HttpError(403, e.message));
next();
});
}