From ea4b038f734d7c9516c1267b319abc64f4e16314 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 29 Jul 2017 22:08:43 +0300 Subject: [PATCH] fixed authlog previous paging --- docs/api.md | 1 + lib/api/2fa.js | 1 + lib/api/asps.js | 4 ++++ lib/api/auth.js | 16 +++++++++------- lib/api/users.js | 21 +++++++++++++++++---- lib/user-handler.js | 21 ++++++++++++++++++++- 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/docs/api.md b/docs/api.md index 36ae6e91..3b555545 100644 --- a/docs/api.md +++ b/docs/api.md @@ -432,6 +432,7 @@ This call prepares the user to support 2FA tokens. If 2FA is already enabled the - **user** (required) is the ID of the user - **issuer** is the name to be shown in the Authenticator App +- **fresh** is a boolean. If true then generates a new seed even if an old one already exists - **ip** is the IP address the request was made from **Response fields** diff --git a/lib/api/2fa.js b/lib/api/2fa.js index 9188082a..c0522a9e 100644 --- a/lib/api/2fa.js +++ b/lib/api/2fa.js @@ -10,6 +10,7 @@ module.exports = (db, server, userHandler) => { const schema = Joi.object().keys({ user: Joi.string().hex().lowercase().length(24).required(), issuer: Joi.string().trim().max(255).required(), + fresh: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).default(false), ip: Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' diff --git a/lib/api/asps.js b/lib/api/asps.js index e93c41a1..67b56c0b 100644 --- a/lib/api/asps.js +++ b/lib/api/asps.js @@ -116,6 +116,10 @@ module.exports = (db, server, userHandler) => { let generateMobileconfig = result.value.generateMobileconfig; let scopes = result.value.scopes || ['*']; + if (scopes.includes('*')) { + scopes = ['*']; + } + if (generateMobileconfig && !scopes.includes('*') && (!scopes.includes('imap') || !scopes.includes('smtp'))) { res.json({ error: 'Profile file requires imap and smtp scopes' diff --git a/lib/api/auth.js b/lib/api/auth.js index 8bb8040a..7feb2146 100644 --- a/lib/api/auth.js +++ b/lib/api/auth.js @@ -72,9 +72,9 @@ module.exports = (db, server, userHandler) => { user: Joi.string().hex().lowercase().length(24).required(), action: Joi.string().trim().lowercase().empty('').max(100), limit: Joi.number().default(20).min(1).max(250), - next: Joi.string().alphanum().max(100), - prev: Joi.string().alphanum().max(100), - page: Joi.number().default(1) + next: Joi.string().empty('').alphanum().max(100), + previous: Joi.string().empty('').alphanum().max(100), + page: Joi.number().empty('').default(1) }); req.query.user = req.params.user; @@ -82,7 +82,7 @@ module.exports = (db, server, userHandler) => { const result = Joi.validate(req.query, schema, { abortEarly: false, convert: true, - allowUnknown: true + allowUnknown: false }); if (result.error) { @@ -97,7 +97,7 @@ module.exports = (db, server, userHandler) => { let action = result.value.action; let page = result.value.page; let pageNext = result.value.next; - let pagePrev = result.value.prev; + let pagePrevious = result.value.previous; db.users.collection('users').findOne({ _id: user @@ -144,8 +144,8 @@ module.exports = (db, server, userHandler) => { if (pageNext) { opts.next = pageNext; - } else if (pagePrev) { - opts.prev = pagePrev; + } else if (pagePrevious) { + opts.previous = pagePrevious; } MongoPaging.find(db.users.collection('authlog'), opts, (err, result) => { @@ -177,7 +177,9 @@ module.exports = (db, server, userHandler) => { total, page, prev: prevUrl, + previousCursor: result.hasPrevious ? result.previous : false, next: nextUrl, + nextCursor: result.hasNext ? result.next : false, results: (result.results || []).map(resultData => { let response = { id: resultData._id diff --git a/lib/api/users.js b/lib/api/users.js index 51aa6e63..b9060cab 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -256,6 +256,9 @@ module.exports = (db, server, userHandler) => { enabled2fa: userData.enabled2fa, + forward: userData.forward, + targetUrl: userData.targetUrl, + limits: { quota: { allowed: Number(userData.quota) || config.maxStorage * 1024 * 1024, @@ -288,14 +291,14 @@ module.exports = (db, server, userHandler) => { const schema = Joi.object().keys({ user: Joi.string().hex().lowercase().length(24).required(), - existingPassword: Joi.string().min(1).max(256), + existingPassword: Joi.string().empty('').min(1).max(256), password: Joi.string().min(8).max(256), language: Joi.string().min(2).max(20).lowercase(), - name: Joi.string().max(256), - forward: Joi.string().email(), - targetUrl: Joi.string().max(256), + name: Joi.string().empty('').max(256), + forward: Joi.string().empty('').email(), + targetUrl: Joi.string().empty('').max(256), retention: Joi.number().min(0), quota: Joi.number().min(0), @@ -330,6 +333,16 @@ module.exports = (db, server, userHandler) => { let user = new ObjectID(result.value.user); if (forward) { result.value.forward = forward; + } else if (!result.value.forward && 'forward' in req.params) { + result.value.forward = ''; + } + + if (!result.value.targetUrl && 'targetUrl' in req.params) { + result.value.targetUrl = ''; + } + + if (!result.value.name && 'name' in req.params) { + result.value.name = ''; } userHandler.update(user, result.value, (err, success) => { diff --git a/lib/user-handler.js b/lib/user-handler.js index 02dbd9d9..a46ad8ad 100644 --- a/lib/user-handler.js +++ b/lib/user-handler.js @@ -12,6 +12,7 @@ const generatePassword = require('generate-password'); const os = require('os'); const crypto = require('crypto'); const mailboxTranslations = require('./translations'); +const base32 = require('base32.js'); class UserHandler { constructor(options) { @@ -499,7 +500,8 @@ class UserHandler { }, { fields: { username: true, - enabled2fa: true + enabled2fa: true, + seed: true } }, (err, userData) => { if (err) { @@ -515,6 +517,23 @@ class UserHandler { return callback(new Error('2FA is already enabled for this user')); } + if (!data.fresh && userData.seed) { + if (userData.seed) { + let otpauth_url = speakeasy.otpauthURL({ + secret: base32.decode(userData.seed), + label: userData.username, + issuer: data.issuer || 'Wild Duck' + }); + return QRCode.toDataURL(otpauth_url, (err, data_url) => { + if (err) { + log.error('DB', 'QRFAIL username=%s error=%s', userData.username, err.message); + return callback(new Error('Failed to generate QR code')); + } + return callback(null, data_url); + }); + } + } + let secret = speakeasy.generateSecret({ length: 20, name: userData.username