mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-24 04:42:58 +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 ObjectID = require('mongodb').ObjectID;
|
||||
const tools = require('../../tools');
|
||||
const roles = require('../../roles');
|
||||
const util = require('util');
|
||||
|
||||
module.exports = (db, server, userHandler) => {
|
||||
// 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
|
||||
* @apiName SetupTotp2FA
|
||||
|
|
@ -53,62 +62,63 @@ module.exports = (db, server, userHandler) => {
|
|||
* "code": "UserNotFound"
|
||||
* }
|
||||
*/
|
||||
server.post('/users/:user/2fa/totp/setup', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
label: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(255),
|
||||
issuer: Joi.string()
|
||||
.trim()
|
||||
.max(255)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
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'
|
||||
server.post(
|
||||
'/users/:user/2fa/totp/setup',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
label: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(255),
|
||||
issuer: Joi.string()
|
||||
.trim()
|
||||
.max(255)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
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 (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
success: true,
|
||||
seed: result.secret,
|
||||
qrcode: result.dataUrl
|
||||
seed: totp.secret,
|
||||
qrcode: totp.dataUrl
|
||||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {post} /users/:user/2fa/totp/enable Enable TOTP seed
|
||||
|
|
@ -151,50 +161,51 @@ module.exports = (db, server, userHandler) => {
|
|||
* "code": "UserNotFound"
|
||||
* }
|
||||
*/
|
||||
server.post('/users/:user/2fa/totp/enable', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.post(
|
||||
'/users/:user/2fa/totp/enable',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
token: Joi.string()
|
||||
.length(6)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
token: Joi.string()
|
||||
.length(6)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
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 (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
error: 'Invalid authentication token',
|
||||
code: 'InvalidToken'
|
||||
|
|
@ -207,8 +218,8 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {delete} /users/:user/2fa/totp Disable TOTP auth
|
||||
|
|
@ -245,55 +256,56 @@ module.exports = (db, server, userHandler) => {
|
|||
* "code": "UserNotFound"
|
||||
* }
|
||||
*/
|
||||
server.del('/users/:user/2fa/totp', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.del(
|
||||
'/users/:user/2fa/totp',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
req.query.user = req.params.user;
|
||||
|
||||
userHandler.disableTotp(user, result.value, (err, success) => {
|
||||
if (err) {
|
||||
const result = Joi.validate(req.query, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
success
|
||||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {post} /users/:user/2fa/totp/check Validate TOTP Token
|
||||
|
|
@ -336,50 +348,51 @@ module.exports = (db, server, userHandler) => {
|
|||
* "code": "InvalidToken"
|
||||
* }
|
||||
*/
|
||||
server.post('/users/:user/2fa/totp/check', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.post(
|
||||
'/users/:user/2fa/totp/check',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
token: Joi.string()
|
||||
.length(6)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
token: Joi.string()
|
||||
.length(6)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
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 (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
error: 'Failed to validate TOTP',
|
||||
code: 'InvalidToken'
|
||||
|
|
@ -392,8 +405,8 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {delete} /users/:user/2fa Disable 2FA
|
||||
|
|
@ -430,53 +443,54 @@ module.exports = (db, server, userHandler) => {
|
|||
* "code": "UserNotFound"
|
||||
* }
|
||||
*/
|
||||
server.del('/users/:user/2fa', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.del(
|
||||
'/users/:user/2fa',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
req.query.user = req.params.user;
|
||||
|
||||
userHandler.disable2fa(user, result.value, (err, success) => {
|
||||
if (err) {
|
||||
const result = Joi.validate(req.query, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
success
|
||||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
const Joi = require('joi');
|
||||
const ObjectID = require('mongodb').ObjectID;
|
||||
const tools = require('../../tools');
|
||||
const roles = require('../../roles');
|
||||
const util = require('util');
|
||||
|
||||
const U2F_ERROR_CODES = {
|
||||
OK: 0,
|
||||
|
|
@ -21,129 +24,137 @@ const U2F_ERROR_MESSAGES = new Map([
|
|||
]);
|
||||
|
||||
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
|
||||
server.post('/users/:user/2fa/u2f/setup', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
appId: Joi.string()
|
||||
.empty('')
|
||||
.uri(),
|
||||
sess: Joi.string().max(255),
|
||||
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'
|
||||
server.post(
|
||||
'/users/:user/2fa/u2f/setup',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
appId: Joi.string()
|
||||
.empty('')
|
||||
.uri(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
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 (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
success: true,
|
||||
u2fRegRequest
|
||||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Send response from U2F key
|
||||
server.post('/users/:user/2fa/u2f/enable', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.post(
|
||||
'/users/:user/2fa/u2f/enable',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
errorCode: Joi.number().max(100),
|
||||
clientData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
registrationData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
version: Joi.string().allow('U2F_V2'),
|
||||
challenge: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(1024),
|
||||
sess: Joi.string().max(255),
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
errorCode: Joi.number().max(100),
|
||||
clientData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
registrationData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
version: Joi.string().allow('U2F_V2'),
|
||||
challenge: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(1024),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
const result = Joi.validate(req.params, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
userHandler.enableU2f(user, result.value, (err, result) => {
|
||||
if (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
error: 'Failed to enable U2F',
|
||||
code: 'U2fEnableFailed'
|
||||
|
|
@ -156,53 +167,53 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Disable U2F auth for an user
|
||||
server.del('/users/:user/2fa/u2f', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.del(
|
||||
'/users/:user/2fa/u2f',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
req.query.user = req.params.user;
|
||||
|
||||
userHandler.disableU2f(user, result.value, (err, result) => {
|
||||
if (err) {
|
||||
const result = Joi.validate(req.query, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
error: 'Failed to disable U2F',
|
||||
code: 'U2fDisableFailed'
|
||||
|
|
@ -215,53 +226,53 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Generate U2F Authentciation Request
|
||||
server.post('/users/:user/2fa/u2f/start', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.post(
|
||||
'/users/:user/2fa/u2f/start',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
appId: Joi.string()
|
||||
.empty('')
|
||||
.uri(),
|
||||
sess: Joi.string().max(255),
|
||||
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'
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
appId: Joi.string()
|
||||
.empty('')
|
||||
.uri(),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
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 (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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) {
|
||||
res.json({
|
||||
error: 'Failed to generate authentication request for U2F',
|
||||
|
|
@ -276,76 +287,76 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Send response from U2F key
|
||||
server.post('/users/:user/2fa/u2f/check', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
server.post(
|
||||
'/users/:user/2fa/u2f/check',
|
||||
tools.asyncifyJson(async (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
errorCode: Joi.number().max(100),
|
||||
clientData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
signatureData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
sess: Joi.string().max(255),
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
const schema = Joi.object().keys({
|
||||
user: Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
errorCode: Joi.number().max(100),
|
||||
clientData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
signatureData: Joi.string()
|
||||
.regex(/^[0-9a-z\-_]+$/i, 'web safe base64')
|
||||
.max(10240),
|
||||
sess: Joi.string().max(255),
|
||||
ip: Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'forbidden'
|
||||
})
|
||||
});
|
||||
|
||||
return next();
|
||||
}
|
||||
const result = Joi.validate(req.params, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
let user = new ObjectID(result.value.user);
|
||||
|
||||
userHandler.checkU2f(user, result.value, (err, result) => {
|
||||
if (err) {
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: err.message,
|
||||
code: err.code
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
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({
|
||||
error: 'Failed to validate U2F request',
|
||||
code: 'U2fFail'
|
||||
|
|
@ -358,6 +369,6 @@ module.exports = (db, server, userHandler) => {
|
|||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue