Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/api/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ module.exports = (db, server, userHandler) => {
.try(Joi.array().items(Joi.string()), booleanSchema.allow(false))
.required()
.description('List of enabled 2FA mechanisms or false if not required'),
require2faEnabled: booleanSchema
.required()
.description('If true then the account is flagged as requiring 2FA to be enabled'),
requirePasswordChange: booleanSchema.required().description('Indicates if account password has been reset and should be replaced'),
token: Joi.string().description(
'If access token was requested then this is the value to use as access token when making API requests on behalf of logged in user.'
Expand Down Expand Up @@ -263,6 +266,7 @@ module.exports = (db, server, userHandler) => {
address: authData.address,
scope: authData.scope,
require2fa: authData.require2fa,
require2faEnabled: authData.require2faEnabled,
requirePasswordChange: authData.requirePasswordChange
};

Expand Down
8 changes: 8 additions & 0 deletions lib/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ module.exports = (db, server, userHandler, settingsHandler) => {
requirePasswordChange: booleanSchema
.default(false)
.description('If true then requires the user to change password, useful if password for the account was autogenerated'),
require2faEnabled: booleanSchema
.default(false)
.description('If true then the account is flagged as requiring 2FA to be enabled'),

imapMaxUpload: Joi.number().min(0).default(0).description('How many bytes can be uploaded via IMAP during 24 hour'),
imapMaxDownload: Joi.number().min(0).default(0).description('How many bytes can be downloaded via IMAP during 24 hour'),
Expand Down Expand Up @@ -902,6 +905,9 @@ module.exports = (db, server, userHandler, settingsHandler) => {
suspended: booleanSchema.required().description('If true then the user can not authenticate'),
lastPwnedCheck: Joi.date().description('Date when the last check of password against the Pwned passwords list was done'),
passwordPwned: booleanSchema.required().description('Specifies whether the user password has been found in Pwned passwords list'),
require2faEnabled: booleanSchema
.required()
.description('If true then the account is flagged as requiring 2FA to be enabled'),
requirePasswordChange: booleanSchema.required().description('Indicates if account password has been reset and should be replaced')
}).$_setFlag('objectName', 'GetUserResponse')
}
Expand Down Expand Up @@ -1153,6 +1159,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
suspended: !!userData.suspended,
lastPwnedCheck: userData.lastPwnedCheck,
passwordPwned: !!userData.passwordPwned,
require2faEnabled: !!userData.require2faEnabled,
requirePasswordChange: !!userData.requirePasswordChange
})
);
Expand Down Expand Up @@ -1258,6 +1265,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
receivedMax: Joi.number().min(0).description('How many messages can be received from MX during 60 seconds'),

disable2fa: booleanSchema.description('If true, then disables 2FA for this user'),
require2faEnabled: booleanSchema.description('If true then the account is flagged as requiring 2FA to be enabled'),

tags: Joi.array().items(Joi.string().trim().max(128)).description('A list of tags associated with this user'),

Expand Down
3 changes: 3 additions & 0 deletions lib/user-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ class UserHandler {
tempPassword: true,
password: true,
enabled2fa: true,
require2faEnabled: true,
webauthn: true,
disabled: true,
suspended: true,
Expand Down Expand Up @@ -972,6 +973,7 @@ class UserHandler {
address: userData.address,
// if 2FA is enabled then require token validation
require2fa: enabled2fa.length && !usingTemporaryPassword ? enabled2fa : false,
require2faEnabled: !!userData.require2faEnabled,
requirePasswordChange // true, if password was reset and using temporary password
};

Expand Down Expand Up @@ -1623,6 +1625,7 @@ class UserHandler {
retention: data.retention || 0,

disabledScopes,
require2faEnabled: !!data.require2faEnabled,

lastLogin: {
time: false,
Expand Down
9 changes: 8 additions & 1 deletion test/api/users-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('API Users', function () {
recipients: 2000,
forwards: 2000,
requirePasswordChange: false,
require2faEnabled: true,
imapMaxUpload: 5368709120,
imapMaxDownload: 21474836480,
pop3MaxDownload: 21474836480,
Expand Down Expand Up @@ -89,6 +90,7 @@ describe('API Users', function () {
username: 'myuser2',
scope: 'master',
require2fa: false,
require2faEnabled: true,
requirePasswordChange: false
});
});
Expand Down Expand Up @@ -165,6 +167,7 @@ describe('API Users', function () {
username: 'myuser2hash',
scope: 'master',
require2fa: false,
require2faEnabled: false,
requirePasswordChange: false
});
});
Expand Down Expand Up @@ -194,6 +197,7 @@ describe('API Users', function () {
let response = await server.get(`/users/${user}`).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.id).to.equal(user);
expect(response.body.require2faEnabled).to.equal(true);
});

it('should GET /users/{user} expect success / using a token', async () => {
Expand Down Expand Up @@ -230,7 +234,8 @@ describe('API Users', function () {
const response = await server
.put(`/users/${user}`)
.send({
name
name,
require2faEnabled: false
})
.expect(200);

Expand All @@ -241,6 +246,7 @@ describe('API Users', function () {
expect(getResponse.body.success).to.be.true;
expect(getResponse.body.id).to.equal(user);
expect(getResponse.body.name).to.equal(name);
expect(getResponse.body.require2faEnabled).to.equal(false);
});

it('should PUT /users/{user} expect success / and renew a token', async () => {
Expand Down Expand Up @@ -343,6 +349,7 @@ describe('API Users', function () {
username: 'myuser2',
scope: 'master',
require2fa: false,
require2faEnabled: false,
// using a temporary password requires a password change
requirePasswordChange: true
});
Expand Down