Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZMS-124 #626

Merged
merged 12 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 44 additions & 10 deletions lib/api/2fa/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,38 @@ const ObjectId = require('mongodb').ObjectId;
const tools = require('../../tools');
const roles = require('../../roles');
const { sessSchema, sessIPSchema } = require('../../schemas');
const { userId } = require('../../schemas/request/general-schemas');
const { successRes } = require('../../schemas/response/general-schemas');

// Custom 2FA needs to be enabled if your website handles its own 2FA and you want to disable
// master password usage for IMAP/POP/SMTP clients

module.exports = (db, server, userHandler) => {
server.put(
'/users/:user/2fa/custom',
{
path: '/users/:user/2fa/custom',
tags: ['TwoFactorAuth'],
summary: 'Enable custom 2FA for a user',
description: 'This method disables account password for IMAP/POP3/SMTP',
validationObjs: {
requestBody: {
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({ success: successRes }) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -52,14 +70,30 @@ module.exports = (db, server, userHandler) => {
);

server.del(
'/users/:user/2fa/custom',
{
path: '/users/:user/2fa/custom',
tags: ['TwoFactorAuth'],
summary: 'Disable custom 2FA for a user',
description: 'This method disables custom 2FA. If it was the only 2FA set up, then account password for IMAP/POP3/SMTP gets enabled again',
validationObjs: {
requestBody: {},
queryParams: {
sess: sessSchema,
ip: sessIPSchema
},
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({}) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down
147 changes: 118 additions & 29 deletions lib/api/2fa/totp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,48 @@ const ObjectId = require('mongodb').ObjectId;
const tools = require('../../tools');
const roles = require('../../roles');
const { sessSchema, sessIPSchema } = require('../../schemas');
const { userId } = require('../../schemas/request/general-schemas');
const { successRes } = require('../../schemas/response/general-schemas');

module.exports = (db, server, userHandler) => {
// Create TOTP seed and request a QR code

server.post(
'/users/:user/2fa/totp/setup',
{
path: '/users/:user/2fa/totp/setup',
tags: ['TwoFactorAuth'],
summary: 'Generate TOTP seed',
description: 'This method generates TOTP seed and QR code for 2FA. User needs to verify the seed value using 2fa/totp/enable endpoint',
validationObjs: {
requestBody: {
label: Joi.string().empty('').trim().max(255).description('Label text for QR code (defaults to username)'),
issuer: Joi.string().trim().max(255).required().description('Description text for QR code (defaults to "WildDuck")'),
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
pathParams: { user: userId },
response: {
200: {
description: 'Success',
model: Joi.object({
success: successRes,
seed: Joi.string().required().description('Generated TOTP seed value'),
qrcode: Joi.string().required().description('Base64 encoded QR code')
})
}
}
}
},
tools.responseWrapper(async (req, res) => {
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: sessSchema,
ip: sessIPSchema

const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -54,15 +82,31 @@ module.exports = (db, server, userHandler) => {
);

server.post(
'/users/:user/2fa/totp/enable',
{
path: '/users/:user/2fa/totp/enable',
tags: ['TwoFactorAuth'],
summary: 'Enable TOTP seed',
description: 'This method enables TOTP for a user by verifying the seed value generated from 2fa/totp/setup',
validationObjs: {
requestBody: {
token: Joi.string().length(6).required().description('6-digit number that matches seed value from 2fa/totp/setup'),
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({ success: successRes }) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
token: Joi.string().length(6).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -113,14 +157,27 @@ module.exports = (db, server, userHandler) => {
);

server.del(
'/users/:user/2fa/totp',
{
path: '/users/:user/2fa/totp',
tags: ['TwoFactorAuth'],
summary: 'Disable TOTP auth',
description: 'This method disables TOTP for a user. Does not affect other 2FA mechanisms a user might have set up',
validationObjs: {
requestBody: {},
queryParams: { sess: sessSchema, ip: sessIPSchema },
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({ success: successRes }) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -154,15 +211,31 @@ module.exports = (db, server, userHandler) => {
);

server.post(
'/users/:user/2fa/totp/check',
{
path: '/users/:user/2fa/totp/check',
tags: ['TwoFactorAuth'],
summary: 'Validate TOTP Token',
description: 'This method checks if a TOTP token provided by a User is valid for authentication',
validationObjs: {
requestBody: {
token: Joi.string().length(6).required().description('6-digit number'),
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({ success: successRes }) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
token: Joi.string().length(6).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -204,14 +277,30 @@ module.exports = (db, server, userHandler) => {
);

server.del(
'/users/:user/2fa',
{
path: '/users/:user/2fa',
tags: ['TwoFactorAuth'],
summary: 'Disable 2FA',
description: 'This method disables all 2FA mechanisms a user might have set up',
validationObjs: {
requestBody: {},
queryParams: {
sess: sessSchema,
ip: sessIPSchema
},
pathParams: { user: userId },
response: { 200: { description: 'Success', model: Joi.object({}) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down