103 lines
3.9 KiB
JavaScript
103 lines
3.9 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
passwordAuth,
|
|
tokenAuth,
|
|
|
|
authorize,
|
|
authorizeOperator,
|
|
};
|
|
|
|
const accesscontrol = require('../accesscontrol.js'),
|
|
apps = require('../apps.js'),
|
|
assert = require('assert'),
|
|
BoxError = require('../boxerror.js'),
|
|
externalLdap = require('../externalldap.js'),
|
|
HttpError = require('connect-lastmile').HttpError,
|
|
safe = require('safetydance'),
|
|
speakeasy = require('speakeasy'),
|
|
users = require('../users.js');
|
|
|
|
async function passwordAuth(req, res, next) {
|
|
assert.strictEqual(typeof req.body, 'object');
|
|
|
|
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'));
|
|
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a string' ));
|
|
|
|
const { username, password, totpToken } = req.body;
|
|
|
|
const verifyFunc = username.indexOf('@') === -1 ? users.verifyWithUsername : users.verifyWithEmail;
|
|
|
|
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()));
|
|
if (error) return next(new HttpError(401, 'Unauthorized'));
|
|
[error] = await safe(externalLdap.verifyPassword(user, password));
|
|
if (error) return next(new HttpError(401, 'Unauthorized'));
|
|
}
|
|
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'));
|
|
|
|
if (!user.ghost && !user.appPassword && user.twoFactorAuthenticationEnabled) {
|
|
if (!totpToken) return next(new HttpError(401, 'A totpToken must be provided'));
|
|
|
|
const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 });
|
|
if (!verified) return next(new HttpError(401, 'Invalid totpToken'));
|
|
}
|
|
|
|
req.user = user;
|
|
|
|
next();
|
|
}
|
|
|
|
async function tokenAuth(req, res, next) {
|
|
let token;
|
|
|
|
// 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) {
|
|
const parts = req.headers.authorization.split(' ');
|
|
if (parts.length == 2) {
|
|
const [scheme, credentials] = parts;
|
|
|
|
if (/^Bearer$/i.test(scheme)) token = credentials;
|
|
}
|
|
}
|
|
|
|
if (!token) return next(new HttpError(401, 'Token required'));
|
|
|
|
const [error, user] = await safe(accesscontrol.verifyToken(token));
|
|
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, error.message));
|
|
if (error) return next(new HttpError(500, error.message));
|
|
|
|
req.access_token = token; // used in logout route
|
|
req.user = user;
|
|
|
|
next();
|
|
}
|
|
|
|
function authorize(requiredRole) {
|
|
assert.strictEqual(typeof requiredRole, 'string');
|
|
|
|
return function (req, res, next) {
|
|
assert.strictEqual(typeof req.user, 'object');
|
|
|
|
if (users.compareRoles(req.user.role, requiredRole) < 0) return next(new HttpError(403, `role '${requiredRole}' is required but user has only '${req.user.role}'`));
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
async function authorizeOperator(req, res, next) {
|
|
assert.strictEqual(typeof req.params.id, 'string');
|
|
assert.strictEqual(typeof req.user, 'object');
|
|
assert.strictEqual(typeof req.app, 'object');
|
|
|
|
if (apps.isOperator(req.app, req.user)) return next();
|
|
|
|
return next(new HttpError(403, 'user is not an operator'));
|
|
}
|