mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-27 06:29:05 +08:00
use roles for totp/u2f setup
This commit is contained in:
parent
5fffe6fb79
commit
5ebda12bde
2 changed files with 442 additions and 417 deletions
|
|
@ -2,10 +2,19 @@
|
||||||
|
|
||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const ObjectID = require('mongodb').ObjectID;
|
const ObjectID = require('mongodb').ObjectID;
|
||||||
|
const tools = require('../../tools');
|
||||||
|
const roles = require('../../roles');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
module.exports = (db, server, userHandler) => {
|
module.exports = (db, server, userHandler) => {
|
||||||
// Create TOTP seed and request a QR code
|
// Create TOTP seed and request a QR code
|
||||||
|
|
||||||
|
const setupTotp = util.promisify(userHandler.setupTotp.bind(userHandler));
|
||||||
|
const enableTotp = util.promisify(userHandler.enableTotp.bind(userHandler));
|
||||||
|
const disableTotp = util.promisify(userHandler.disableTotp.bind(userHandler));
|
||||||
|
const checkTotp = util.promisify(userHandler.checkTotp.bind(userHandler));
|
||||||
|
const disable2fa = util.promisify(userHandler.disable2fa.bind(userHandler));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /users/:user/2fa/totp/setup Generate TOTP seed
|
* @api {post} /users/:user/2fa/totp/setup Generate TOTP seed
|
||||||
* @apiName SetupTotp2FA
|
* @apiName SetupTotp2FA
|
||||||
|
|
@ -53,62 +62,63 @@ module.exports = (db, server, userHandler) => {
|
||||||
* "code": "UserNotFound"
|
* "code": "UserNotFound"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
server.post('/users/:user/2fa/totp/setup', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/totp/setup',
|
||||||
const schema = Joi.object().keys({
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
user: Joi.string()
|
res.charSet('utf-8');
|
||||||
.hex()
|
const schema = Joi.object().keys({
|
||||||
.lowercase()
|
user: Joi.string()
|
||||||
.length(24)
|
.hex()
|
||||||
.required(),
|
.lowercase()
|
||||||
label: Joi.string()
|
.length(24)
|
||||||
.empty('')
|
.required(),
|
||||||
.trim()
|
label: Joi.string()
|
||||||
.max(255),
|
.empty('')
|
||||||
issuer: Joi.string()
|
.trim()
|
||||||
.trim()
|
.max(255),
|
||||||
.max(255)
|
issuer: Joi.string()
|
||||||
.required(),
|
.trim()
|
||||||
sess: Joi.string().max(255),
|
.max(255)
|
||||||
ip: Joi.string().ip({
|
.required(),
|
||||||
version: ['ipv4', 'ipv6'],
|
sess: Joi.string().max(255),
|
||||||
cidr: 'forbidden'
|
ip: Joi.string().ip({
|
||||||
})
|
version: ['ipv4', 'ipv6'],
|
||||||
});
|
cidr: 'forbidden'
|
||||||
|
})
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
const result = Joi.validate(req.params, schema, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
userHandler.setupTotp(user, result.value, (err, result) => {
|
if (result.error) {
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let totp = await setupTotp(user, result.value);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
seed: result.secret,
|
seed: totp.secret,
|
||||||
qrcode: result.dataUrl
|
qrcode: totp.dataUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /users/:user/2fa/totp/enable Enable TOTP seed
|
* @api {post} /users/:user/2fa/totp/enable Enable TOTP seed
|
||||||
|
|
@ -151,50 +161,51 @@ module.exports = (db, server, userHandler) => {
|
||||||
* "code": "UserNotFound"
|
* "code": "UserNotFound"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
server.post('/users/:user/2fa/totp/enable', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/totp/enable',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
token: Joi.string()
|
token: Joi.string()
|
||||||
.length(6)
|
.length(6)
|
||||||
.required(),
|
.required(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
const result = Joi.validate(req.params, schema, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
userHandler.enableTotp(user, result.value, (err, result) => {
|
if (result.error) {
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let totp = await enableTotp(user, result.value);
|
||||||
|
|
||||||
|
if (!totp) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Invalid authentication token',
|
error: 'Invalid authentication token',
|
||||||
code: 'InvalidToken'
|
code: 'InvalidToken'
|
||||||
|
|
@ -207,8 +218,8 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {delete} /users/:user/2fa/totp Disable TOTP auth
|
* @api {delete} /users/:user/2fa/totp Disable TOTP auth
|
||||||
|
|
@ -245,55 +256,56 @@ module.exports = (db, server, userHandler) => {
|
||||||
* "code": "UserNotFound"
|
* "code": "UserNotFound"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
server.del('/users/:user/2fa/totp', (req, res, next) => {
|
server.del(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/totp',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
req.query.user = req.params.user;
|
|
||||||
|
|
||||||
const result = Joi.validate(req.query, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
req.query.user = req.params.user;
|
||||||
|
|
||||||
userHandler.disableTotp(user, result.value, (err, success) => {
|
const result = Joi.validate(req.query, schema, {
|
||||||
if (err) {
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let success = await disableTotp(user, result.value);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success
|
success
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /users/:user/2fa/totp/check Validate TOTP Token
|
* @api {post} /users/:user/2fa/totp/check Validate TOTP Token
|
||||||
|
|
@ -336,50 +348,51 @@ module.exports = (db, server, userHandler) => {
|
||||||
* "code": "InvalidToken"
|
* "code": "InvalidToken"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
server.post('/users/:user/2fa/totp/check', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/totp/check',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
token: Joi.string()
|
token: Joi.string()
|
||||||
.length(6)
|
.length(6)
|
||||||
.required(),
|
.required(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
const result = Joi.validate(req.params, schema, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
userHandler.checkTotp(user, result.value, (err, result) => {
|
if (result.error) {
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).readOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).readAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let totp = await checkTotp(user, result.value);
|
||||||
|
|
||||||
|
if (!totp) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Failed to validate TOTP',
|
error: 'Failed to validate TOTP',
|
||||||
code: 'InvalidToken'
|
code: 'InvalidToken'
|
||||||
|
|
@ -392,8 +405,8 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {delete} /users/:user/2fa Disable 2FA
|
* @api {delete} /users/:user/2fa Disable 2FA
|
||||||
|
|
@ -430,53 +443,54 @@ module.exports = (db, server, userHandler) => {
|
||||||
* "code": "UserNotFound"
|
* "code": "UserNotFound"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
server.del('/users/:user/2fa', (req, res, next) => {
|
server.del(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
req.query.user = req.params.user;
|
|
||||||
|
|
||||||
const result = Joi.validate(req.query, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
req.query.user = req.params.user;
|
||||||
|
|
||||||
userHandler.disable2fa(user, result.value, (err, success) => {
|
const result = Joi.validate(req.query, schema, {
|
||||||
if (err) {
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let success = await disable2fa(user, result.value);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success
|
success
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const ObjectID = require('mongodb').ObjectID;
|
const ObjectID = require('mongodb').ObjectID;
|
||||||
|
const tools = require('../../tools');
|
||||||
|
const roles = require('../../roles');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
const U2F_ERROR_CODES = {
|
const U2F_ERROR_CODES = {
|
||||||
OK: 0,
|
OK: 0,
|
||||||
|
|
@ -21,129 +24,137 @@ const U2F_ERROR_MESSAGES = new Map([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
module.exports = (db, server, userHandler) => {
|
module.exports = (db, server, userHandler) => {
|
||||||
|
const setupU2f = util.promisify(userHandler.setupU2f.bind(userHandler));
|
||||||
|
const enableU2f = util.promisify(userHandler.enableU2f.bind(userHandler));
|
||||||
|
const disableU2f = util.promisify(userHandler.disableU2f.bind(userHandler));
|
||||||
|
const startU2f = util.promisify(userHandler.startU2f.bind(userHandler));
|
||||||
|
const checkU2f = util.promisify(userHandler.checkU2f.bind(userHandler));
|
||||||
|
|
||||||
// Create U2F keys
|
// Create U2F keys
|
||||||
server.post('/users/:user/2fa/u2f/setup', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/u2f/setup',
|
||||||
const schema = Joi.object().keys({
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
user: Joi.string()
|
res.charSet('utf-8');
|
||||||
.hex()
|
const schema = Joi.object().keys({
|
||||||
.lowercase()
|
user: Joi.string()
|
||||||
.length(24)
|
.hex()
|
||||||
.required(),
|
.lowercase()
|
||||||
appId: Joi.string()
|
.length(24)
|
||||||
.empty('')
|
.required(),
|
||||||
.uri(),
|
appId: Joi.string()
|
||||||
sess: Joi.string().max(255),
|
.empty('')
|
||||||
ip: Joi.string().ip({
|
.uri(),
|
||||||
version: ['ipv4', 'ipv6'],
|
sess: Joi.string().max(255),
|
||||||
cidr: 'forbidden'
|
ip: Joi.string().ip({
|
||||||
})
|
version: ['ipv4', 'ipv6'],
|
||||||
});
|
cidr: 'forbidden'
|
||||||
|
})
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
const result = Joi.validate(req.params, schema, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
userHandler.setupU2f(user, result.value, (err, u2fRegRequest) => {
|
if (result.error) {
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let u2fRegRequest = await setupU2f(user, result.value);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
u2fRegRequest
|
u2fRegRequest
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Send response from U2F key
|
// Send response from U2F key
|
||||||
server.post('/users/:user/2fa/u2f/enable', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/u2f/enable',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
errorCode: Joi.number().max(100),
|
errorCode: Joi.number().max(100),
|
||||||
clientData: Joi.string()
|
clientData: Joi.string()
|
||||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||||
.max(10240),
|
.max(10240),
|
||||||
registrationData: Joi.string()
|
registrationData: Joi.string()
|
||||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||||
.max(10240),
|
.max(10240),
|
||||||
version: Joi.string().allow('U2F_V2'),
|
version: Joi.string().allow('U2F_V2'),
|
||||||
challenge: Joi.string()
|
challenge: Joi.string()
|
||||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||||
.max(1024),
|
.max(1024),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.value.errorCode) {
|
|
||||||
let error;
|
|
||||||
|
|
||||||
switch (result.value.errorCode) {
|
|
||||||
case U2F_ERROR_CODES.DEVICE_INELIGIBLE:
|
|
||||||
error = 'U2F token is already registered';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error = U2F_ERROR_MESSAGES.get(result.value.errorCode) || 'Unknown error code' + result.value.errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
error
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
const result = Joi.validate(req.params, schema, {
|
||||||
}
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
if (result.error) {
|
||||||
|
|
||||||
userHandler.enableU2f(user, result.value, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
if (result.value.errorCode) {
|
||||||
|
let error;
|
||||||
|
|
||||||
|
switch (result.value.errorCode) {
|
||||||
|
case U2F_ERROR_CODES.DEVICE_INELIGIBLE:
|
||||||
|
error = 'U2F token is already registered';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = U2F_ERROR_MESSAGES.get(result.value.errorCode) || 'Unknown error code' + result.value.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let u2f = await enableU2f(user, result.value);
|
||||||
|
|
||||||
|
if (!u2f) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Failed to enable U2F',
|
error: 'Failed to enable U2F',
|
||||||
code: 'U2fEnableFailed'
|
code: 'U2fEnableFailed'
|
||||||
|
|
@ -156,53 +167,53 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Disable U2F auth for an user
|
// Disable U2F auth for an user
|
||||||
server.del('/users/:user/2fa/u2f', (req, res, next) => {
|
server.del(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/u2f',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
req.query.user = req.params.user;
|
|
||||||
|
|
||||||
const result = Joi.validate(req.query, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
req.query.user = req.params.user;
|
||||||
|
|
||||||
userHandler.disableU2f(user, result.value, (err, result) => {
|
const result = Joi.validate(req.query, schema, {
|
||||||
if (err) {
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let u2f = await disableU2f(user, result.value);
|
||||||
|
if (!u2f) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Failed to disable U2F',
|
error: 'Failed to disable U2F',
|
||||||
code: 'U2fDisableFailed'
|
code: 'U2fDisableFailed'
|
||||||
|
|
@ -215,53 +226,53 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Generate U2F Authentciation Request
|
// Generate U2F Authentciation Request
|
||||||
server.post('/users/:user/2fa/u2f/start', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/u2f/start',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
appId: Joi.string()
|
appId: Joi.string()
|
||||||
.empty('')
|
.empty('')
|
||||||
.uri(),
|
.uri(),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
});
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
const result = Joi.validate(req.params, schema, {
|
||||||
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
userHandler.startU2f(user, result.value, (err, u2fAuthRequest) => {
|
if (result.error) {
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).readOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).readAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let u2fAuthRequest = await startU2f(user, result.value);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Failed to generate authentication request for U2F',
|
error: 'Failed to generate authentication request for U2F',
|
||||||
|
|
@ -276,76 +287,76 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Send response from U2F key
|
// Send response from U2F key
|
||||||
server.post('/users/:user/2fa/u2f/check', (req, res, next) => {
|
server.post(
|
||||||
res.charSet('utf-8');
|
'/users/:user/2fa/u2f/check',
|
||||||
|
tools.asyncifyJson(async (req, res, next) => {
|
||||||
|
res.charSet('utf-8');
|
||||||
|
|
||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
user: Joi.string()
|
user: Joi.string()
|
||||||
.hex()
|
.hex()
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.length(24)
|
.length(24)
|
||||||
.required(),
|
.required(),
|
||||||
errorCode: Joi.number().max(100),
|
errorCode: Joi.number().max(100),
|
||||||
clientData: Joi.string()
|
clientData: Joi.string()
|
||||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||||
.max(10240),
|
.max(10240),
|
||||||
signatureData: Joi.string()
|
signatureData: Joi.string()
|
||||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||||
.max(10240),
|
.max(10240),
|
||||||
sess: Joi.string().max(255),
|
sess: Joi.string().max(255),
|
||||||
ip: Joi.string().ip({
|
ip: Joi.string().ip({
|
||||||
version: ['ipv4', 'ipv6'],
|
version: ['ipv4', 'ipv6'],
|
||||||
cidr: 'forbidden'
|
cidr: 'forbidden'
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const result = Joi.validate(req.params, schema, {
|
|
||||||
abortEarly: false,
|
|
||||||
convert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
res.json({
|
|
||||||
error: result.error.message,
|
|
||||||
code: 'InputValidationError'
|
|
||||||
});
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.value.errorCode) {
|
|
||||||
let error;
|
|
||||||
|
|
||||||
switch (result.value.errorCode) {
|
|
||||||
case U2F_ERROR_CODES.DEVICE_INELIGIBLE:
|
|
||||||
error = 'U2F token is not registered';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error = U2F_ERROR_MESSAGES.get(result.value.errorCode) || 'Unknown error code' + result.value.errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
error
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
const result = Joi.validate(req.params, schema, {
|
||||||
}
|
abortEarly: false,
|
||||||
|
convert: true
|
||||||
|
});
|
||||||
|
|
||||||
let user = new ObjectID(result.value.user);
|
if (result.error) {
|
||||||
|
|
||||||
userHandler.checkU2f(user, result.value, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
res.json({
|
res.json({
|
||||||
error: err.message,
|
error: result.error.message,
|
||||||
code: err.code
|
code: 'InputValidationError'
|
||||||
});
|
});
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
if (result.value.errorCode) {
|
||||||
|
let error;
|
||||||
|
|
||||||
|
switch (result.value.errorCode) {
|
||||||
|
case U2F_ERROR_CODES.DEVICE_INELIGIBLE:
|
||||||
|
error = 'U2F token is not registered';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = U2F_ERROR_MESSAGES.get(result.value.errorCode) || 'Unknown error code' + result.value.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// permissions check
|
||||||
|
if (req.user && req.user === result.value.user) {
|
||||||
|
req.validate(roles.can(req.role).updateOwn('users'));
|
||||||
|
} else {
|
||||||
|
req.validate(roles.can(req.role).updateAny('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new ObjectID(result.value.user);
|
||||||
|
let u2f = await checkU2f(user, result.value);
|
||||||
|
if (!u2f) {
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Failed to validate U2F request',
|
error: 'Failed to validate U2F request',
|
||||||
code: 'U2fFail'
|
code: 'U2fFail'
|
||||||
|
|
@ -358,6 +369,6 @@ module.exports = (db, server, userHandler) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue