/* global it:false */ /* global describe:false */ /* global before:false */ /* global after:false */ 'use strict'; const BoxError = require('../boxerror.js'), common = require('./common.js'), expect = require('expect.js'), safe = require('safetydance'), speakeasy = require('speakeasy'), users = require('../users.js'), _ = require('underscore'); describe('User', function () { const { domainSetup, cleanup, admin, user, auditSource, checkMails, clearMailQueue } = common; async function cleanupUsers() { for (const u of await users.list()) { await users.del(u, auditSource); } } async function createOwner() { await cleanupUsers(); const id = await users.add(admin.email, admin, auditSource); admin.id = id; } before(domainSetup); after(cleanup); function checkUser(a, b) { expect(a.creationTime).to.be.a(Date); expect(a.resetTokenCreationTime).to.be.a(Date); const fields = [ 'id', 'username', 'email', 'fallbackEmail', 'role', 'displayName', 'source', 'permissions', 'active' ]; expect(_.pick(a, fields)).to.be.eql(_.pick(b, fields)); } describe('add', function () { it('fails due to short password', async function () { const user = Object.assign({}, admin, { password: 'Fo$%23' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails due to reserved username', async function () { const user = Object.assign({}, admin, { username: 'admin' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails due to invalid username', async function () { const user = Object.assign({}, admin, { username: 'moo+daemon' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails due to empty username', async function () { const user = Object.assign({}, admin, { username: '' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails due to long username', async function () { const user = Object.assign({}, admin, { username: new Array(257).fill('Z').join('') }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails due to reserved app pattern', async function () { const user = Object.assign({}, admin, { username: 'maybe.app' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails because password is empty', async function () { const user = Object.assign({}, admin, { password: '' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('fails because fallbackEmail is not an email', async function () { const user = Object.assign({}, admin, { fallbackEmail: 'notanemail' }); const [error] = await safe(users.add(user.email, user, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('can add user', async function () { const id = await users.add(admin.email, admin, auditSource); admin.id = id; }); it('cannot add user with same email again', async function () { const [error] = await safe(users.add(admin.email, admin, auditSource)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); expect(error.message).to.equal('email already exists'); }); it('cannot add user with same username again', async function () { const [error] = await safe(users.add('somethingelse@not.taken', admin, auditSource)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); expect(error.message).to.equal('username already exists'); }); }); describe('getters', function () { before(cleanupUsers); it('cannot get by bad user id', async function () { const result = await users.get('random'); expect(result).to.be(null); }); it('fails because there is no owner', async function () { const owner = await users.getOwner(); expect(owner).to.be(null); }); it('getOwner succeeds', async function () { const id = await users.add(admin.email, admin, auditSource); admin.id = id; const owner = await users.getOwner(); checkUser(owner, admin); }); it('can get by user id', async function () { const result = await users.get(admin.id); checkUser(result, admin); }); it('can get by username', async function () { const result = await users.getByUsername(admin.username); checkUser(result, admin); }); it('can get by email', async function () { const result = await users.getByEmail(admin.email); checkUser(result, admin); }); it('add another admin', async function () { const result = await users.add(user.email, user, auditSource); user.id = result; await users.update(user, { role: users.ROLE_ADMIN }, auditSource); user.role = users.ROLE_ADMIN; }); it('getSuperadmins succeeds', async function () { const results = await users.getSuperadmins(); expect(results.length).to.be(1); checkUser(results[0], admin); }); it('getAdmins succeeds', async function () { const results = await users.getAdmins(); expect(results.length).to.be(2); checkUser(results[0], admin); // owner is always the first checkUser(results[1], user); }); it('getByResetToken fails for empty resetToken', async function () { const [error] = await safe(users.getByResetToken('')); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('getByResetToken fails for bad resetToken', async function () { const result = await users.getByResetToken(new Array(64).fill('Z').join('')); expect(result).to.be(null); }); it('can get by resetToken', async function () { user.resetToken = new Array(64).fill('X').join(''); await users.update(user, { resetToken: user.resetToken }, auditSource); const result = await users.getByResetToken(user.resetToken); checkUser(result, user); }); it('can list', async function () { const results = await users.list(); expect(results.length).to.be(2); checkUser(results[0], admin); checkUser(results[1], user); }); it('can listPaged', async function () { let results = await users.listPaged(null, null, 1, 1); expect(results.length).to.be(1); checkUser(results[0], admin); results = await users.listPaged(null, null, 2, 1); expect(results.length).to.be(1); checkUser(results[0], user); }); it('can listPaged (search)', async function () { const results = await users.listPaged(admin.email.slice(0, 8), null, 1, 1); expect(results.length).to.be(1); checkUser(results[0], admin); }); }); describe('update', function () { before(createOwner); it('fails due to unknown userid', async function () { const user = Object.assign({}, admin, { id: 'random' }); const [error] = await safe(users.update(user, { displayName: 'full name' }, auditSource)); expect(error.reason).to.equal(BoxError.NOT_FOUND); }); it('fails due to invalid email', async function () { const [error] = await safe(users.update(admin, { email: 'brokenemailaddress' }, auditSource)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('cannot update the user with already existing email', async function () { const result = await users.add(user.email, user, auditSource); user.id = result; const [error] = await safe(users.update(admin, { email: user.email }, auditSource)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); expect(error.message).to.equal('email already exists'); }); it('cannot update username', async function () { const [error] = await safe(users.update(admin, { username: user.username }, auditSource)); expect(error.reason).to.be(BoxError.CONFLICT); expect(error.message).to.equal('Username cannot be changed'); }); it('can update the user', async function () { await users.update(admin, { email: 'some@thing.com', displayName: 'Heiter' }, auditSource); const user = await users.get(admin.id); expect(user.email).to.equal('some@thing.com'); expect(user.displayName).to.equal('Heiter'); }); }); describe('verify', function () { before(createOwner); it('fails due to non existing user', async function () { const [error] = await safe(users.verify('somerandomid', 'somepassword', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.NOT_FOUND); }); it('fails due to empty password', async function () { const [error] = await safe(users.verify(admin.id, '', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('fails due to wrong password', async function () { const [error] = await safe(users.verify(admin.id, admin.password+'x', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds', async function () { const result = await users.verify(admin.id, admin.password, users.AP_WEBADMIN, {}); expect(result).to.be.ok(); expect(result.appPassword).to.not.be.ok(); expect(result.ghost).to.not.be.ok(); }); it('fails for ghost if not enabled', async function () { const [error] = await safe(users.verify(admin.id, 'foobar', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('fails for ghost with wrong password', async function () { await users.setGhost(admin, 'testpassword', 0); const [error] = await safe(users.verify(admin.id, 'foobar', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds for ghost', async function () { await users.setGhost(admin, 'testpassword', 0); const result = await users.verify(admin.id, 'testpassword', users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); expect(result.ghost).to.be(true); }); it('succeeds for normal user password when ghost file exists', async function () { await users.setGhost(admin, 'testpassword', 0); const result = await users.verify(admin.id, admin.password, users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); expect(result.ghost).to.not.be.ok(); }); }); describe('verifyWithUsername', function () { before(createOwner); it('fails due to non existing username', async function () { const [error] = await safe(users.verifyWithUsername('someusername', 'somepass', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.NOT_FOUND); }); it('fails due to empty password', async function () { const [error] = await safe(users.verifyWithUsername(admin.username, '', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('fails due to wrong password', async function () { const [error] = await safe(users.verifyWithUsername(admin.username, 'somepass', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds', async function () { const result = await users.verifyWithUsername(admin.username, admin.password, users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); }); it('succeeds for different username case', async function () { const result = await users.verifyWithUsername(admin.username.toUpperCase(), admin.password, users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); }); it('fails for ghost with wrong password', async function () { await users.setGhost(admin, 'testpassword', 0); const [error] = await safe(users.verifyWithUsername(admin.username, 'foobar', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds for ghost', async function () { await users.setGhost(admin, 'testpassword', 0); const result = await users.verifyWithUsername(admin.username, 'testpassword', users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); expect(result.ghost).to.be(true); }); }); describe('verifyWithEmail', function () { before(createOwner); it('fails due to non existing user', async function () { const [error] = await safe(users.verifyWithEmail('bad@email.com', admin.password, users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.NOT_FOUND); }); it('fails due to empty password', async function () { const [error] = await safe(users.verifyWithEmail(admin.email, '', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('fails due to wrong password', async function () { const [error] = await safe(users.verifyWithEmail(admin.email, 'badpassword', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds', async function () { const result = await users.verifyWithEmail(admin.email, admin.password, users.AP_WEBADMIN, {}); expect(result.id).to.be(admin.id); }); it('succeeds for different email case', async function () { const result = await users.verifyWithEmail(admin.email.toUpperCase(), admin.password, users.AP_WEBADMIN, {}); expect(result.id).to.be(admin.id); }); it('fails for ghost with wrong password', async function () { await users.setGhost(admin, 'testpassword', 0); const [error] = await safe(users.verifyWithEmail(admin.email, 'foobar', users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('succeeds for ghost', async function () { await users.setGhost(admin, 'testpassword', 0); const result = await users.verifyWithEmail(admin.email, 'testpassword', users.AP_WEBADMIN, {}); expect(result.id).to.equal(admin.id); expect(result.ghost).to.equal(true); }); }); describe('setup 2fa', function () { before(createOwner); let twofa; it('create secret', async function () { twofa = await users.setTwoFactorAuthenticationSecret(admin, auditSource); expect(twofa.secret).to.be.a('string'); expect(twofa.qrcode).to.be.a('string'); }); it('can create secret again', async function () { twofa = await users.setTwoFactorAuthenticationSecret(admin, auditSource); expect(twofa.secret).to.be.a('string'); expect(twofa.qrcode).to.be.a('string'); admin.twoFactorAuthenticationSecret = twofa.secret; // update user object }); it('enable 2fa', async function () { const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); await users.enableTwoFactorAuthentication(admin, totpToken, auditSource); const u = await users.get(admin.id); expect(u.twoFactorAuthenticationEnabled).to.be(true); admin.twoFactorAuthenticationEnabled = true; // update user object }); it('cannot re-create secret', async function () { const [error] = await safe(users.setTwoFactorAuthenticationSecret(admin, auditSource)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); it('cannot re-enable 2fa', async function () { const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); const [error] = await safe(users.enableTwoFactorAuthentication(admin, totpToken, auditSource)); expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); it('verify fails without 2fa', async function () { const [error] = await safe(users.verifyWithUsername(admin.username, admin.password, users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); expect(error.message).to.be('A totpToken must be provided'); }); it('verify succeeds with relaxed 2fa', async function () { const user = await users.verifyWithUsername(admin.username, admin.password, users.AP_WEBADMIN, { skipTotpCheck: true }); expect(user.id).to.be(admin.id); }); it('verify succeeds with relaxed 2fa but incorrect totp (totp is ignored)', async function () { const user = await users.verifyWithUsername(admin.username, admin.password, users.AP_WEBADMIN, { totpToken: 'schlecht', skipTotpCheck: true }); expect(user.id).to.be(admin.id); }); it('verify succeeds with valid 2fa', async function () { const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); const user = await users.verifyWithUsername(admin.username, admin.password, users.AP_WEBADMIN, { totpToken }); expect(user.id).to.be(admin.id); }); }); describe('active', function () { before(createOwner); it('verify fails for inactive user', async function () { await users.update(admin, { active: false }, auditSource); const [error] = await safe(users.verify(admin.id, admin.password, users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.NOT_FOUND); }); it('verify succeeds for inactive user', async function () { await users.update(admin, { active: true }, auditSource); await users.verify(admin.id, admin.password, users.AP_WEBADMIN, {}); }); }); describe('get', function () { before(createOwner); it('fails due to non existing user', async function () { const result = await users.get('randomid'); expect(result).to.be(null); }); it('succeeds', async function () { const result = await users.get(admin.id); expect(result.id).to.equal(admin.id); expect(result.email).to.equal(admin.email.toLowerCase()); expect(result.fallbackEmail).to.equal(admin.fallbackEmail.toLowerCase()); expect(result.username).to.equal(admin.username.toLowerCase()); expect(result.displayName).to.equal(admin.displayName); }); }); describe('activated', function () { before(cleanupUsers); it('succeeds with no users', async function () { const activated = await users.isActivated(); expect(activated).to.be(false); }); it('create admin', createOwner); it('succeeds with users', async function () { const activated = await users.isActivated(); expect(activated).to.be(true); }); }); describe('set password', function () { before(createOwner); it('fails due to unknown user', async function () { const user = Object.assign({}, admin, { id: 'doesnotexist' }); const [error] = await safe(users.setPassword(user, 'newpassword', auditSource)); expect(error.reason).to.be(BoxError.NOT_FOUND); }); it('fails due to empty password', async function () { const [error] = await safe(users.setPassword(admin, '', auditSource)); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('fails due to invalid password', async function () { const [error] = await safe(users.setPassword(admin, 'foobar', auditSource)); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('succeeds', async function () { await users.setPassword(admin, 'ThisIsNew1Password', auditSource); }); it('actually changed the password (unable to login with old pasword)', async function () { const [error] = await safe(users.verify(admin.id, admin.password, users.AP_WEBADMIN, {})); expect(error.reason).to.equal(BoxError.INVALID_CREDENTIALS); }); it('actually changed the password (login with new password)', async function () { await users.verify(admin.id, 'ThisIsNew1Password', users.AP_WEBADMIN, {}); }); }); describe('sendPasswordResetByIdentifier', function () { before(createOwner); it('fails due to unknown email', async function () { const [error] = await safe(users.sendPasswordResetByIdentifier('unknown@mail.com', auditSource)); expect(error.reason).to.eql(BoxError.NOT_FOUND); }); it('fails due to unknown username', async function () { const [error] = await safe(users.sendPasswordResetByIdentifier('unknown', auditSource)); expect(error.reason).to.eql(BoxError.NOT_FOUND); }); it('succeeds with email', async function () { await clearMailQueue(); await users.sendPasswordResetByIdentifier(admin.email, auditSource); await checkMails(1); }); it('succeeds with username', async function () { await clearMailQueue(); await users.sendPasswordResetByIdentifier(admin.username, auditSource); await checkMails(1); }); }); describe('language', function () { before(createOwner); it('default language is empty', async function () { const result = await users.get(admin.id); expect(result.language).to.be(''); }); it('cannot set bad language', async function () { const [error] = await safe(users.update(admin, { language: 'ta '}, auditSource)); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('can set language', async function () { await users.update(admin, { language: 'en' }, auditSource); const result = await users.get(admin.id); expect(result.language).to.be('en'); }); it('can reset language', async function () { await users.update(admin, { language: '' }, auditSource); const result = await users.get(admin.id); expect(result.language).to.be(''); }); }); describe('invite', function () { before(createOwner); let user; it('get link fails as alreayd been used', async function () { const [error] = await safe(users.getInviteLink(admin, auditSource)); expect(error.reason).to.be(BoxError.BAD_STATE); }); it('can get link', async function () { const userId = await users.add('some@mail.com', { username: 'someoneinvited', displayName: 'some one', password: 'unsafe1234' }, auditSource); user = await users.get(userId); const inviteLink = await users.getInviteLink(user, auditSource); expect(inviteLink).to.be.a('string'); expect(inviteLink).to.contain(user.inviteToken); }); it('cannot send mail for already active user', async function () { const [error] = await safe(users.sendInviteEmail(admin, 'admin@mail.com', auditSource)); expect(error.reason).to.be(BoxError.BAD_STATE); }); it('cannot send mail with empty receipient', async function () { const [error] = await safe(users.sendInviteEmail(user, '', auditSource)); expect(error.reason).to.be(BoxError.BAD_FIELD); }); it('can send mail', async function () { await clearMailQueue(); await users.sendInviteEmail(user, 'custom@mail.com', auditSource); const emails = await checkMails(1); expect(emails[0].to).to.equal('custom@mail.com'); }); }); describe('remove', function () { before(createOwner); it('fails for unknown user', async function () { const user = Object.assign({}, admin, { id: 'doesnotexist' }); const [error] = await safe(users.del(user, auditSource)); expect(error.reason).to.be(BoxError.NOT_FOUND); }); it('can remove valid user', async function () { await users.del(admin, auditSource); }); it('can re-create user after user was removed', createOwner); }); describe('parseDisplayName', function () { it('parses names', function () { const names = [ { in: 'James', out: { firstName: 'James', lastName: '', middleName: '' } }, { in: 'James Anderson', out: { firstName: 'James', lastName: 'Anderson', middleName: '' } }, { in: 'James Philip Anderson', out: { firstName: 'James', lastName: 'Philip Anderson', middleName: '' } }, ]; for (const name of names) { expect(users.parseDisplayName(name.in)).to.eql(name.out); } }); }); });