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

150 lines
5.7 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
2020-02-06 14:50:12 +01:00
passwordAuth: passwordAuth,
tokenAuth: tokenAuth,
scope: scope,
2018-09-05 23:03:17 -07:00
websocketAuth: websocketAuth
};
var 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,
2020-02-06 15:36:14 +01:00
users = require('../users.js'),
speakeasy = require('speakeasy');
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
function passwordAuth(req, res, next) {
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'));
const username = req.body.username;
const password = req.body.password;
2020-02-06 15:36:14 +01:00
function check2FA(user) {
assert.strictEqual(typeof user, 'object');
if (!user.ghost && !user.appPassword && user.twoFactorAuthenticationEnabled) {
if (!req.body.totpToken) return next(new HttpError(401, 'A totpToken must be provided'));
let verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken, window: 2 });
if (!verified) return next(new HttpError(401, 'Invalid totpToken'));
}
req.user = user;
next();
}
2020-02-06 14:50:12 +01:00
function createAndVerifyUserIfNotExist(identifier, password) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof password, 'string');
externalLdap.createAndVerifyUserIfNotExist(identifier.toLowerCase(), password, function (error, result) {
if (error && error.reason === BoxError.BAD_STATE) return next(new HttpError(401, 'Unauthorized'));
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(401, 'Unauthorized'));
if (error && error.reason === BoxError.CONFLICT) return next(new HttpError(401, 'Unauthorized'));
if (error && error.reason === BoxError.NOT_FOUND) 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));
2018-06-15 17:31:28 -07:00
2020-02-06 15:36:14 +01:00
check2FA(result);
2018-06-15 17:31:28 -07:00
});
2020-02-06 14:50:12 +01:00
}
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if (username.indexOf('@') === -1) {
users.verifyWithUsername(username, password, users.AP_WEBADMIN, function (error, result) {
if (error && error.reason === BoxError.NOT_FOUND) return createAndVerifyUserIfNotExist(username, password);
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, 'Unauthorized'));
if (error) return next(new HttpError(500, error));
if (!result) return next(new HttpError(401, 'Unauthorized'));
2020-02-06 15:36:14 +01:00
check2FA(result);
2020-02-06 14:50:12 +01:00
});
} else {
users.verifyWithEmail(username, password, users.AP_WEBADMIN, function (error, result) {
if (error && error.reason === BoxError.NOT_FOUND) return createAndVerifyUserIfNotExist(username, password);
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, 'Unauthorized'));
if (error) return next(new HttpError(500, error));
if (!result) return next(new HttpError(401, 'Unauthorized'));
2018-06-15 17:31:28 -07:00
2020-02-06 15:36:14 +01:00
check2FA(result);
2020-02-06 14:50:12 +01:00
});
}
2018-06-15 17:31:28 -07:00
}
2020-02-06 14:50:12 +01:00
function tokenAuth(req, res, next) {
var 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) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0];
var credentials = parts[1];
2018-06-15 17:31:28 -07:00
2020-02-06 14:50:12 +01:00
if (/^Bearer$/i.test(scheme)) token = credentials;
}
}
if (!token) return next(new HttpError(401, 'Unauthorized'));
accesscontrol.validateToken(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;
next();
});
2018-06-15 17:31:28 -07:00
}
// 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(',');
2020-02-06 14:50:12 +01:00
return function (req, res, next) {
assert(req.authInfo && typeof req.authInfo === 'object');
2018-06-17 15:25:41 -07:00
2020-02-06 14:50:12 +01:00
var error = accesscontrol.hasScopes(req.authInfo.authorizedScopes, requiredScopes);
if (error) return next(new HttpError(403, error.message));
2020-02-06 14:50:12 +01:00
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'));
accesscontrol.validateToken(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;
2018-06-17 19:54:05 -07:00
var e = accesscontrol.hasScopes(info.authorizedScopes, requiredScopes);
if (e) return next(new HttpError(403, e.message));
next();
});
}