98 lines
3.7 KiB
JavaScript
98 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
passwordAuth,
|
|
tokenAuth,
|
|
|
|
authorize,
|
|
authorizeOperator,
|
|
};
|
|
|
|
const apps = require('../apps.js'),
|
|
tokens = require('../tokens.js'),
|
|
assert = require('assert'),
|
|
BoxError = require('../boxerror.js'),
|
|
HttpError = require('connect-lastmile').HttpError,
|
|
safe = require('safetydance'),
|
|
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, { totpToken, skipTotpCheck: false }));
|
|
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, error.message));
|
|
if (error && error.reason === BoxError.NOT_FOUND) return next(new HttpError(401, 'Unauthorized'));
|
|
if (error) return next(new HttpError(500, error));
|
|
if (!user) return next(new HttpError(401, 'Unauthorized'));
|
|
|
|
req.user = user;
|
|
|
|
next();
|
|
}
|
|
|
|
async function tokenAuth(req, res, next) {
|
|
let accessToken;
|
|
|
|
// this determines the priority
|
|
if (req.body && req.body.access_token) accessToken = req.body.access_token;
|
|
if (req.query && req.query.access_token) accessToken = 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)) accessToken = credentials;
|
|
}
|
|
}
|
|
|
|
if (!accessToken) return next(new HttpError(401, 'Token required'));
|
|
|
|
const token = await tokens.getByAccessToken(accessToken);
|
|
if (!token) return next(new HttpError(401, 'No such token'));
|
|
|
|
const user = await users.get(token.identifier);
|
|
if (!user) return next(new HttpError(401,'User not found'));
|
|
if (!user.active) return next(new HttpError(401,'User not active'));
|
|
|
|
await safe(tokens.update(token.id, { lastUsedTime: new Date() })); // ignore any error
|
|
|
|
req.token = token;
|
|
req.user = user;
|
|
|
|
next();
|
|
}
|
|
|
|
function authorize(requiredRole) {
|
|
assert.strictEqual(typeof requiredRole, 'string');
|
|
|
|
return function (req, res, next) {
|
|
assert.strictEqual(typeof req.user, 'object');
|
|
assert.strictEqual(typeof req.token, '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}'`));
|
|
if (!tokens.hasScope(req.token, req.method, req.path)) return next(new HttpError(403, 'access token does not have this scope'));
|
|
|
|
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');
|
|
assert.strictEqual(typeof req.token, 'object');
|
|
|
|
if (!tokens.hasScope(req.token, req.method, req.path)) return next(new HttpError(403, 'access token does not have this scope'));
|
|
if (apps.isOperator(req.app, req.user)) return next();
|
|
|
|
return next(new HttpError(403, 'user is not an operator'));
|
|
}
|