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

108 lines
4.2 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
2021-01-06 21:57:23 -08:00
passwordAuth,
tokenAuth,
2021-01-06 21:57:23 -08:00
authorize,
websocketAuth
};
2021-06-04 09:28:40 -07:00
const accesscontrol = require('../accesscontrol.js'),
assert = require('assert'),
2019-10-22 21:16:00 -07:00
BoxError = require('../boxerror.js'),
externalLdap = require('../externalldap.js'),
HttpError = require('connect-lastmile').HttpError,
2021-06-04 09:28:40 -07:00
safe = require('safetydance'),
speakeasy = require('speakeasy'),
users = require('../users.js');
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
async function passwordAuth(req, res, next) {
2020-02-06 14:50:12 +01:00
assert.strictEqual(typeof req.body, 'object');
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if (!req.body.username || typeof req.body.username !== 'string') return next(new HttpError(400, 'A username must be non-empty string'));
if (!req.body.password || typeof req.body.password !== 'string') return next(new HttpError(400, 'A password must be non-empty string'));
2020-12-20 14:41:16 -08:00
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a string' ));
2020-02-06 14:50:12 +01:00
2020-12-20 14:41:16 -08:00
const { username, password, totpToken } = req.body;
2020-02-06 14:50:12 +01:00
2021-07-15 09:50:11 -07:00
const verifyFunc = username.indexOf('@') === -1 ? users.verifyWithUsername : users.verifyWithEmail;
2020-02-06 15:36:14 +01:00
2021-07-15 09:50:11 -07:00
let [error, user] = await safe(verifyFunc(username, password, users.AP_WEBADMIN));
if (error && error.reason === BoxError.NOT_FOUND) {
[error, user] = await safe(externalLdap.maybeCreateUser(username.toLowerCase(), password));
if (error) return next(new HttpError(401, 'Unauthorized'));
[error] = await safe(externalLdap.verifyPassword(user));
if (error) return next(new HttpError(401, 'Unauthorized'));
2020-02-06 15:36:14 +01:00
}
2021-07-15 09:50:11 -07:00
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, 'Unauthorized'));
if (error) return next(new HttpError(500, error));
if (!user) return next(new HttpError(401, 'Unauthorized'));
2020-02-06 15:36:14 +01:00
2021-07-15 09:50:11 -07:00
if (!user.ghost && !user.appPassword && user.twoFactorAuthenticationEnabled) {
if (!totpToken) return next(new HttpError(401, 'A totpToken must be provided'));
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 });
if (!verified) return next(new HttpError(401, 'Invalid totpToken'));
2020-02-06 14:50:12 +01:00
}
2018-06-15 17:31:28 -07:00
2021-07-15 09:50:11 -07:00
req.user = user;
next();
2018-06-15 17:31:28 -07:00
}
2021-06-04 09:28:40 -07:00
async function tokenAuth(req, res, next) {
let token;
2020-02-06 14:50:12 +01:00
// this determines the priority
if (req.body && req.body.access_token) token = req.body.access_token;
if (req.query && req.query.access_token) token = req.query.access_token;
if (req.headers && req.headers.authorization) {
2021-06-04 09:28:40 -07:00
const parts = req.headers.authorization.split(' ');
2020-02-06 14:50:12 +01:00
if (parts.length == 2) {
2021-06-04 09:28:40 -07:00
const [scheme, credentials] = parts;
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if (/^Bearer$/i.test(scheme)) token = credentials;
}
}
2021-06-05 21:26:43 -07:00
if (!token) return next(new HttpError(401, 'Token required'));
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
const [error, user] = await safe(accesscontrol.verifyToken(token));
2021-06-05 21:26:43 -07:00
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, error.message));
2021-06-04 09:28:40 -07:00
if (error) return next(new HttpError(500, error.message));
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
req.access_token = token; // used in logout route
req.user = user;
2020-02-06 14:50:12 +01:00
2021-06-04 09:28:40 -07:00
next();
2018-06-15 17:31:28 -07:00
}
function authorize(requiredRole) {
assert.strictEqual(typeof requiredRole, 'string');
2020-02-06 14:50:12 +01:00
return function (req, res, next) {
2020-02-06 17:29:45 +01:00
assert.strictEqual(typeof req.user, 'object');
2018-06-17 15:25:41 -07:00
if (users.compareRoles(req.user.role, requiredRole) < 0) return next(new HttpError(403, `role '${requiredRole}' is required but user has only '${req.user.role}'`));
2020-02-06 14:50:12 +01:00
next();
};
}
2021-06-04 09:28:40 -07:00
async function websocketAuth(requiredRole, req, res, next) {
2020-02-06 16:44:46 +01:00
assert.strictEqual(typeof requiredRole, 'string');
2021-06-05 21:26:43 -07:00
if (typeof req.query.access_token !== 'string') return next(new HttpError(401, 'access_token must be a string'));
2021-06-04 09:28:40 -07:00
const [error, user] = await safe(accesscontrol.verifyToken(req.query.access_token));
2021-06-05 21:26:43 -07:00
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, error.message));
2021-06-04 09:28:40 -07:00
if (error) return next(new HttpError(500, error.message));
2021-06-04 09:28:40 -07:00
req.user = user;
2021-06-04 09:28:40 -07:00
if (users.compareRoles(req.user.role, requiredRole) < 0) return next(new HttpError(403, `role '${requiredRole}' is required but user has only '${user.role}'`));
2021-06-04 09:28:40 -07:00
next();
}