Files
upstream-rocketchat/apps/meteor/tests/end-to-end/api/users.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

6547 lines
202 KiB
TypeScript

import crypto from 'crypto';
import type { Credentials } from '@rocket.chat/api-client';
import type { IRoom, ISubscription, ITeam, IUser } from '@rocket.chat/core-typings';
import { Random } from '@rocket.chat/random';
import type { IGetRoomRoles, PaginatedResult, DefaultUserInfo } from '@rocket.chat/rest-typings';
import { assert, expect } from 'chai';
import { after, afterEach, before, beforeEach, describe, it } from 'mocha';
import { MongoClient } from 'mongodb';
import speakeasy from 'speakeasy';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials, apiEmail, apiUsername, wait, reservedWords } from '../../data/api-data';
import { imgURL, tiffURL } from '../../data/interactions';
import { createAgent, makeAgentAvailable } from '../../data/livechat/rooms';
import { removeAgent, getAgent } from '../../data/livechat/users';
import { updatePermission, updateSetting, restorePermissionToRoles, getSettingValueById } from '../../data/permissions.helper';
import type { ActionRoomParams } from '../../data/rooms.helper';
import { actionRoom, createRoom, deleteRoom } from '../../data/rooms.helper';
import { createTeam, deleteTeam } from '../../data/teams.helper';
import type { IUserWithCredentials } from '../../data/user';
import { adminEmail, password, adminUsername } from '../../data/user';
import type { TestUser } from '../../data/users.helper';
import { createUser, login, deleteUser, getUserByUsername } from '../../data/users.helper';
import { IS_EE, URL_MONGODB } from '../../e2e/config/constants';
const MAX_BIO_LENGTH = 260;
const MAX_NICKNAME_LENGTH = 120;
const customFieldText = {
type: 'text',
required: true,
minLength: 2,
maxLength: 10,
};
function setCustomFields(customFields: unknown) {
const stringified = customFields ? JSON.stringify(customFields) : '';
return request.post(api('settings/Accounts_CustomFields')).set(credentials).send({ value: stringified }).expect(200);
}
function clearCustomFields() {
return setCustomFields(null);
}
const joinChannel = ({ overrideCredentials = credentials, roomId }: { overrideCredentials: Credentials; roomId: IRoom['_id'] }) =>
request.post(api('channels.join')).set(overrideCredentials).send({
roomId,
});
const inviteToChannel = ({
overrideCredentials = credentials,
roomId,
userId,
}: {
overrideCredentials?: Credentials;
roomId: IRoom['_id'];
userId: IUser['_id'];
}) =>
request.post(api('channels.invite')).set(overrideCredentials).send({
userId,
roomId,
});
const addRoomOwner = ({ type, roomId, userId }: { type: ActionRoomParams['type']; roomId: IRoom['_id']; userId: IUser['_id'] }) =>
actionRoom({ action: 'addOwner', type, roomId, extraData: { userId } });
const removeRoomOwner = ({ type, roomId, userId }: { type: ActionRoomParams['type']; roomId: IRoom['_id']; userId: IUser['_id'] }) =>
actionRoom({ action: 'removeOwner', type, roomId, extraData: { userId } });
const getChannelRoles = async ({
roomId,
overrideCredentials = credentials,
}: {
roomId: IRoom['_id'];
overrideCredentials?: Credentials;
}) =>
(
await request.get(api('channels.roles')).set(overrideCredentials).query({
roomId,
})
).body.roles as IGetRoomRoles[];
const setRoomConfig = ({ roomId, favorite, isDefault }: { roomId: IRoom['_id']; favorite?: boolean; isDefault: boolean }) => {
return request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: roomId,
default: isDefault,
favorite: favorite
? {
defaultValue: true,
favorite: false,
}
: undefined,
});
};
const preferences = {
data: {
newRoomNotification: 'door',
newMessageNotification: 'chime',
muteFocusedConversations: true,
clockMode: 1,
useEmojis: true,
convertAsciiEmoji: true,
saveMobileBandwidth: true,
collapseMediaByDefault: false,
autoImageLoad: true,
emailNotificationMode: 'mentions',
unreadAlert: true,
notificationsSoundVolume: 100,
desktopNotifications: 'default',
pushNotifications: 'default',
enableAutoAway: true,
highlights: [],
desktopNotificationRequireInteraction: false,
hideUsernames: false,
hideRoles: false,
displayAvatars: true,
hideFlexTab: false,
sendOnEnter: 'normal',
idleTimeLimit: 3600,
notifyCalendarEvents: false,
enableMobileRinging: false,
...(IS_EE && {
omnichannelTranscriptPDF: false,
}),
},
};
const getUserStatus = (userId: IUser['_id']) =>
new Promise<{
_id: string;
status: 'online' | 'offline' | 'away' | 'busy';
connectionStatus?: 'online' | 'offline' | 'away' | 'busy';
statusSource?: string;
statusExpiresAt?: string;
}>((resolve) => {
void request
.get(api('users.getStatus'))
.query({ userId })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.end((_end, res) => {
resolve(res.body);
});
});
const registerUser = async (
userData: {
username?: string;
email?: string;
name?: string;
pass?: string;
} = {},
overrideCredentials: Credentials | null = credentials,
) => {
const username = userData.username || `user.test.${Date.now()}`;
const email = userData.email || `${username}@rocket.chat`;
const req = request.post(api('users.register'));
if (overrideCredentials) {
req.set(overrideCredentials);
}
const result = await req.send({ email, name: username, username, pass: password, ...userData });
return result.body.user;
};
// For changing user data when it's not possible to do so via API
const updateUserInDb = async (userId: IUser['_id'], userData: Partial<IUser>) => {
const connection = await MongoClient.connect(URL_MONGODB);
await connection
.db()
.collection('users')
.updateOne({ _id: userId as any }, { $set: { ...userData } });
await connection.close();
};
describe('[Users]', () => {
let targetUser: { _id: IUser['_id']; username: string; emails: { address: string }[] };
let userCredentials: Credentials;
before((done) => getCredentials(done));
before('should create a new user', async () => {
const user = await createUser({
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
});
targetUser = {
_id: user._id,
username: user.username,
emails: user.emails,
};
userCredentials = await login(user.username, password);
});
after(() => Promise.all([deleteUser(targetUser), updateSetting('E2E_Enable', false)]));
it('enabling E2E in server and generating keys to user...', async () => {
await updateSetting('E2E_Enable', true);
await request
.post(api('e2e.setUserPublicAndPrivateKeys'))
.set(userCredentials)
.send({
private_key: 'test',
public_key: 'test',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('e2e.fetchMyKeys'))
.set(userCredentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('public_key', 'test');
expect(res.body).to.have.property('private_key', 'test');
});
});
it('should fail when trying to set keys for a user with keys already set', async () => {
await request
.post(api('e2e.setUserPublicAndPrivateKeys'))
.set(userCredentials)
.send({
private_key: 'test',
public_key: 'test',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Keys already set [error-keys-already-set]');
});
});
describe('[/e2e.requestSubscriptionKeys]', () => {
it('should return unauthorized when not authenticated', async () => {
await request.post(api('e2e.requestSubscriptionKeys')).expect(401);
});
it('should accept the request and return success for an authenticated user', async () => {
await request
.post(api('e2e.requestSubscriptionKeys'))
.set(userCredentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
});
describe('[/users.create]', () => {
before(async () => clearCustomFields());
after(async () => clearCustomFields());
it('should create a new user with custom fields', async () => {
await setCustomFields({ customFieldText });
const username = `customField_${apiUsername}`;
const email = `customField_${apiEmail}`;
const customFields = { customFieldText: 'success' };
const res = await request
.post(api('users.create'))
.set(credentials)
.send({
email,
name: username,
username,
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
customFields,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
expect(res.body).to.have.nested.property('user.emails[0].address', email);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', username);
expect(res.body).to.have.nested.property('user.customFields.customFieldText', 'success');
expect(res.body).to.not.have.nested.property('user.e2e');
const { user } = res.body;
await deleteUser(user);
});
function failCreateUser(name: string) {
it(`should not create a new user if username is the reserved word ${name}`, async () => {
const res = await request
.post(api('users.create'))
.set(credentials)
.send({
email: `create_user_fail_${apiEmail}`,
name: `create_user_fail_${apiUsername}`,
username: name,
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', `${name} is blocked and can't be used! [error-blocked-username]`);
});
}
function failUserWithCustomField(field: { name: string; value: unknown; reason: string }) {
it(`should not create a user if a custom field ${field.reason}`, async () => {
await setCustomFields({ customFieldText });
const customFields: Record<string, unknown> = {};
customFields[field.name] = field.value;
await request
.post(api('users.create'))
.set(credentials)
.send({
email: `customField_fail_${apiEmail}`,
name: `customField_fail_${apiUsername}`,
username: `customField_fail_${apiUsername}`,
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
customFields,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-user-registration-custom-field');
});
});
}
[
{ name: 'customFieldText', value: '', reason: 'is required and missing' },
{ name: 'customFieldText', value: '0', reason: 'length is less than minLength' },
{ name: 'customFieldText', value: '0123456789-0', reason: 'length is more than maxLength' },
].forEach((field) => {
failUserWithCustomField(field);
});
reservedWords.forEach((name) => {
failCreateUser(name);
});
describe('users default roles configuration', () => {
const users: IUser[] = [];
before(async () => {
await updateSetting('Accounts_Registration_Users_Default_Roles', 'user,admin');
});
after(async () => {
await updateSetting('Accounts_Registration_Users_Default_Roles', 'user');
await Promise.all(users.map((user) => deleteUser(user)));
});
it('should create a new user with default roles', (done) => {
const username = `defaultUserRole_${apiUsername}${Date.now()}`;
const email = `defaultUserRole_${apiEmail}${Date.now()}`;
void request
.post(api('users.create'))
.set(credentials)
.send({
email,
name: username,
username,
password,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
expect(res.body).to.have.nested.property('user.emails[0].address', email);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', username);
expect(res.body.user.roles).to.have.members(['user', 'admin']);
users.push(res.body.user);
})
.end(done);
});
it('should create a new user with only the role provided', (done) => {
const username = `defaultUserRole_${apiUsername}${Date.now()}`;
const email = `defaultUserRole_${apiEmail}${Date.now()}`;
void request
.post(api('users.create'))
.set(credentials)
.send({
email,
name: username,
username,
password,
roles: ['guest'],
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
expect(res.body).to.have.nested.property('user.emails[0].address', email);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', username);
expect(res.body.user.roles).to.have.members(['guest']);
users.push(res.body.user);
})
.end(done);
});
});
describe('auto join default channels', () => {
let defaultTeamRoomId: IRoom['_id'];
let defaultTeamId: ITeam['_id'];
let group: IRoom;
let user: IUser;
let userCredentials: Credentials;
let user2: IUser;
let user3: IUser;
let userNoDefault: IUser;
const teamName = `defaultTeam_${Date.now()}`;
before(async () => {
const defaultTeam = await createTeam(credentials, teamName, 0);
defaultTeamRoomId = defaultTeam.roomId;
defaultTeamId = defaultTeam._id;
});
before(async () => {
const { body } = await createRoom({
name: `defaultGroup_${Date.now()}`,
type: 'p',
credentials,
extraData: {
broadcast: false,
encrypted: false,
teamId: defaultTeamId,
topic: '',
},
});
group = body.group;
});
after(() =>
Promise.all([
deleteRoom({ roomId: group._id, type: 'p' }),
deleteTeam(credentials, teamName),
deleteUser(user),
deleteUser(user2),
deleteUser(user3),
deleteUser(userNoDefault),
]),
);
it('should not create subscriptions to non default teams or rooms even if joinDefaultChannels is true', async () => {
userNoDefault = await createUser({ joinDefaultChannels: true });
const noDefaultUserCredentials = await login(userNoDefault.username, password);
await request
.get(api('subscriptions.getOne'))
.set(noDefaultUserCredentials)
.query({ roomId: defaultTeamRoomId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription').that.is.null;
});
await request
.get(api('subscriptions.getOne'))
.set(noDefaultUserCredentials)
.query({ roomId: group._id })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription').that.is.null;
});
});
it('should create a subscription for a default team room if joinDefaultChannels is true', async () => {
await setRoomConfig({ roomId: defaultTeamRoomId, favorite: true, isDefault: true });
user = await createUser({ joinDefaultChannels: true });
userCredentials = await login(user.username, password);
await request
.get(api('subscriptions.getOne'))
.set(userCredentials)
.query({ roomId: defaultTeamRoomId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription');
expect(res.body.subscription).to.have.property('rid', defaultTeamRoomId);
});
});
it('should NOT create a subscription for non auto-join rooms inside a default team', async () => {
await request
.get(api('subscriptions.getOne'))
.set(userCredentials)
.query({ roomId: group._id })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription').that.is.null;
});
});
it('should create a subscription for the user in all the auto join rooms of the team', async () => {
await request.post(api('teams.updateRoom')).set(credentials).send({
roomId: group._id,
isDefault: true,
});
user2 = await createUser({ joinDefaultChannels: true });
const user2Credentials = await login(user2.username, password);
await request
.get(api('subscriptions.getOne'))
.set(user2Credentials)
.query({ roomId: group._id })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription');
expect(res.body.subscription).to.have.property('rid', group._id);
});
});
it('should create a subscription for a default room inside a non default team', async () => {
await setRoomConfig({ roomId: defaultTeamRoomId, isDefault: false });
await setRoomConfig({ roomId: group._id, favorite: true, isDefault: true });
user3 = await createUser({ joinDefaultChannels: true });
const user3Credentials = await login(user3.username, password);
// New user should be subscribed to the default room inside a team
await request
.get(api('subscriptions.getOne'))
.set(user3Credentials)
.query({ roomId: group._id })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription');
expect(res.body.subscription).to.have.property('rid', group._id);
});
// New user should not be subscribed to the parent team
await request
.get(api('subscriptions.getOne'))
.set(user3Credentials)
.query({ roomId: defaultTeamRoomId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('subscription').that.is.null;
});
});
});
(IS_EE ? describe : describe.skip)('Voice call extension', () => {
beforeEach(async () => {
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', true);
});
after(async () => {
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', true);
});
it('should create a user with a voice call extension', async () => {
const freeSwitchExtension = '888999';
let user: TestUser<IUser>;
await request
.post(api('users.create'))
.set(credentials)
.send({
email: 'success_extension_user@rocket.chat',
name: 'success_extension_user',
username: 'success_extension_user',
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
freeSwitchExtension,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.freeSwitchExtension', freeSwitchExtension);
user = res.body.user;
});
if (user!) {
await deleteUser(user);
}
});
it('should not create a user with a voice call extension that is already in use', async () => {
const freeSwitchExtension = '123123';
const user = await createUser({ freeSwitchExtension });
await request
.post(api('users.create'))
.set(credentials)
.send({
email: 'fail_extension_in_use@rocket.chat',
name: 'fail_extension_in_use',
username: 'fail_extension_in_use',
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
freeSwitchExtension,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-extension-not-available');
});
await deleteUser(user);
});
it('should not create a user if voip is disabled', async () => {
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', false);
await request
.post(api('users.create'))
.set(credentials)
.send({
email: 'fail_voip_disabled@rocket.chat',
name: 'fail_voip_disabled',
username: 'fail_voip_disabled',
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
freeSwitchExtension: '999',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
});
});
});
describe('default email2fa auto opt in configuration', () => {
let user: IUser;
afterEach(async () => {
await deleteUser(user);
await updateSetting('Accounts_TwoFactorAuthentication_By_Email_Enabled', true);
await updateSetting('Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In', true);
await updateSetting('Accounts_TwoFactorAuthentication_Enabled', true);
});
const dummyUser = {
email: 'email2fa_auto_opt_in@rocket.chat',
name: 'email2fa_auto_opt_in',
username: 'email2fa_auto_opt_in',
password,
};
it('should auto opt in new users for email2fa ', async () => {
await request
.post(api('users.create'))
.set(credentials)
.send(dummyUser)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
user = res.body.user;
});
const newUserCredentials = await login(dummyUser.username, dummyUser.password);
await request
.get(api('users.info'))
.set(newUserCredentials)
.query({
username: dummyUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.services.email2fa.enabled', true);
});
});
it('should not auto opt in new users for email2fa if email2fa is disabled', async () => {
await updateSetting('Accounts_TwoFactorAuthentication_By_Email_Enabled', false);
await request
.post(api('users.create'))
.set(credentials)
.send(dummyUser)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
user = res.body.user;
});
const newUserCredentials = await login(dummyUser.username, dummyUser.password);
await request
.get(api('users.info'))
.set(newUserCredentials)
.query({
username: dummyUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.services.email2fa.enabled');
});
});
it('should not auto opt in new users for email2fa if two factor authentication is disabled', async () => {
await updateSetting('Accounts_TwoFactorAuthentication_Enabled', false);
await request
.post(api('users.create'))
.set(credentials)
.send(dummyUser)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
user = res.body.user;
});
const newUserCredentials = await login(dummyUser.username, dummyUser.password);
await request
.get(api('users.info'))
.set(newUserCredentials)
.query({
username: dummyUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.services.email2fa.enabled');
});
});
it('should not auto opt in new users for email2fa if email2fa is enabled but auto opt in is disabled', async () => {
await updateSetting('Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In', false);
await request
.post(api('users.create'))
.set(credentials)
.send(dummyUser)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
user = res.body.user;
});
const newUserCredentials = await login(dummyUser.username, dummyUser.password);
await request
.get(api('users.info'))
.set(newUserCredentials)
.query({
username: dummyUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.services.email2fa.enabled');
});
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.create'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.create'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.register]', () => {
const email = `email@email${Date.now()}.com`;
const username = `myusername${Date.now()}`;
const users: IUser[] = [];
after(async () => {
await Promise.all(users.map((user) => deleteUser(user)));
});
it('should register new user', async () => {
const res = await request
.post(api('users.register'))
.send({
email,
name: 'name',
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', 'name');
users.push(res.body.user);
});
it('should return an error when trying register new user with an invalid username', async () => {
const res = await request
.post(api('users.register'))
.send({
email,
name: 'name',
username: 'test$username<>',
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error').and.to.be.equal('The username provided is not valid');
});
it('should return an error when trying register new user with an existing username', async () => {
const res = await request
.post(api('users.register'))
.send({
email,
name: 'name',
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error').and.to.be.equal('Username is already in use');
});
it("should return an error when registering a user's name with invalid characters: >, <, /, or \\", async () => {
const res = await request
.post(api('users.register'))
.send({
email,
name: '</\\name>',
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error').and.to.be.equal('Name contains invalid characters');
});
it('should return an error when logged in user tries to register', async () => {
const res = await request
.post(api('users.register'))
.set(credentials)
.send({
email: `newuser${Date.now()}@email.com`,
name: 'New User',
username: `newuser${Date.now()}`,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error').and.to.be.equal('Logged in users can not register again.');
});
describe('registration form setting', () => {
let previousRegistrationForm: Awaited<ReturnType<typeof getSettingValueById>>;
beforeEach(async () => {
previousRegistrationForm = await getSettingValueById('Accounts_RegistrationForm');
});
afterEach(async () => {
await updateSetting('Accounts_RegistrationForm', previousRegistrationForm);
});
it('should reject registration when public registration is disabled', async () => {
await updateSetting('Accounts_RegistrationForm', 'Disabled');
const username = `disabledRegistration_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-user-registration-disabled');
});
const user = await getUserByUsername(username);
expect(user).to.be.undefined;
});
});
describe('secret URL registration', () => {
let previousRegistrationForm: Awaited<ReturnType<typeof getSettingValueById>>;
let previousRegistrationFormSecretURL: Awaited<ReturnType<typeof getSettingValueById>>;
beforeEach(async () => {
[previousRegistrationForm, previousRegistrationFormSecretURL] = await Promise.all([
getSettingValueById('Accounts_RegistrationForm'),
getSettingValueById('Accounts_RegistrationForm_SecretURL'),
]);
await Promise.all([
updateSetting('Accounts_RegistrationForm', 'Secret URL'),
updateSetting('Accounts_RegistrationForm_SecretURL', 'valid-secret'),
]);
});
afterEach(async () => {
await Promise.all([
updateSetting('Accounts_RegistrationForm', previousRegistrationForm),
updateSetting('Accounts_RegistrationForm_SecretURL', previousRegistrationFormSecretURL),
]);
});
it('should reject registration without a secret when registration is limited to a secret URL', async () => {
const username = `missingSecret_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-user-registration-secret');
});
const user = await getUserByUsername(username);
expect(user).to.be.undefined;
});
it('should reject registration with an invalid secret when registration is limited to a secret URL', async () => {
const username = `invalidSecret_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
secret: 'invalid-secret',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-user-registration-secret');
});
const user = await getUserByUsername(username);
expect(user).to.be.undefined;
});
it('should register a user with a valid secret when registration is limited to a secret URL', async () => {
const username = `validSecret_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
secret: 'valid-secret',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
users.push(res.body.user);
});
});
});
describe('manual approval', () => {
let previousManuallyApproveNewUsers: Awaited<ReturnType<typeof getSettingValueById>>;
beforeEach(async () => {
previousManuallyApproveNewUsers = await getSettingValueById('Accounts_ManuallyApproveNewUsers');
});
afterEach(async () => {
await updateSetting('Accounts_ManuallyApproveNewUsers', previousManuallyApproveNewUsers);
});
it('should register an inactive user and persist the reason when manual approval is enabled', async () => {
await updateSetting('Accounts_ManuallyApproveNewUsers', true);
const username = `manualApproval_${Date.now()}`;
const email = `${username}@rocket.chat`;
const reason = 'I need access to the workspace';
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
reason,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
expect(res.body).to.have.nested.property('user.active', false);
users.push(res.body.user);
});
const user = await getUserByUsername<IUser>(username);
expect(user).to.have.property('reason', reason);
});
});
describe('allowed email domains', () => {
let previousAllowedDomainsList: Awaited<ReturnType<typeof getSettingValueById>>;
beforeEach(async () => {
previousAllowedDomainsList = await getSettingValueById('Accounts_AllowedDomainsList');
});
afterEach(async () => {
await updateSetting('Accounts_AllowedDomainsList', previousAllowedDomainsList);
});
it('should reject registration when the email domain is not allowed', async () => {
await updateSetting('Accounts_AllowedDomainsList', 'rocket.chat');
const username = `invalidDomain_${Date.now()}`;
const email = `${username}@example.com`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-invalid-domain');
});
const user = await getUserByUsername(username);
expect(user).to.be.undefined;
});
it('should register a user when the email domain is allowed', async () => {
await updateSetting('Accounts_AllowedDomainsList', 'rocket.chat');
const username = `validDomain_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
users.push(res.body.user);
});
});
});
describe('custom fields', () => {
let previousCustomFields: Awaited<ReturnType<typeof getSettingValueById>>;
beforeEach(async () => {
previousCustomFields = await getSettingValueById('Accounts_CustomFields');
await setCustomFields({ customFieldText });
});
afterEach(async () => {
await updateSetting('Accounts_CustomFields', previousCustomFields);
});
it('should reject registration when a required custom field is empty', async () => {
const username = `missingCustomField_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
customFields: {
customFieldText: '',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
// TODO: assert error-user-registration-custom-field directly after users.register formats this Meteor.Error correctly
expect(res.body).to.have.property('errorType', 'error-invalid-body');
expect(res.body).to.have.nested.property('body.error', 'error-user-registration-custom-field');
});
const user = await getUserByUsername(username);
expect(user).to.be.undefined;
});
it('should save valid custom fields when registering a user', async () => {
const username = `validCustomField_${Date.now()}`;
const email = `${username}@rocket.chat`;
await request
.post(api('users.register'))
.send({
email,
name: username,
username,
pass: password,
customFields: {
customFieldText: 'success',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', username);
users.push(res.body.user);
});
const user = await getUserByUsername<IUser>(username);
expect(user).to.have.nested.property('customFields.customFieldText', 'success');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.register'))
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.info]', () => {
let infoRoom: IRoom;
before(async () => {
infoRoom = (
await createRoom({
type: 'c',
name: `channel.test.info.${Date.now()}-${Math.random()}`,
members: [targetUser.username],
})
).body.channel;
});
after(() =>
Promise.all([
updatePermission('view-other-user-channels', ['admin']),
updatePermission('view-full-other-user-info', ['admin']),
deleteRoom({ type: 'c', roomId: infoRoom._id }),
]),
);
it('should fail when request is without authentication credentials', async () => {
await request
.get(api('users.info'))
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
describe('authentication', () => {
before(() => updateSetting('Accounts_AllowAnonymousRead', true));
after(() => updateSetting('Accounts_AllowAnonymousRead', false));
it('should fail when request is without authentication credentials and Anonymous Read is enabled', async () => {
await request
.get(api('users.info'))
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
it('should fail when request is without token and Anonymous Read is enabled', async () => {
await request
.get(api('users.info'))
.query({
userId: targetUser._id,
})
.set({ 'X-User-Id': credentials['X-User-Id'] })
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
});
it('should return an error when the user does not exist', async () => {
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
username: 'invalid-username',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
it('should query information about a user by userId', async () => {
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', targetUser.username);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', targetUser.username);
expect(res.body).to.not.have.nested.property('user.e2e');
});
it('should return "rooms" property when user request it and the user has the necessary permission (admin, "view-other-user-channels")', async () => {
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: targetUser._id,
includeUserRooms: true,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.rooms').and.to.be.an('array');
const createdRoom = (res.body.user.rooms as ISubscription[]).find((room) => room.rid === infoRoom._id);
expect(createdRoom).to.have.property('unread');
});
it('should NOT return "rooms" property when user NOT request it but the user has the necessary permission (admin, "view-other-user-channels")', async () => {
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.rooms');
});
it('should return the rooms when the user requests their own rooms but they do NOT have the necessary permission', async () => {
await updatePermission('view-other-user-channels', []);
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: credentials['X-User-Id'],
includeUserRooms: true,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.rooms');
expect(res.body.user.rooms).with.lengthOf.at.least(1);
expect(res.body.user.rooms[0]).to.have.property('unread');
});
it("should NOT return the rooms when the user requests another user's rooms WITHOUT having the necessary permission", async () => {
await updatePermission('view-other-user-channels', []);
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: targetUser._id,
fields: JSON.stringify({ userRooms: 1 }),
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.rooms');
});
it("should NOT return any services fields when requesting another user's info, even if the user has the necessary permission", async () => {
await updatePermission('view-full-other-user-info', ['admin']);
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.nested.property('user.services.emailCode');
expect(res.body).to.not.have.nested.property('user.services');
});
it('should return all services fields when request for myself data even without privileged permission', async () => {
await updatePermission('view-full-other-user-info', []);
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: credentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.services.password');
});
it('should correctly route users that have `ufs` in their username', async () => {
const ufsUsername = `ufs-${Date.now()}`;
const user = await createUser({
email: `me-${Date.now()}@email.com`,
name: 'testuser',
username: ufsUsername,
password,
});
await request
.get(api('users.info'))
.set(credentials)
.query({
username: ufsUsername,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.user).to.have.property('type', 'user');
expect(res.body.user).to.have.property('name', 'testuser');
expect(res.body.user).to.have.property('username', ufsUsername);
expect(res.body.user).to.have.property('active', true);
});
await deleteUser(user);
});
it("should NOT return sensitive fields on services even though it's the same user requesting its info", async () => {
const res = await request
.get(api('users.info'))
.set(credentials)
.query({
userId: credentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.services.password').and.to.be.a('boolean');
expect(res.body).to.not.have.nested.property('user.services.email');
expect(res.body).to.not.have.nested.property('user.services.resume');
expect(res.body).to.not.have.nested.property('user.services.passwordHistory');
});
describe('querying by user email', () => {
after(async () => {
await restorePermissionToRoles('view-full-other-user-info');
});
describe("with 'view-full-other-user-info' permission", () => {
before(async () => {
await updatePermission('view-full-other-user-info', ['admin']);
});
it('should query information about a user by email', async () => {
const targetEmail = targetUser.emails[0].address;
await request
.get(api('users.info'))
.set(credentials)
.query({
email: targetEmail,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', targetUser.username);
expect(res.body).to.have.nested.property('user._id', targetUser._id);
expect(res.body).to.have.nested.property('user.emails[0].address', targetEmail);
});
});
it('should return an error when querying by an email that does not exist', async () => {
await request
.get(api('users.info'))
.set(credentials)
.query({
email: 'this_is_a_fake_email_that_does_not_exist@invalid.com',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User not found.');
});
});
});
describe("without 'view-full-other-user-info' permission", () => {
before(async () => {
await updatePermission('view-full-other-user-info', []);
});
it('should return an error when querying another user by email and lacking "view-full-other-user-info" permission', async () => {
await request
.get(api('users.info'))
.set(credentials)
.query({
email: targetUser.emails[0].address,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User not found.');
});
});
it('should query information about myself by email', async () => {
await request
.get(api('users.info'))
.set(userCredentials)
.query({
email: targetUser.emails[0].address,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', targetUser.username);
expect(res.body).to.have.nested.property('user.emails[0].address', targetUser.emails[0].address);
});
});
});
});
(IS_EE ? describe : describe.skip)('querying by freeSwitch extension', () => {
let previousVoipSetting: boolean;
let targetUser: IUser;
before(async () => {
previousVoipSetting = (await getSettingValueById('VoIP_TeamCollab_SIP_Integration_Enabled')) as boolean;
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', true);
targetUser = await createUser({ freeSwitchExtension: '123123' });
});
after(async () => {
await restorePermissionToRoles('view-full-other-user-info');
await deleteUser(targetUser);
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', previousVoipSetting);
});
describe("with 'view-full-other-user-info' permission", () => {
before(async () => {
await updatePermission('view-full-other-user-info', ['admin']);
});
it('should successfully return information on an existing user', async () => {
await request
.get(api('users.info'))
.set(credentials)
.query({
freeSwitchExtension: targetUser.freeSwitchExtension,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', targetUser.username);
expect(res.body).to.have.nested.property('user._id', targetUser._id);
expect(res.body).to.have.nested.property('user.freeSwitchExtension', targetUser.freeSwitchExtension);
});
});
it('should return an error when user does not exist', async () => {
await request
.get(api('users.info'))
.set(credentials)
.query({
freeSwitchExtension: 'this_is_a_fake_extension_that_does_not_exist',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User not found.');
});
});
});
describe("without 'view-full-other-user-info' permission", () => {
before(async () => {
await updatePermission('view-full-other-user-info', []);
});
it('should successfully return information on an existing user', async () => {
await request
.get(api('users.info'))
.set(credentials)
.query({
freeSwitchExtension: targetUser.freeSwitchExtension,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', targetUser.username);
expect(res.body).to.have.nested.property('user.freeSwitchExtension', targetUser.freeSwitchExtension);
});
});
});
});
});
describe('[/users.getPresence]', () => {
it("should query a user's presence by userId", async () => {
const res = await request
.get(api('users.getPresence'))
.set(credentials)
.query({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('presence', 'offline');
});
describe('Logging in with type: "resume"', () => {
let user: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
user = await createUser({ joinDefaultChannels: false });
userCredentials = await login(user.username, password);
});
after(() => deleteUser(user));
it('should return "offline" after a login type "resume" via REST', async () => {
await request
.post(api('login'))
.send({
resume: userCredentials['X-Auth-Token'],
})
.expect('Content-Type', 'application/json')
.expect(200);
await request
.get(api('users.getPresence'))
.set(credentials)
.query({
userId: user._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('presence', 'offline');
});
});
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.getPresence'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.presence]', () => {
describe('Not logged in:', () => {
it('should return 401 unauthorized', async () => {
const res = await request.get(api('users.presence')).expect('Content-Type', 'application/json').expect(401);
expect(res.body).to.have.property('message');
});
});
describe('Logged in:', () => {
it('should return online users full list', async () => {
const res = await request.get(api('users.presence')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', true);
const user = (res.body.users as IUser[]).find((user) => user.username === 'rocket.cat');
expect(user).to.have.all.keys('_id', 'avatarETag', 'username', 'name', 'status', 'utcOffset');
});
it('should return no online users updated after now', async () => {
const res = await request
.get(api('users.presence'))
.query({ from: new Date().toISOString() })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', false);
expect(res.body).to.have.property('users').that.is.an('array').that.has.lengthOf(0);
});
it('should return presence for a single id', async () => {
const res = await request
.get(api('users.presence'))
.query({ ids: 'rocket.cat' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', false);
expect(res.body).to.have.property('users').that.is.an('array').with.lengthOf(1);
expect(res.body.users[0]).to.have.property('_id', 'rocket.cat');
});
it('should correctly parse comma-separated ids and not return an empty result', async () => {
const res = await request
.get(api('users.presence'))
.query({ ids: `rocket.cat,${credentials['X-User-Id']}` })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', false);
// only rocket.cat is guaranteed to be online; admin may be offline
expect(res.body.users.map((u: IUser) => u._id)).to.include('rocket.cat');
});
it('should return presence for repeated ids params', async () => {
const res = await request
.get(api('users.presence'))
.query(`ids=rocket.cat&ids=${credentials['X-User-Id']}`)
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', false);
// only rocket.cat is guaranteed to be online; admin may be offline
expect(res.body.users.map((u: IUser) => u._id)).to.include('rocket.cat');
});
it('should return full list of online users for more than 10 minutes in the past', (done) => {
const date = new Date();
date.setMinutes(date.getMinutes() - 11);
void request
.get(api('users.presence'))
.query({ from: date.toISOString() })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', true);
const user = (res.body.users as IUser[]).find((user) => user.username === 'rocket.cat');
expect(user).to.have.all.keys('_id', 'avatarETag', 'username', 'name', 'status', 'utcOffset');
})
.end(done);
});
it('should return an offline user that still carries a custom status (vacation text/expiration survives offline)', async () => {
const vacationUser = await createUser();
const vacationCredentials = await login(vacationUser.username, password);
const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
await request
.post(api('users.setStatus'))
.set(vacationCredentials)
.send({ status: 'offline', message: 'On vacation', expiresAt })
.expect(200);
const res = await request
.get(api('users.presence'))
.query({ ids: vacationUser._id })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
const returned = (res.body.users as IUser[]).find((u) => u._id === vacationUser._id);
expect(returned, 'offline user with a custom status must be returned by users.presence').to.not.be.undefined;
expect(returned).to.have.property('status', 'offline');
expect(returned).to.have.property('statusText', 'On vacation');
expect(returned).to.have.property('statusExpiresAt');
await deleteUser(vacationUser);
});
});
});
describe('[/users.list]', () => {
let user: TestUser<IUser>;
let deactivatedUser: TestUser<IUser>;
let user2: TestUser<IUser>;
let user2Credentials: Credentials;
let user3: TestUser<IUser>;
let user3Credentials: Credentials;
let group: IRoom;
let inviteToken: string;
before(async () => {
const username = `deactivated_${Date.now()}${apiUsername}`;
const email = `deactivated_+${Date.now()}${apiEmail}`;
const userData = {
email,
name: username,
username,
password,
active: false,
};
deactivatedUser = await createUser(userData);
expect(deactivatedUser).to.not.be.null;
expect(deactivatedUser).to.have.nested.property('username', username);
expect(deactivatedUser).to.have.nested.property('emails[0].address', email);
expect(deactivatedUser).to.have.nested.property('active', false);
expect(deactivatedUser).to.have.nested.property('name', username);
expect(deactivatedUser).to.not.have.nested.property('e2e');
});
before(async () => {
await setCustomFields({ customFieldText });
const username = `customField_${Date.now()}${apiUsername}`;
const email = `customField_+${Date.now()}${apiEmail}`;
const customFields = { customFieldText: 'success' };
const userData = {
email,
name: username,
username,
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true,
customFields,
};
user = await createUser(userData);
expect(user).to.not.be.null;
expect(user).to.have.nested.property('username', username);
expect(user).to.have.nested.property('emails[0].address', email);
expect(user).to.have.nested.property('active', true);
expect(user).to.have.nested.property('name', username);
expect(user).to.have.nested.property('customFields.customFieldText', 'success');
expect(user).to.not.have.nested.property('e2e');
});
before(async () => {
user2 = await createUser({ joinDefaultChannels: false });
user2Credentials = await login(user2.username, password);
user3 = await createUser({ joinDefaultChannels: false });
user3Credentials = await login(user3.username, password);
});
before('Create a group', async () => {
group = (
await createRoom({
type: 'p',
name: `group.test.${Date.now()}-${Math.random()}`,
})
).body.group;
});
before('Create invite link', async () => {
inviteToken = (
await request.post(api('findOrCreateInvite')).set(credentials).send({
rid: group._id,
days: 0,
maxUses: 0,
})
).body._id;
});
after('Remove invite link', async () =>
request
.delete(api(`removeInvite/${inviteToken}`))
.set(credentials)
.send(),
);
after(() =>
Promise.all([
clearCustomFields(),
deleteUser(deactivatedUser),
deleteUser(user),
deleteUser(user2),
deleteUser(user3),
deleteRoom({ type: 'p', roomId: group._id }),
updatePermission('view-outside-room', ['admin', 'owner', 'moderator', 'user']),
updateSetting('API_Apply_permission_view-outside-room_on_users-list', false),
]),
);
it('should query all users in the system', async () => {
const res = await request.get(api('users.list')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
const myself = (res.body.users as IUser[]).find((user) => user.username === adminUsername);
expect(myself).to.not.have.property('e2e');
});
it('should sort for user statuses and check if deactivated user is correctly sorted', (done) => {
const query = {
fields: JSON.stringify({
username: 1,
_id: 1,
active: 1,
status: 1,
}),
sort: JSON.stringify({
status: 1,
}),
};
void request
.get(api('users.list'))
.query(query)
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('users');
const firstUser = (res.body.users as IUser[]).find((u) => u._id === deactivatedUser._id);
expect(firstUser).to.have.property('active', false);
})
.end(done);
});
it('should query all users in the system when logged as normal user and `view-outside-room` not granted', async () => {
await updatePermission('view-outside-room', ['admin']);
await request
.get(api('users.list'))
.set(user2Credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
});
});
it('should not query users when logged as normal user, `view-outside-room` not granted and temp setting enabled', async () => {
await updatePermission('view-outside-room', ['admin']);
await updateSetting('API_Apply_permission_view-outside-room_on_users-list', true);
await request.get(api('users.list')).set(user2Credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should exclude inviteToken in the user item for privileged users', async () => {
await request
.post(api('useInviteToken'))
.set(user2Credentials)
.send({ token: inviteToken })
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('room');
expect(res.body.room).to.have.property('rid', group._id);
});
await request
.get(api('users.list'))
.set(credentials)
.expect('Content-Type', 'application/json')
.query({
fields: JSON.stringify({ inviteToken: 1 }),
sort: JSON.stringify({ inviteToken: -1 }),
count: 100,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
res.body.users.forEach((user: IUser) => {
expect(user).to.not.have.property('inviteToken');
});
});
});
it('should exclude inviteToken in the user item for normal users', async () => {
await updateSetting('API_Apply_permission_view-outside-room_on_users-list', false);
await request
.post(api('useInviteToken'))
.set(user3Credentials)
.send({ token: inviteToken })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('room');
expect(res.body.room).to.have.property('rid', group._id);
});
await request
.get(api('users.list'))
.set(user3Credentials)
.expect('Content-Type', 'application/json')
.query({
fields: JSON.stringify({ inviteToken: 1 }),
sort: JSON.stringify({ inviteToken: -1 }),
count: 100,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
res.body.users.forEach((user: IUser) => {
expect(user).to.not.have.property('inviteToken');
});
});
});
describe('querying by user email', async () => {
after(async () => {
await restorePermissionToRoles('view-full-other-user-info');
});
describe("with 'view-full-other-user-info' permission", async () => {
before(async () => {
await updatePermission('view-full-other-user-info', ['admin']);
});
it('should return the specific user with the "emails" property', async () => {
const targetEmail = targetUser.emails[0].address;
await request
.get(api('users.list'))
.query({ email: targetEmail })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users').that.is.an('array').with.lengthOf(1);
const returnedUser = res.body.users[0];
expect(returnedUser).to.have.property('_id', targetUser._id);
expect(returnedUser).to.have.property('emails').that.is.an('array');
expect(returnedUser).to.have.nested.property('emails[0].address', targetEmail);
});
});
it('should return an empty array when querying by an email that does not exist', async () => {
await request
.get(api('users.list'))
.query({ email: 'this_email_does_not_exist@invalid.com' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users').that.is.an('array').with.lengthOf(0);
expect(res.body).to.have.property('count', 0);
});
});
});
describe("without 'view-full-other-user-info' permission", async () => {
before(async () => {
await updatePermission('view-full-other-user-info', []);
});
it('should return 403 Forbidden', async () => {
await request
.get(api('users.list'))
.query({ email: targetUser.emails[0].address })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'unauthorized');
});
});
});
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.list'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('Avatars', () => {
let user: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
user = await createUser();
userCredentials = await login(user.username, password);
await Promise.all([
updateSetting('Accounts_AllowUserAvatarChange', true),
updatePermission('edit-other-user-avatar', ['admin', 'user']),
]);
});
after(() =>
Promise.all([
updateSetting('Accounts_AllowUserAvatarChange', true),
deleteUser(user),
updatePermission('edit-other-user-avatar', ['admin']),
]),
);
describe('[/users.setAvatar]', () => {
it('should set the avatar of the logged user by a local image', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(userCredentials)
.attach('image', imgURL)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should reject non-renderable image types (e.g. TIFF)', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(userCredentials)
.attach('image', tiffURL)
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-invalid-file-type');
});
it('should update the avatar of another user by userId when the logged user has the necessary permission (edit-other-user-avatar)', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(userCredentials)
.attach('image', imgURL)
.field({ userId: credentials['X-User-Id'] })
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should set the avatar of another user by username and local image when the logged user has the necessary permission (edit-other-user-avatar)', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(credentials)
.attach('image', imgURL)
.field({ username: adminUsername })
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it("should prevent from updating someone else's avatar when the logged user doesn't have the necessary permission(edit-other-user-avatar)", async () => {
await updatePermission('edit-other-user-avatar', []);
const res = await request
.post(api('users.setAvatar'))
.set(userCredentials)
.attach('image', imgURL)
.field({ userId: credentials['X-User-Id'] })
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should allow users with the edit-other-user-avatar permission to update avatars when the Accounts_AllowUserAvatarChange setting is off', async () => {
await updateSetting('Accounts_AllowUserAvatarChange', false);
await updatePermission('edit-other-user-avatar', ['admin']);
const res = await request
.post(api('users.setAvatar'))
.set(credentials)
.attach('image', imgURL)
.field({ userId: userCredentials['X-User-Id'] })
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should prevent users from passing server-side request forgery (SSRF) payloads as avatarUrl', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(credentials)
.send({
userId: userCredentials['X-User-Id'],
avatarUrl: 'http://169.254.169.254/',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.setAvatar'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.resetAvatar]', () => {
before(async () => {
await Promise.all([
updateSetting('Accounts_AllowUserAvatarChange', true),
updatePermission('edit-other-user-avatar', ['admin', 'user']),
]);
});
it('should set the avatar of the logged user by a local image', async () => {
const res = await request
.post(api('users.setAvatar'))
.set(userCredentials)
.attach('image', imgURL)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should reset the avatar of the logged user', async () => {
const res = await request
.post(api('users.resetAvatar'))
.set(userCredentials)
.expect('Content-Type', 'application/json')
.send({
userId: userCredentials['X-User-Id'],
})
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should reset the avatar of another user by userId when the logged user has the necessary permission (edit-other-user-avatar)', async () => {
const res = await request
.post(api('users.resetAvatar'))
.set(userCredentials)
.send({
userId: credentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should reset the avatar of another user by username and local image when the logged user has the necessary permission (edit-other-user-avatar)', async () => {
const res = await request
.post(api('users.resetAvatar'))
.set(credentials)
.send({
username: adminUsername,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it("should prevent from resetting someone else's avatar when the logged user doesn't have the necessary permission(edit-other-user-avatar)", async () => {
await updatePermission('edit-other-user-avatar', []);
const res = await request
.post(api('users.resetAvatar'))
.set(userCredentials)
.send({
userId: credentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should allow users with the edit-other-user-avatar permission to reset avatars when the Accounts_AllowUserAvatarChange setting is off', async () => {
await updateSetting('Accounts_AllowUserAvatarChange', false);
await updatePermission('edit-other-user-avatar', ['admin']);
const res = await request
.post(api('users.resetAvatar'))
.set(credentials)
.send({
userId: userCredentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.resetAvatar'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.getAvatar]', () => {
it('should get the url of the avatar of the logged user via userId', async () => {
await request
.get(api('users.getAvatar'))
.set(userCredentials)
.query({
userId: userCredentials['X-User-Id'],
})
.expect(307);
});
it('should get the url of the avatar of the logged user via username', async () => {
await request
.get(api('users.getAvatar'))
.set(userCredentials)
.query({
username: user.username,
})
.expect(307);
});
});
describe('[/users.getAvatarSuggestion]', () => {
it('should return 401 unauthorized when user is not logged in', (done) => {
void request.get(api('users.getAvatarSuggestion')).expect('Content-Type', 'application/json').expect(401).end(done);
});
it('should get avatar suggestion of the logged user via userId', async () => {
const res = await request
.get(api('users.getAvatarSuggestion'))
.set(userCredentials)
.query({
userId: userCredentials['X-User-Id'],
})
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('suggestions').and.to.be.an('object');
});
});
});
describe('[/users.update]', () => {
before(async () =>
Promise.all([
updateSetting('Accounts_AllowUserProfileChange', true),
updateSetting('Accounts_AllowUsernameChange', true),
updateSetting('Accounts_AllowRealNameChange', true),
updateSetting('Accounts_AllowUserStatusMessageChange', true),
updateSetting('Accounts_AllowEmailChange', true),
updateSetting('Accounts_AllowPasswordChange', true),
updatePermission('edit-other-user-info', ['admin']),
]),
);
after(async () =>
Promise.all([
updateSetting('Accounts_AllowUserProfileChange', true),
updateSetting('Accounts_AllowUsernameChange', true),
updateSetting('Accounts_AllowRealNameChange', true),
updateSetting('Accounts_AllowUserStatusMessageChange', true),
updateSetting('Accounts_AllowEmailChange', true),
updateSetting('Accounts_AllowPasswordChange', true),
updatePermission('edit-other-user-info', ['admin']),
]),
);
it("should update a user's info by userId", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
email: apiEmail,
name: `edited${apiUsername}`,
username: `edited${apiUsername}`,
password,
active: true,
roles: ['user'],
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.username', `edited${apiUsername}`);
expect(res.body).to.have.nested.property('user.emails[0].address', apiEmail);
expect(res.body).to.have.nested.property('user.active', true);
expect(res.body).to.have.nested.property('user.name', `edited${apiUsername}`);
expect(res.body).to.not.have.nested.property('user.e2e');
});
it("should update a user's email by userId", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
email: `edited${apiEmail}`,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.emails[0].address', `edited${apiEmail}`);
expect(res.body).to.have.nested.property('user.emails[0].verified', false);
expect(res.body).to.not.have.nested.property('user.e2e');
});
it("should update a user's bio by userId", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
bio: `edited-bio-test`,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.bio', 'edited-bio-test');
expect(res.body).to.not.have.nested.property('user.e2e');
});
it("should update a user's nickname by userId", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
nickname: `edited-nickname-test`,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.nickname', 'edited-nickname-test');
expect(res.body).to.not.have.nested.property('user.e2e');
});
it(`should return an error when trying to set a nickname longer than ${MAX_NICKNAME_LENGTH} characters`, async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
nickname: Random.hexString(MAX_NICKNAME_LENGTH + 1),
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', `Nickname size exceeds ${MAX_NICKNAME_LENGTH} characters [error-nickname-size-exceeded]`);
});
it(`should return an error when trying to set a bio longer than ${MAX_BIO_LENGTH} characters`, async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
bio: Random.hexString(MAX_BIO_LENGTH + 1),
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', `Bio size exceeds ${MAX_BIO_LENGTH} characters [error-bio-size-exceeded]`);
});
it('should return an error when trying to upsert a user by sending an empty userId', () => {
return request
.post(api('users.update'))
.set(credentials)
.send({
userId: '',
data: {},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
expect(res.body).to.have.property('error', 'must NOT have fewer than 1 characters');
});
});
it('should return an error when trying to use the joinDefaultChannels param, which is not intended for updates', () => {
return request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
joinDefaultChannels: true,
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
expect(res.body).to.have.property('error', 'must NOT have additional properties');
});
});
it("should update a bot's email", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: 'rocket.cat',
data: { email: 'nouser@rocket.cat' },
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it("should verify user's email by userId", async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
verified: true,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.emails[0].verified', true);
expect(res.body).to.not.have.nested.property('user.e2e');
});
it('should return an error when trying update username and it is not allowed', async () => {
await updatePermission('edit-other-user-info', ['user']);
await updateSetting('Accounts_AllowUsernameChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
username: 'fake.name',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update the user name when the required permission is applied', async () => {
await Promise.all([updatePermission('edit-other-user-info', ['admin']), updateSetting('Accounts_AllowUsernameChange', false)]);
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
username: `fake.name.${Date.now()}`,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should return an error when trying update user real name and it is not allowed', async () => {
await updatePermission('edit-other-user-info', ['user']);
await updateSetting('Accounts_AllowRealNameChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
name: 'Fake name',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update user real name when the required permission is applied', async () => {
await updatePermission('edit-other-user-info', ['admin']);
await updateSetting('Accounts_AllowRealNameChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
name: 'Fake name',
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return an error when trying update user status message and it is not allowed', async () => {
await updatePermission('edit-other-user-info', ['user']);
await updateSetting('Accounts_AllowUserStatusMessageChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
statusMessage: 'a new status',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update user status message when the required permission is applied', async () => {
await updatePermission('edit-other-user-info', ['admin']);
await updateSetting('Accounts_AllowUserStatusMessageChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
name: 'a new status',
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return an error when trying update user email and it is not allowed', async () => {
await updatePermission('edit-other-user-info', ['user']);
await updateSetting('Accounts_AllowEmailChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
email: 'itsnotworking@email.com',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update user email when the required permission is applied', async () => {
await updatePermission('edit-other-user-info', ['admin']);
await updateSetting('Accounts_AllowEmailChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
email: apiEmail,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return an error when trying update user password and it is not allowed', async () => {
await updatePermission('edit-other-user-password', ['user']);
await updateSetting('Accounts_AllowPasswordChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
password: '1tsn0tw0rkingP@ssw0rd1234.!',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update user password when the required permission is applied', async () => {
await updatePermission('edit-other-user-password', ['admin']);
await updateSetting('Accounts_AllowPasswordChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
password: '1tsn0tw0rkingP@ssw0rd1234.!',
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return an error when trying update profile and it is not allowed', async () => {
await updatePermission('edit-other-user-info', ['user']);
await updateSetting('Accounts_AllowUserProfileChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
verified: true,
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should update profile when the required permission is applied', async () => {
await updatePermission('edit-other-user-info', ['admin']);
await updateSetting('Accounts_AllowUserProfileChange', false);
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
verified: true,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should delete requirePasswordChangeReason when requirePasswordChange is set to false', async () => {
const user = await createUser({
requirePasswordChange: true,
});
await updateUserInDb(user._id, { requirePasswordChangeReason: 'any_data' });
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: user._id,
data: {
requirePasswordChange: false,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.have.property('requirePasswordChange', false);
expect(res.body.user).to.not.have.property('requirePasswordChangeReason');
});
await deleteUser(user);
});
describe('email verification', () => {
let admin: TestUser<IUser>;
let userToUpdate: TestUser<IUser>;
let userCredentials: Credentials;
beforeEach(async () => {
admin = await createUser({ roles: ['admin'] });
userToUpdate = await createUser();
userCredentials = await login(admin.username, password);
});
afterEach(async () => {
await deleteUser(userToUpdate);
await deleteUser(admin);
});
it("should update user's email verified correctly", async () => {
await request
.post(api('users.update'))
.set(userCredentials)
.send({
userId: userToUpdate._id,
data: {
verified: true,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.emails[0].verified', true);
expect(res.body).to.not.have.nested.property('user.e2e');
});
});
it("should update user's email verified even if email is not changed", async () => {
const res = await request
.post(api('users.update'))
.set(userCredentials)
.send({
userId: userToUpdate._id,
data: {
email: userToUpdate.emails[0].address,
verified: true,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.emails[0].verified', true);
expect(res.body).to.not.have.nested.property('user.e2e');
});
});
function failUpdateUser(name: string) {
it(`should not update an user if the new username is the reserved word ${name}`, async () => {
const res = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
username: name,
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Could not save user identity [error-could-not-save-identity]');
});
}
reservedWords.forEach((name) => {
failUpdateUser(name);
});
describe('Custom Fields', () => {
let testUser: TestUser<IUser>;
before(async () => {
await setCustomFields({
customFieldText1: {
type: 'text',
required: false,
},
customFieldText2: {
type: 'text',
required: false,
},
});
});
after(async () => {
await clearCustomFields();
});
beforeEach(async () => {
testUser = await createUser();
});
afterEach(async () => {
await deleteUser(testUser);
});
it('should merge custom fields instead of replacing them when updating a user', async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: 'value1',
customFieldText2: 'value2',
},
},
})
.expect(200);
const updateResponse = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: 'updated1',
},
},
})
.expect(200);
expect(updateResponse.body).to.have.property('success', true);
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText1', 'updated1');
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText2', 'value2');
const userInfoResponse = await request.get(api('users.info')).set(credentials).query({ userId: testUser._id }).expect(200);
expect(userInfoResponse.body).to.have.property('success', true);
expect(userInfoResponse.body).to.have.nested.property('user.customFields.customFieldText1', 'updated1');
expect(userInfoResponse.body).to.have.nested.property('user.customFields.customFieldText2', 'value2');
});
it('should preserve existing custom fields when adding new ones', async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: 'initial1',
},
},
})
.expect(200);
const updateResponse = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText2: 'additional2',
},
},
})
.expect(200);
expect(updateResponse.body).to.have.property('success', true);
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText1', 'initial1');
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText2', 'additional2');
});
it('should update custom field with empty string', async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: 'value1',
},
},
})
.expect(200);
const updateResponse = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: '',
},
},
})
.expect(200);
expect(updateResponse.body).to.have.property('success', true);
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText1', '');
});
it('should update custom field with null', async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: 'value1',
},
},
})
.expect(200);
const updateResponse = await request
.post(api('users.update'))
.set(credentials)
.send({
userId: testUser._id,
data: {
customFields: {
customFieldText1: null,
},
},
})
.expect(200);
expect(updateResponse.body).to.have.property('success', true);
expect(updateResponse.body).to.have.nested.property('user.customFields.customFieldText1', null);
});
});
(IS_EE ? describe : describe.skip)('Voice call extension', () => {
let user: TestUser<IUser>;
before(async () => {
user = await createUser();
});
after(async () => {
await Promise.all([deleteUser(user), updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', true)]);
});
beforeEach(async () => {
await Promise.all([updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', true)]);
});
it("should update the user's voice call extension", async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: user._id,
data: {
freeSwitchExtension: '999',
},
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.freeSwitchExtension', '999');
});
});
it("should not update the user's voice call extension if the extension is already assigned to another user", async () => {
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: targetUser._id,
data: {
freeSwitchExtension: '999',
},
})
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Extension is already assigned to another user [error-extension-not-available]');
});
});
it("should not update the user's voice call extension if voip setting is disabled", async () => {
await updateSetting('VoIP_TeamCollab_SIP_Integration_Enabled', false);
await request
.post(api('users.update'))
.set(credentials)
.send({
userId: user._id,
data: {
freeSwitchExtension: '9998',
},
})
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Edit user voice call extension is not allowed [error-action-not-allowed]');
});
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.update'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.updateOwnBasicInfo]', () => {
let user: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
user = await createUser();
userCredentials = await login(user.username, password);
});
after(() =>
Promise.all([
deleteUser(user),
updateSetting('E2E_Enable', false),
updateSetting('Accounts_AllowRealNameChange', true),
updateSetting('Accounts_AllowUsernameChange', true),
updateSetting('Accounts_AllowUserStatusMessageChange', true),
updateSetting('Accounts_AllowEmailChange', true),
updateSetting('Accounts_AllowPasswordChange', true),
]),
);
const newPassword = `${password}test`;
const currentPassword = crypto.createHash('sha256').update(password, 'utf8').digest('hex');
const editedUsername = `basicInfo.name${+new Date()}`;
const editedName = `basic-info-test-name${+new Date()}`;
const editedEmail = `test${+new Date()}@mail.com`;
it('enabling E2E in server and generating keys to user...', async () => {
await updateSetting('E2E_Enable', true);
const res = await request
.post(api('e2e.setUserPublicAndPrivateKeys'))
.set(userCredentials)
.send({
private_key: 'test',
public_key: 'test',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should update the user own basic information', async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
name: editedName,
username: editedUsername,
currentPassword,
newPassword,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
const { user } = res.body;
expect(res.body).to.have.property('success', true);
expect(user.username).to.be.equal(editedUsername);
expect(user.name).to.be.equal(editedName);
expect(user).to.not.have.property('e2e');
});
it('should update the user name only', async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
username: editedUsername,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
const { user } = res.body;
expect(res.body).to.have.property('success', true);
expect(user.username).to.be.equal(editedUsername);
expect(user).to.not.have.property('e2e');
});
it('should throw an error when user try change email without the password', async () => {
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
email: editedEmail,
},
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should throw an error when user try change password without the actual password', async () => {
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
newPassword: '1Tsn3wP@ssw0rd1234.!',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should throw an error when the name is only whitespaces', async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
name: ' ',
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it("should set new email as 'unverified'", async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
email: editedEmail,
currentPassword: crypto.createHash('sha256').update(newPassword, 'utf8').digest('hex'),
},
})
.expect('Content-Type', 'application/json')
.expect(200);
const { user } = res.body;
expect(res.body).to.have.property('success', true);
expect(user.emails[0].address).to.be.equal(editedEmail);
expect(user.emails[0].verified).to.be.false;
expect(user).to.not.have.property('e2e');
});
it("should not include sensitive data on the 'services' object from the response", async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
username: editedUsername,
},
})
.expect('Content-Type', 'application/json')
.expect(200);
const { user } = res.body;
expect(res.body).to.have.property('success', true);
expect(user.services).to.not.have.property('passwordHistory');
expect(user.services).to.not.have.property('email');
expect(user.services.password).to.have.property('exists').that.is.a('boolean');
});
function failUpdateUserOwnBasicInfo(name: string) {
it(`should not update an user's basic info if the new username is the reserved word ${name}`, async () => {
const res = await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
username: name,
},
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Could not save user identity [error-could-not-save-identity]');
});
}
reservedWords.forEach((name) => {
failUpdateUserOwnBasicInfo(name);
});
it('should throw an error if not allowed to change real name', async () => {
await updateSetting('Accounts_AllowRealNameChange', false);
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
name: 'edited name',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if not allowed to change username', async () => {
await updateSetting('Accounts_AllowUsernameChange', false);
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
username: 'edited.user.name',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if not allowed to change statusText', async () => {
await updateSetting('Accounts_AllowUserStatusMessageChange', false);
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
statusText: 'My custom status',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if not allowed to change email', async () => {
await updateSetting('Accounts_AllowEmailChange', false);
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
email: 'changed@email.com',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if not allowed to change password', async () => {
await updateSetting('Accounts_AllowPasswordChange', false);
await request
.post(api('users.updateOwnBasicInfo'))
.set(credentials)
.send({
data: {
newPassword: '1Tsn3wP@ssw0rd1234.!',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should be able to erase bio and nickname', async () => {
const user = await createUser({
bio: `edited-bio-test`,
nickname: `edited-nickname-test`,
});
const userCredentials = await login(user.username, password);
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
bio: '',
nickname: '',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const userData = await getUserByUsername(user.username);
expect(userData).to.not.have.property('bio');
expect(userData).to.not.have.property('nickname');
});
describe('[Password Policy]', () => {
let previousMinLength: Awaited<ReturnType<typeof getSettingValueById>>;
let previousMaxLength: Awaited<ReturnType<typeof getSettingValueById>>;
before(async () => {
await updateSetting('Accounts_AllowPasswordChange', true);
await updateSetting('Accounts_TwoFactorAuthentication_Enabled', false);
[previousMinLength, previousMaxLength] = await Promise.all([
getSettingValueById('Accounts_Password_Policy_MinLength'),
getSettingValueById('Accounts_Password_Policy_MaxLength'),
]);
});
after(async () => {
await updateSetting('Accounts_AllowPasswordChange', true);
await updateSetting('Accounts_TwoFactorAuthentication_Enabled', true);
await updateSetting('Accounts_Password_Policy_MaxLength', previousMaxLength);
await updateSetting('Accounts_Password_Policy_MinLength', previousMinLength);
});
it('should throw an error if the password length is less than the minimum length', async () => {
const expectedError = {
error: 'error-password-policy-not-met-minLength',
message: 'The password does not meet the minimum length password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: '2',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password length is greater than the maximum length', async () => {
// max must stay >= min, so lower the minimum before capping the maximum at 5
await updateSetting('Accounts_Password_Policy_MinLength', 1);
await updateSetting('Accounts_Password_Policy_MaxLength', 5);
const expectedError = {
error: 'error-password-policy-not-met-maxLength',
message: 'The password does not meet the maximum length password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'Abc@12345678',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password contains repeating characters', async () => {
const expectedError = {
error: 'error-password-policy-not-met-repeatingCharacters',
message: 'The password contains repeating characters which is against the password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'A@123aaaa',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password does not contain at least one lowercase character', async () => {
const expectedError = {
error: 'error-password-policy-not-met-oneLowercase',
message: 'The password does not contain at least one lowercase character which is against the password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'PASSWORD@123',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password does not contain at least one uppercase character', async () => {
const expectedError = {
error: 'error-password-policy-not-met-oneUppercase',
message: 'The password does not contain at least one uppercase character which is against the password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'password@123',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password does not contain at least one numerical character', async () => {
const expectedError = {
error: 'error-password-policy-not-met-oneNumber',
message: 'The password does not contain at least one numerical character which is against the password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'Password@',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should throw an error if the password does not contain at least one special character', async () => {
const expectedError = {
error: 'error-password-policy-not-met-oneSpecial',
message: 'The password does not contain at least one special character which is against the password policy.',
};
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: 'Password123',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
expect(res.body.errorType).to.be.equal('error-password-policy-not-met');
expect(res.body.details).to.be.an('array').that.deep.includes(expectedError);
})
.expect(400);
});
it('should be able to update if the password meets all the validation rules', async () => {
await updateSetting('Accounts_Password_Policy_MaxLength', -1);
await request
.post(api('users.updateOwnBasicInfo'))
.set(userCredentials)
.send({
data: {
currentPassword,
newPassword: '1Tsn3wP@ssw0rd1234.!',
},
})
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
})
.expect(200);
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.updateOwnBasicInfo'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
// TODO check for all response fields
describe('[/users.setPreferences]', () => {
after(() => updatePermission('edit-other-user-info', ['admin']));
it('should return an error when the user try to update info of another user and does not have the necessary permission', (done) => {
const userPreferences = {
userId: 'rocket.cat',
data: {
...preferences.data,
},
};
void updatePermission('edit-other-user-info', []).then(() => {
void request
.post(api('users.setPreferences'))
.set(credentials)
.send(userPreferences)
.expect(400)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Editing user is not allowed [error-action-not-allowed]');
expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
})
.end(done);
});
});
it('should return an error when the user try to update info of an nonexistent user', (done) => {
const userPreferences = {
userId: 'invalid-id',
data: {
...preferences.data,
},
};
void updatePermission('edit-other-user-info', ['admin', 'user']).then(() => {
void request
.post(api('users.setPreferences'))
.set(credentials)
.send(userPreferences)
.expect(400)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property(
'error',
'The optional "userId" param provided does not match any users [error-invalid-user]',
);
expect(res.body).to.have.property('errorType', 'error-invalid-user');
})
.end(done);
});
});
it('should set some preferences of another user successfully', (done) => {
const userPreferences = {
userId: 'rocket.cat',
data: {
...preferences.data,
},
};
void updatePermission('edit-other-user-info', ['admin', 'user']).then(() => {
void request
.post(api('users.setPreferences'))
.set(credentials)
.send(userPreferences)
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body.user).to.have.property('settings');
expect(res.body.user.settings).to.have.property('preferences');
expect(res.body.user._id).to.be.equal('rocket.cat');
expect(res.body).to.have.property('success', true);
})
.end(done);
});
});
it('should set some preferences by user when execute successfully', (done) => {
const userPreferences = {
userId: credentials['X-User-Id'],
data: {
...preferences.data,
},
};
void request
.post(api('users.setPreferences'))
.set(credentials)
.send(userPreferences)
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body.user).to.have.property('settings');
expect(res.body.user.settings).to.have.property('preferences');
expect(res.body).to.have.property('success', true);
})
.end(done);
});
it('should set some preferences and language preference by user when execute successfully', (done) => {
const userPreferences = {
userId: credentials['X-User-Id'],
data: {
...preferences.data,
language: 'en',
},
};
void request
.post(api('users.setPreferences'))
.set(credentials)
.send(userPreferences)
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.user.settings.preferences).to.have.property('language', 'en');
})
.end(done);
});
it('should persist the utcOffset preference on the user document', async () => {
await request
.post(api('users.setPreferences'))
.set(credentials)
.send({ data: { utcOffset: 5 } })
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('me'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('utcOffset', 5);
});
});
it('should fail when utcOffset is not a number', async () => {
await request
.post(api('users.setPreferences'))
.set(credentials)
.send({ data: { utcOffset: 'not-a-number' } })
.expect(400)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.setPreferences'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.setPreferences'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.getPreferences]', () => {
it('should return all preferences when execute successfully', (done) => {
const userPreferences = {
...preferences.data,
language: 'en',
};
void request
.get(api('users.getPreferences'))
.set(credentials)
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body.preferences).to.be.eql(userPreferences);
expect(res.body).to.have.property('success', true);
})
.end(done);
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.getPreferences'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.forgotPassword]', () => {
it('should return an error when "Accounts_PasswordReset" is disabled', async () => {
await updateSetting('Accounts_PasswordReset', false);
const res = await request
.post(api('users.forgotPassword'))
.send({
email: adminEmail,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Password reset is not enabled');
});
it('should send email to user (return success), when is a valid email', async () => {
await updateSetting('Accounts_PasswordReset', true);
const res = await request
.post(api('users.forgotPassword'))
.send({
email: adminEmail,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should not send email to user(return error), when is a invalid email', async () => {
const res = await request
.post(api('users.forgotPassword'))
.send({
email: 'invalidEmail',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should return an error when email is missing', async () => {
const res = await request.post(api('users.forgotPassword')).send({}).expect('Content-Type', 'application/json').expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
expect(res.body).to.have.property('error', "must have required property 'email'");
});
});
describe('[/users.sendConfirmationEmail]', () => {
[
{ description: 'authenticated + known email', auth: true, email: adminEmail },
{ description: 'unauthenticated + known email', auth: false, email: adminEmail },
{ description: 'unauthenticated + unknown email', auth: false, email: 'nobody@example.invalid' },
].forEach(({ description, auth, email }) => {
it(`should return 200 success for ${description}`, async () => {
const req = request.post(api('users.sendConfirmationEmail')).send({ email });
if (auth) {
req.set(credentials);
}
await req
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.sendConfirmationEmail'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.getUsernameSuggestion]', () => {
const testUsername = `test${+new Date()}`;
let targetUser: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
targetUser = await registerUser(
{
email: `${testUsername}.@test.com`,
username: `${testUsername}test`,
name: testUsername,
pass: password,
},
null,
);
userCredentials = await login(targetUser.username, password);
});
after(() => deleteUser(targetUser));
it('should return an username suggestion', async () => {
const res = await request
.get(api('users.getUsernameSuggestion'))
.set(userCredentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.exist;
});
it('should return 401 when not authenticated', async () => {
const res = await request.get(api('users.getUsernameSuggestion')).expect('Content-Type', 'application/json').expect(401);
expect(res.body).to.have.property('status', 'error');
expect(res.body).to.have.property('message');
});
});
describe('[/users.checkUsernameAvailability]', () => {
let targetUser: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
targetUser = await registerUser(undefined, null);
userCredentials = await login(targetUser.username, password);
});
after(() => deleteUser(targetUser));
it('should return 401 unauthorized when user is not logged in', async () => {
const res = await request.get(api('users.checkUsernameAvailability')).expect('Content-Type', 'application/json').expect(401);
expect(res.body).to.have.property('message');
});
it('should return true if the username is the same user username set', async () => {
const res = await request
.get(api('users.checkUsernameAvailability'))
.set(userCredentials)
.query({
username: targetUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body.result).to.be.equal(true);
});
it('should return true if the username is available', async () => {
const res = await request
.get(api('users.checkUsernameAvailability'))
.set(userCredentials)
.query({
username: `${targetUser.username}-${+new Date()}`,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body.result).to.be.equal(true);
});
it('should return an error when the username is invalid', async () => {
const res = await request
.get(api('users.checkUsernameAvailability'))
.set(userCredentials)
.query({})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
});
describe('[/users.deleteOwnAccount]', () => {
let targetUser: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
targetUser = await registerUser(undefined, null);
userCredentials = await login(targetUser.username, password);
});
after(async () => deleteUser(targetUser));
it('Enable "Accounts_AllowDeleteOwnAccount" setting...', (done) => {
void request
.post('/api/v1/settings/Accounts_AllowDeleteOwnAccount')
.set(credentials)
.send({ value: true })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
})
.end(wait(done, 200));
});
it('should delete user own account', async () => {
const res = await request
.post(api('users.deleteOwnAccount'))
.set(userCredentials)
.send({
password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'),
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should delete user own account when the SHA256 hash is in upper case', async () => {
const user = await createUser();
const createdUserCredentials = await login(user.username, password);
await request
.post(api('users.deleteOwnAccount'))
.set(createdUserCredentials)
.send({
password: crypto.createHash('sha256').update(password, 'utf8').digest('hex').toUpperCase(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await deleteUser(user);
});
describe('last owner cases', () => {
let user: TestUser<IUser>;
let createdUserCredentials: Credentials;
let room: IRoom;
beforeEach(async () => {
user = await createUser();
createdUserCredentials = await login(user.username, password);
room = (
await createRoom({
type: 'c',
name: `channel.test.${Date.now()}-${Math.random()}`,
username: user.username,
members: [user.username],
})
).body.channel;
await addRoomOwner({ type: 'c', roomId: room._id, userId: user._id });
await removeRoomOwner({ type: 'c', roomId: room._id, userId: credentials['X-User-Id'] });
});
afterEach(async () => {
await deleteRoom({ type: 'c', roomId: room._id });
await deleteUser(user);
});
it('should return an error when trying to delete user own account if user is the last room owner', async () => {
await request
.post(api('users.deleteOwnAccount'))
.set(createdUserCredentials)
.send({
password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'),
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', '[user-last-owner]');
expect(res.body).to.have.property('errorType', 'user-last-owner');
});
});
it('should delete user own account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => {
await request
.post(api('users.deleteOwnAccount'))
.set(createdUserCredentials)
.send({
password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'),
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should assign a new owner to the room if the last room owner is deleted', async () => {
await request
.post(api('users.deleteOwnAccount'))
.set(createdUserCredentials)
.send({
password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'),
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const roles = await getChannelRoles({ roomId: room._id });
expect(roles).to.have.lengthOf(1);
expect(roles[0].roles).to.eql(['owner']);
expect(roles[0].u).to.have.property('_id', credentials['X-User-Id']);
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.deleteOwnAccount'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.deleteOwnAccount'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.delete]', () => {
let newUser: TestUser<IUser>;
before(async () => {
newUser = await createUser();
});
after(async () => {
await deleteUser(newUser);
await updatePermission('delete-user', ['admin']);
});
it('should return an error when trying delete user account without "delete-user" permission', async () => {
await updatePermission('delete-user', ['user']);
await request
.post(api('users.delete'))
.set(credentials)
.send({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
});
it('should delete user account when logged user has "delete-user" permission', async () => {
await updatePermission('delete-user', ['admin']);
await request
.post(api('users.delete'))
.set(credentials)
.send({
userId: newUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
describe('last owner cases', () => {
let targetUser: TestUser<IUser>;
let room: IRoom;
beforeEach(async () => {
targetUser = await registerUser(undefined, null);
room = (
await createRoom({
type: 'c',
name: `channel.test.${Date.now()}-${Math.random()}`,
members: [targetUser.username],
})
).body.channel;
await addRoomOwner({ type: 'c', roomId: room._id, userId: targetUser._id });
await removeRoomOwner({ type: 'c', roomId: room._id, userId: credentials['X-User-Id'] });
});
afterEach(() => Promise.all([deleteRoom({ type: 'c', roomId: room._id }), deleteUser(targetUser, { confirmRelinquish: true })]));
it('should return an error when trying to delete user account if the user is the last room owner', async () => {
await updatePermission('delete-user', ['admin']);
await request
.post(api('users.delete'))
.set(credentials)
.send({
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', '[user-last-owner]');
expect(res.body).to.have.property('errorType', 'user-last-owner');
});
});
it('should delete user account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => {
await updatePermission('delete-user', ['admin']);
await request
.post(api('users.delete'))
.set(credentials)
.send({
userId: targetUser._id,
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should assign a new owner to the room if the last room owner is deleted', async () => {
await updatePermission('delete-user', ['admin']);
await request
.post(api('users.delete'))
.set(credentials)
.send({
userId: targetUser._id,
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const roles = await getChannelRoles({ roomId: room._id });
expect(roles).to.have.lengthOf(1);
expect(roles[0].roles).to.eql(['owner']);
expect(roles[0].u).to.have.property('_id', credentials['X-User-Id']);
});
});
});
describe('Personal Access Tokens', () => {
const tokenName = `${Date.now()}token`;
describe('successful cases', () => {
before(() => updatePermission('create-personal-access-tokens', ['admin']));
after(() => updatePermission('create-personal-access-tokens', ['admin']));
it('should accept loginToken when we call /me', async () => {
let loginToken = '';
await request
.post(api('users.generatePersonalAccessToken'))
.set(credentials)
.send({
tokenName: 'test',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('token');
loginToken = res.body.token;
});
await request
.get(api('me')) // it does not really matter what we call here, we just want to test that the loginToken is accepted
.set({
'X-Auth-Token': loginToken,
'X-User-Id': credentials['X-User-Id'],
})
.expect(200);
});
describe('[/users.getPersonalAccessTokens]', () => {
it('should return an array when the user does not have personal tokens configured', async () => {
const res = await request
.get(api('users.getPersonalAccessTokens'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('tokens').and.to.be.an('array');
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.getPersonalAccessTokens'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.generatePersonalAccessToken]', () => {
it('should return a personal access token to user', async () => {
const res = await request
.post(api('users.generatePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('token');
});
it('should throw an error when user tries generate a token with the same name', async () => {
const res = await request
.post(api('users.generatePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.generatePersonalAccessToken'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.generatePersonalAccessToken'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.regeneratePersonalAccessToken]', () => {
it('should return a personal access token to user when user regenerates the token', async () => {
const res = await request
.post(api('users.regeneratePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('token');
});
it('should throw an error when user tries regenerate a token that does not exist', async () => {
const res = await request
.post(api('users.regeneratePersonalAccessToken'))
.set(credentials)
.send({
tokenName: 'tokenthatdoesnotexist',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.regeneratePersonalAccessToken'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.regeneratePersonalAccessToken'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.getPersonalAccessTokens]', () => {
it('should return my personal access tokens', async () => {
const res = await request
.get(api('users.getPersonalAccessTokens'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('tokens').and.to.be.an('array');
});
});
describe('[/users.removePersonalAccessToken]', () => {
it('should return success when user remove a personal access token', async () => {
const res = await request
.post(api('users.removePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
});
it('should throw an error when user tries remove a token that does not exist', async () => {
const res = await request
.post(api('users.removePersonalAccessToken'))
.set(credentials)
.send({
tokenName: 'tokenthatdoesnotexist',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.removePersonalAccessToken'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.removePersonalAccessToken'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
});
describe('unsuccessful cases', () => {
before(() => updatePermission('create-personal-access-tokens', []));
after(() => updatePermission('create-personal-access-tokens', ['admin']));
describe('should return an error when the user dont have the necessary permission "create-personal-access-tokens"', () => {
it('/users.generatePersonalAccessToken', async () => {
const res = await request
.post(api('users.generatePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('not-authorized');
});
it('/users.regeneratePersonalAccessToken', async () => {
const res = await request
.post(api('users.regeneratePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('not-authorized');
});
it('/users.getPersonalAccessTokens', async () => {
const res = await request
.get(api('users.getPersonalAccessTokens'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
});
it('/users.removePersonalAccessToken', async () => {
const res = await request
.post(api('users.removePersonalAccessToken'))
.set(credentials)
.send({
tokenName,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('not-authorized');
});
it('should throw an error when user tries remove a token that does not exist', async () => {
const res = await request
.post(api('users.removePersonalAccessToken'))
.set(credentials)
.send({
tokenName: 'tokenthatdoesnotexist',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('not-authorized');
});
});
});
});
describe('[/users.setActiveStatus]', () => {
let user: TestUser<IUser>;
let agent: IUserWithCredentials;
let agentUser: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
agentUser = await createUser();
const agentUserCredentials = await login(agentUser.username, password);
await createAgent(agentUser.username);
await makeAgentAvailable(agentUserCredentials);
agent = {
user: agentUser,
credentials: agentUserCredentials,
};
});
before(async () => {
user = await createUser();
userCredentials = await login(user.username, password);
await Promise.all([
updatePermission('edit-other-user-active-status', ['admin', 'user']),
updatePermission('manage-moderation-actions', ['admin']),
]);
});
after(() =>
Promise.all([
deleteUser(user),
updatePermission('edit-other-user-active-status', ['admin']),
updatePermission('manage-moderation-actions', ['admin']),
]),
);
after(() => Promise.all([removeAgent(agent.user._id), deleteUser(agent.user)]));
it('should set other user active status to false when the logged user has the necessary permission(edit-other-user-active-status)', async () => {
const res = await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.active', false);
});
it('should set other user active status to true when the logged user has the necessary permission(edit-other-user-active-status)', async () => {
const res = await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: true,
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('user.active', true);
});
it('should return an error when trying to set other user active status and has not the necessary permission(edit-other-user-active-status)', async () => {
await updatePermission('edit-other-user-active-status', []);
const res = await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
it('should return an error when trying to set user own active status and has not the necessary permission(edit-other-user-active-status)', async () => {
const res = await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: user._id,
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
it('should set user own active status to false when the user has the necessary permission(edit-other-user-active-status)', async () => {
await updatePermission('edit-other-user-active-status', ['admin']);
const res = await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: user._id,
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
it('users should retain their roles when they are deactivated', async () => {
const testUser = await createUser({ roles: ['user', 'livechat-agent'] });
await request
.post(api('users.setActiveStatus'))
.set(credentials)
.send({
activeStatus: false,
userId: testUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
const user = await getUserByUsername(testUser.username);
expect(user).to.have.property('roles');
expect(user.roles).to.be.an('array').of.length(2);
expect(user.roles).to.include('user', 'livechat-agent');
await deleteUser(testUser);
});
it('should make agents not-available when the user is deactivated', async () => {
await makeAgentAvailable(agent.credentials);
await request
.post(api('users.setActiveStatus'))
.set(credentials)
.send({
activeStatus: false,
userId: agent.user._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
const agentInfo = await getAgent(agent.user._id);
expect(agentInfo).to.have.property('statusLivechat', 'not-available');
});
it('should not make agents available when the user is activated', async () => {
let agentInfo = await getAgent(agent.user._id);
expect(agentInfo).to.have.property('statusLivechat', 'not-available');
await request
.post(api('users.setActiveStatus'))
.set(credentials)
.send({
activeStatus: true,
userId: agent.user._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
agentInfo = await getAgent(agent.user._id);
expect(agentInfo).to.have.property('statusLivechat', 'not-available');
});
describe('last owner cases', () => {
let room: IRoom;
beforeEach(() =>
Promise.all([
updatePermission('edit-other-user-active-status', ['admin', 'user']),
updatePermission('manage-moderation-actions', ['admin', 'user']),
]),
);
afterEach(() => deleteRoom({ type: 'c', roomId: room._id }));
it('should return an error when trying to set other user status to inactive and the user is the last owner of a room', async () => {
room = (
await createRoom({
type: 'c',
name: `channel.test.${Date.now()}-${Math.random()}`,
username: targetUser.username,
members: [targetUser.username],
})
).body.channel;
await inviteToChannel({ userId: targetUser._id, roomId: room._id });
await addRoomOwner({ type: 'c', userId: targetUser._id, roomId: room._id });
await removeRoomOwner({ type: 'c', userId: credentials['X-User-Id'], roomId: room._id });
await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', '[user-last-owner]');
expect(res.body).to.have.property('errorType', 'user-last-owner');
});
});
it('should set other user status to inactive if the user is the last owner of a room and `confirmRelinquish` is set to `true`', async () => {
room = (
await createRoom({
type: 'c',
name: `channel.test.${Date.now()}-${Math.random()}`,
username: targetUser.username,
members: [targetUser.username],
})
).body.channel;
await inviteToChannel({ userId: targetUser._id, roomId: room._id });
await addRoomOwner({ type: 'c', userId: targetUser._id, roomId: room._id });
await removeRoomOwner({ type: 'c', userId: credentials['X-User-Id'], roomId: room._id });
await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: targetUser._id,
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should set other user as room owner if the last owner of a room is deactivated and `confirmRelinquish` is set to `true`', async () => {
room = (
await createRoom({
type: 'c',
name: `channel.test.${Date.now()}-${Math.random()}`,
members: [targetUser.username],
})
).body.channel;
await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: true,
userId: targetUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await inviteToChannel({ userId: targetUser._id, roomId: room._id });
await addRoomOwner({ type: 'c', userId: targetUser._id, roomId: room._id });
await removeRoomOwner({ type: 'c', userId: credentials['X-User-Id'], roomId: room._id });
await request
.post(api('users.setActiveStatus'))
.set(userCredentials)
.send({
activeStatus: false,
userId: targetUser._id,
confirmRelinquish: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const roles = await getChannelRoles({ roomId: room._id });
expect(roles).to.have.lengthOf(2);
const originalCreator = roles.find((role) => role.u._id === credentials['X-User-Id']);
assert.isDefined(originalCreator);
expect(originalCreator.roles).to.eql(['owner']);
expect(originalCreator.u).to.have.property('_id', credentials['X-User-Id']);
});
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.setActiveStatus'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.deactivateIdle]', () => {
let testUser: TestUser<IUser>;
const testRoleId = 'guest';
before('Create test user', async () => {
testUser = await createUser();
await request
.post(api('roles.addUserToRole'))
.set(credentials)
.send({
roleId: testRoleId,
username: testUser.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
after(() => Promise.all([deleteUser(testUser), updatePermission('edit-other-user-active-status', ['admin'])]));
it('should fail to deactivate if user doesnt have edit-other-user-active-status permission', async () => {
await updatePermission('edit-other-user-active-status', []);
const res = await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({
daysIdle: 0,
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
it('should deactivate no users when no users in time range', async () => {
await updatePermission('edit-other-user-active-status', ['admin']);
const res = await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({
daysIdle: 999999,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count', 0);
});
it('should deactivate the test user when given its role and daysIdle = 0', async () => {
await updatePermission('edit-other-user-active-status', ['admin']);
const res = await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({
daysIdle: 0,
role: testRoleId,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count', 1);
});
it('should not deactivate the test user again when given its role and daysIdle = 0', async () => {
await updatePermission('edit-other-user-active-status', ['admin']);
const res = await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({
daysIdle: 0,
role: testRoleId,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count', 0);
});
it('should return 400 when body is empty', async () => {
await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should revoke login tokens of deactivated idle users', async () => {
const idleUser = await createUser();
await request.post(api('roles.addUserToRole')).set(credentials).send({ roleId: testRoleId, username: idleUser.username }).expect(200);
const idleUserCredentials = await login(idleUser.username, password);
await request.get(api('me')).set(idleUserCredentials).expect(200);
await updatePermission('edit-other-user-active-status', ['admin']);
await request
.post(api('users.deactivateIdle'))
.set(credentials)
.send({ daysIdle: 0, role: testRoleId })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count').that.is.greaterThan(0);
});
await request.get(api('me')).set(idleUserCredentials).expect(401);
await deleteUser(idleUser);
});
});
describe('[/users.requestDataDownload]', () => {
it('should return the request data with fullExport false when no query parameter was send', async () => {
const res = await request
.get(api('users.requestDataDownload'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('requested');
expect(res.body).to.have.property('exportOperation').and.to.be.an('object');
expect(res.body.exportOperation).to.have.property('fullExport', false);
});
it('should return the request data with fullExport false when the fullExport query parameter is false', async () => {
const res = await request
.get(api('users.requestDataDownload'))
.query({ fullExport: 'false' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('requested');
expect(res.body).to.have.property('exportOperation').and.to.be.an('object');
expect(res.body.exportOperation).to.have.property('fullExport', false);
});
it('should return the request data with fullExport true when the fullExport query parameter is true', async () => {
const res = await request
.get(api('users.requestDataDownload'))
.query({ fullExport: 'true' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('requested');
expect(res.body).to.have.property('exportOperation').and.to.be.an('object');
expect(res.body.exportOperation).to.have.property('fullExport', true);
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.requestDataDownload'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.logoutOtherClients]', function () {
let user: TestUser<IUser>;
let userCredentials: Credentials;
let newCredentials: Credentials;
this.timeout(20000);
before(async () => {
user = await createUser({ joinDefaultChannels: false });
userCredentials = await login(user.username, password);
newCredentials = await login(user.username, password);
});
after(() => deleteUser(user));
it('should invalidate all active sesions', (done) => {
/* We want to validate that the login with the "old" credentials fails
However, the removal of the tokens is done asynchronously.
Thus, we check that within the next seconds, at least one try to
access an authentication requiring route fails */
let counter = 0;
async function checkAuthenticationFails() {
const result = await request.get(api('me')).set(userCredentials);
return result.statusCode === 401;
}
async function tryAuthentication() {
if (await checkAuthenticationFails()) {
done();
} else if (++counter < 20) {
setTimeout(tryAuthentication, 1000);
} else {
done('Session did not invalidate in time');
}
}
void request
.post(api('users.logoutOtherClients'))
.set(newCredentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('token').and.to.be.a('string');
expect(res.body).to.have.property('tokenExpires').and.to.be.a('string');
})
.then(tryAuthentication);
});
it('should remove only logged out session push tokens', async () => {
const credentials1 = await login(user.username, password);
const credentials2 = await login(user.username, password);
await request
.post(api('push.token'))
.set(credentials1)
.send({
type: 'gcm',
value: 'device-1-token',
appName: 'com.example.device1',
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.post(api('push.token'))
.set(credentials2)
.send({
type: 'gcm',
value: 'device-2-token',
appName: 'com.example.device2',
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.post(api('users.logoutOtherClients'))
.set(credentials2)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await new Promise((resolve) => setTimeout(resolve, 1000));
await request
.delete(api('push.token'))
.set(credentials2)
.send({
token: 'device-1-token',
})
.expect(404)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
await request
.delete(api('push.token'))
.set(credentials2)
.send({
token: 'device-2-token',
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.logoutOtherClients'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/api/v1/logout]', () => {
let user: TestUser<IUser>;
before(async () => {
user = await createUser();
});
after(() => deleteUser(user));
it('should logout the current user and invalidate the session', async () => {
const userCredentials = await login(user.username, password);
await request.post(api('logout')).set(userCredentials).expect('Content-Type', 'application/json').expect(200);
const meRes = await request.get(api('me')).set(userCredentials);
expect(meRes.statusCode).to.equal(401);
});
(IS_EE ? it : it.skip)('should remove the session from the list after logout', async () => {
const credentials = await login(user.username, password);
const authToken = credentials['X-Auth-Token'];
await request.post(api('login')).send({ resume: authToken }).expect(200);
const sessionsBefore = await request.get(api('sessions/list')).set(credentials).expect(200);
expect(sessionsBefore.body.sessions).to.have.lengthOf(1);
const sessionIdBefore = sessionsBefore.body.sessions[0]._id;
await request.post(api('logout')).set(credentials).expect(200);
const newCredentials = await login(user.username, password);
const newAuthToken = newCredentials['X-Auth-Token'];
await request.post(api('login')).send({ resume: newAuthToken }).expect(200);
const sessionsAfter = await request.get(api('sessions/list')).set(newCredentials).expect(200);
expect(sessionsAfter.body.sessions).to.have.lengthOf(1);
expect(sessionsAfter.body.sessions[0]._id).to.not.equal(sessionIdBefore);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('logout'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.autocomplete]', () => {
after(() => updatePermission('view-outside-room', ['admin', 'owner', 'moderator', 'user']));
describe('[without permission]', function () {
let user: TestUser<IUser>;
let userCredentials: Credentials;
let user2: TestUser<IUser>;
let user2Credentials: Credentials;
let roomId: IRoom['_id'];
this.timeout(20000);
before(async () => {
const users = await Promise.all([createUser({ joinDefaultChannels: false }), createUser({ joinDefaultChannels: false })]);
user = users[0];
user2 = users[1];
const credentials = await Promise.all([
login(user.username, password),
login(user2.username, password),
await updatePermission('view-outside-room', []),
]);
userCredentials = credentials[0];
user2Credentials = credentials[1];
roomId = (await createRoom({ type: 'c', credentials: userCredentials, name: `channel.autocomplete.${Date.now()}` })).body.channel
._id;
});
after(async () => {
await Promise.all([deleteRoom({ type: 'c', roomId }), deleteUser(user), deleteUser(user2)]);
});
it('should return an empty list when the user does not have any subscription', async () => {
const res = await request
.get(api('users.autocomplete'))
.query({ selector: '{}' })
.set(userCredentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('items').and.to.be.an('array').that.has.lengthOf(0);
});
it('should return users that are subscribed to the same rooms as the requester', async () => {
await joinChannel({ overrideCredentials: user2Credentials, roomId });
void request
.get(api('users.autocomplete'))
.query({ selector: '{}' })
.set(userCredentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('items').and.to.be.an('array').with.lengthOf(1);
});
});
});
describe('[with permission]', () => {
before(async () => updatePermission('view-outside-room', ['admin', 'user']));
it('should return an error when the required parameter "selector" is not provided', async () => {
const res = await request
.get(api('users.autocomplete'))
.query({})
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
it('should return the users to fill auto complete', async () => {
const res = await request
.get(api('users.autocomplete'))
.query({ selector: '{}' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('items').and.to.be.an('array');
});
(IS_EE ? it : it.skip)('should return users filtered by freeSwitchExtension and display it', async () => {
const user = await createUser({ joinDefaultChannels: false, freeSwitchExtension: '1234567890' });
await request
.get(api('users.autocomplete'))
.query({ selector: JSON.stringify({ conditions: { freeSwitchExtension: '1234567890' } }) })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('items').and.to.be.an('array').with.lengthOf(1);
expect(res.body.items[0]).to.have.property('freeSwitchExtension', '1234567890');
});
await deleteUser(user);
});
it('should filter results when using allowed operators', async () => {
const res = await request
.get(api('users.autocomplete'))
.set(credentials)
.query({
selector: JSON.stringify({
conditions: {
$and: [
{
active: false,
},
{
status: 'online',
},
],
},
}),
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('items').and.to.be.an('array').with.lengthOf(0);
});
it('should return an error when using forbidden operators', async () => {
const res = await request
.get(api('users.autocomplete'))
.set(credentials)
.query({
selector: JSON.stringify({
conditions: {
$nor: [
{
username: {
$exists: false,
},
},
{
status: {
$exists: false,
},
},
],
},
}),
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
});
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.autocomplete'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.getStatus]', () => {
it('should return my own status', async () => {
const res = await request.get(api('users.getStatus')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('_id', credentials['X-User-Id']);
expect(res.body).to.have.property('status');
});
it('should return other user status', async () => {
const res = await request
.get(api('users.getStatus'))
.query({ userId: 'rocket.cat' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('_id', 'rocket.cat');
expect(res.body).to.have.property('status');
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.getStatus'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.setStatus]', () => {
let user: TestUser<IUser>;
before(async () => {
user = await createUser();
});
after(() => Promise.all([deleteUser(user), updateSetting('Accounts_AllowUserStatusMessageChange', true)]));
it('should return an error when the setting "Accounts_AllowUserStatusMessageChange" is disabled', async () => {
await updateSetting('Accounts_AllowUserStatusMessageChange', false);
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
message: 'test',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-not-allowed');
expect(res.body.error).to.be.equal('Change status is not allowed [error-not-allowed]');
});
it('should update my own status', async () => {
await updateSetting('Accounts_AllowUserStatusMessageChange', true);
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
message: 'test',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
void getUserStatus(credentials['X-User-Id']).then((status) => expect(status.status).to.be.equal('busy'));
});
it('should return an error when trying to update other user status without the required permission', async () => {
await updatePermission('edit-other-user-info', []);
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
message: 'test',
userId: user._id,
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('unauthorized');
});
it('should update another user status succesfully', async () => {
await updatePermission('edit-other-user-info', ['admin']);
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
message: 'test',
userId: user._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
void getUserStatus(credentials['X-User-Id']).then((status) => {
expect(status.status).to.be.equal('busy');
});
});
it('should return an error when the user try to update user status with an invalid status', async () => {
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'invalid',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('invalid-params');
expect(res.body.error).to.include('must be equal to one of the allowed values');
});
it('should return an error when user changes status to offline and "Accounts_AllowInvisibleStatusOption" is disabled', async () => {
await updateSetting('Accounts_AllowInvisibleStatusOption', false);
await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'offline',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-status-not-allowed');
expect(res.body.error).to.be.equal('Invisible status is disabled [error-status-not-allowed]');
});
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
});
it('should reject a message-only update when status resolves to offline via statusDefault and "Accounts_AllowInvisibleStatusOption" is disabled', async () => {
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
await request.post(api('users.setStatus')).set(credentials).send({ status: 'offline' }).expect(200);
await updateSetting('Accounts_AllowInvisibleStatusOption', false);
await request
.post(api('users.setStatus'))
.set(credentials)
.send({ message: 'still trying to stay invisible' })
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-status-not-allowed');
});
await updateSetting('Accounts_AllowInvisibleStatusOption', true);
});
it('should return an error when the payload is missing all supported fields', async () => {
const res = await request
.post(api('users.setStatus'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400);
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('Match error: Failed Match.OneOf, Match.Maybe or Match.Optional validation');
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.setStatus'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should set status with expiresAt and return statusSource and statusExpiresAt in getStatus', async () => {
const expiresAt = new Date(Date.now() + 3600_000).toISOString();
await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
message: 'focus time',
expiresAt,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const status = await getUserStatus(credentials['X-User-Id']);
// display status is offline because the test user has no active DDP session;
// the busy claim is persisted in statusDefault and takes effect on reconnect
expect(status.status).to.be.equal('offline');
expect(status).to.have.property('statusSource', 'manual');
expect(status).to.have.property('statusExpiresAt');
expect(new Date(status.statusExpiresAt!).getTime()).to.be.closeTo(new Date(expiresAt).getTime(), 2000);
});
it('should reject a past expiresAt date', async () => {
await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'busy',
expiresAt: '2020-01-01T00:00:00.000Z',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-invalid-date');
expect(res.body.error).to.be.equal('expiresAt must be a future date [error-invalid-date]');
});
});
it('should not return statusExpiresAt when expiresAt is not set', async () => {
await request
.post(api('users.setStatus'))
.set(credentials)
.send({
status: 'online',
})
.expect(200);
const status = await getUserStatus(credentials['X-User-Id']);
expect(status.status).to.be.equal('online');
expect(status).to.not.have.property('statusExpiresAt');
});
it('should update only the status message when no status is provided', async () => {
await updateSetting('Accounts_AllowUserStatusMessageChange', true);
await request
.post(api('users.setStatus'))
.set(credentials)
.send({
message: 'message only, no status',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const infoResponse = await request.get(api('users.info')).query({ userId: credentials['X-User-Id'] }).set(credentials).expect(200);
expect(infoResponse.body.user).to.have.property('statusText', 'message only, no status');
});
});
describe('[/users.removeOtherTokens]', () => {
let user: TestUser<IUser>;
let userCredentials: Credentials;
let newCredentials: Credentials;
before(async () => {
user = await createUser();
userCredentials = await login(user.username, password);
newCredentials = await login(user.username, password);
});
after(() => deleteUser(user));
it('should invalidate all active sesions', (done) => {
/* We want to validate that the login with the "old" credentials fails
However, the removal of the tokens is done asynchronously.
Thus, we check that within the next seconds, at least one try to
access an authentication requiring route fails */
let counter = 0;
async function checkAuthenticationFails() {
const result = await request.get(api('me')).set(userCredentials);
return result.statusCode === 401;
}
async function tryAuthentication() {
if (await checkAuthenticationFails()) {
done();
} else if (++counter < 20) {
setTimeout(tryAuthentication, 1000);
} else {
done('Session did not invalidate in time');
}
}
void request.post(api('users.removeOtherTokens')).set(newCredentials).expect(200).then(tryAuthentication);
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.removeOtherTokens'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.listTeams]', () => {
const teamName1 = `team-name-${Date.now()}`;
const teamName2 = `team-name-2-${Date.now()}`;
let testUser: TestUser<IUser>;
before('create team 1', (done) => {
void request
.post(api('teams.create'))
.set(credentials)
.send({
name: teamName1,
type: 0,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('team');
expect(res.body).to.have.nested.property('team._id');
})
.end(done);
});
before('create team 2', (done) => {
void request
.post(api('teams.create'))
.set(credentials)
.send({
name: teamName2,
type: 0,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('team');
expect(res.body).to.have.nested.property('team._id');
})
.end(done);
});
before('create new user', async () => {
testUser = await createUser({ joinDefaultChannels: false });
});
before('add test user to team 1', (done) => {
void request
.post(api('teams.addMembers'))
.set(credentials)
.send({
teamName: teamName1,
members: [
{
userId: testUser._id,
roles: ['member'],
},
],
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
})
.then(() => done());
});
before('add test user to team 2', (done) => {
void request
.post(api('teams.addMembers'))
.set(credentials)
.send({
teamName: teamName2,
members: [
{
userId: testUser._id,
roles: ['member', 'owner'],
},
],
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
})
.then(() => done());
});
after(() => Promise.all([...[teamName1, teamName2].map((team) => deleteTeam(credentials, team)), deleteUser(testUser)]));
it('should list both channels', async () => {
const res = await request
.get(api('users.listTeams'))
.set(credentials)
.query({
userId: testUser._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('teams');
const { teams } = res.body;
expect(teams).to.have.length(2);
expect(teams[0].isOwner).to.not.be.eql(teams[1].isOwner);
});
it('should return 401 when not authenticated', async () => {
await request
.get(api('users.listTeams'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when query is empty', async () => {
await request
.get(api('users.listTeams'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.logout]', () => {
let user: TestUser<IUser>;
let otherUser: TestUser<IUser>;
let userCredentials: Credentials;
before(async () => {
user = await createUser();
otherUser = await createUser();
userCredentials = await login(user.username, password);
});
after(() => Promise.all([deleteUser(user), deleteUser(otherUser), updatePermission('logout-other-user', ['admin'])]));
it('should throw unauthorized error to user w/o "logout-other-user" permission', async () => {
await updatePermission('logout-other-user', []);
await request
.post(api('users.logout'))
.set(credentials)
.send({ userId: otherUser._id })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should logout other user', async () => {
await updatePermission('logout-other-user', ['admin']);
await request
.post(api('users.logout'))
.set(credentials)
.send({ userId: otherUser._id })
.expect('Content-Type', 'application/json')
.expect(200);
});
it('should logout the requester', (done) => {
void updatePermission('logout-other-user', []).then(() => {
void request.post(api('users.logout')).set(userCredentials).expect('Content-Type', 'application/json').expect(200).end(done);
});
});
it('should remove all push tokens when user logs out', async () => {
const testCredentials = await login(user.username, password);
await request
.post(api('push.token'))
.set(testCredentials)
.send({
type: 'gcm',
value: 'logout-test-token',
appName: 'com.example.logout.test',
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.post(api('users.logout'))
.set(testCredentials)
.send({
userId: user._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await new Promise((resolve) => setTimeout(resolve, 1000));
const newCredentials = await login(user.username, password);
await request
.delete(api('push.token'))
.set(newCredentials)
.send({
token: 'logout-test-token',
})
.expect(404)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.logout'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.listByStatus]', () => {
let user: TestUser<IUser>;
let otherUser: TestUser<IUser>;
let otherUserCredentials: Credentials;
before(async () => {
user = await createUser();
otherUser = await createUser();
otherUserCredentials = await login(otherUser.username, password);
});
after(async () => {
await deleteUser(user);
await deleteUser(otherUser);
await updatePermission('view-outside-room', ['admin', 'owner', 'moderator', 'user']);
await updatePermission('view-d-room', ['admin', 'owner', 'moderator', 'user']);
});
it('should list pending users', async () => {
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ hasLoggedIn: false, type: 'user', count: 50 })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.include(user._id);
});
});
it('should list all users', async () => {
await request
.get(api('users.listByStatus'))
.query({ searchTerm: user.name })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.include(user._id);
});
});
it('should list active users', async () => {
await login(user.username, password);
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ hasLoggedIn: true, status: 'active', searchTerm: user.name })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.include(user._id);
});
});
it('should filter users by role', async () => {
await login(user.username, password);
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ 'roles[]': 'admin' })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.not.include(user._id);
});
});
it('should list deactivated users', async () => {
await request.post(api('users.setActiveStatus')).set(credentials).send({
userId: user._id,
activeStatus: false,
confirmRelinquish: false,
});
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ hasLoggedIn: true, status: 'deactivated' })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.include(user._id);
});
});
it('should filter users by username', async () => {
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ searchTerm: user.username })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('users');
const { users } = res.body as PaginatedResult<{
users: DefaultUserInfo[];
}>;
const ids = users.map((user) => user._id);
expect(ids).to.include(user._id);
});
});
it('should return error for invalid status params', async () => {
await request
.get(api('users.listByStatus'))
.set(credentials)
.query({ status: 'abcd' })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.errorType).to.be.equal('error-invalid-params');
expect(res.body.error).to.be.equal('must be equal to one of the allowed values');
});
});
it('should throw unauthorized error to user without "view-d-room" permission', async () => {
await updatePermission('view-d-room', ['admin']);
await request
.get(api('users.listByStatus'))
.set(otherUserCredentials)
.query({ status: 'active' })
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
});
});
it('should throw unauthorized error to user without "view-outside-room" permission', async () => {
await updatePermission('view-outside-room', ['admin']);
await request
.get(api('users.listByStatus'))
.set(otherUserCredentials)
.query({ status: 'active' })
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
});
});
});
describe('[/users.sendWelcomeEmail]', async () => {
let user: TestUser<IUser>;
let otherUser: TestUser<IUser>;
before(async () => {
user = await createUser();
otherUser = await createUser();
});
after(async () => {
await deleteUser(user);
await deleteUser(otherUser);
});
it('should send Welcome Email to user', async () => {
await updateSetting('SMTP_Host', 'localhost');
await request
.post(api('users.sendWelcomeEmail'))
.set(credentials)
.send({ email: user.emails[0].address })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should fail to send Welcome Email due to SMTP settings missing', async () => {
await updateSetting('SMTP_Host', '');
await request
.post(api('users.sendWelcomeEmail'))
.set(credentials)
.send({ email: user.emails[0].address })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('SMTP is not configured [error-email-send-failed]');
});
});
it('should fail to send Welcome Email due to missing param', async () => {
await updateSetting('SMTP_Host', '');
await request
.post(api('users.sendWelcomeEmail'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
expect(res.body).to.have.property('error', "must have required property 'email'");
});
});
it('should fail to send Welcome Email due missing user', async () => {
await updateSetting('SMTP_Host', 'localhost');
await request
.post(api('users.sendWelcomeEmail'))
.set(credentials)
.send({ email: 'fake_user32132131231@rocket.chat' })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-invalid-user');
expect(res.body).to.have.property('error', 'Invalid user [error-invalid-user]');
});
});
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.sendWelcomeEmail'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.createToken]', () => {
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.createToken'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body is missing required fields', async () => {
await request
.post(api('users.createToken'))
.set(credentials)
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.resetE2EKey]', () => {
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.resetE2EKey'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body has invalid properties', async () => {
await request
.post(api('users.resetE2EKey'))
.set(credentials)
.send({ invalidProp: true })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.resetTOTP]', () => {
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.resetTOTP'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
it('should return 400 when body has invalid properties', async () => {
await request
.post(api('users.resetTOTP'))
.set(credentials)
.send({ invalidProp: true })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.2fa.enableEmail]', () => {
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.2fa.enableEmail'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.2fa.disableEmail]', () => {
it('should return 401 when not authenticated', async () => {
await request
.post(api('users.2fa.disableEmail'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res: Response) => {
expect(res.body).to.have.property('status', 'error');
});
});
});
describe('[/users.2fa.sendEmailCode]', () => {
it('should return 400 when emailOrUsername is missing', async () => {
await request
.post(api('users.2fa.sendEmailCode'))
.send({})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
describe('[/users.verifyEmail]', () => {
it('should fail with 400 when the token is not provided', () => request.post(api('users.verifyEmail')).send({}).expect(400));
it('should fail with 403 when the token does not match any user', () =>
request
.post(api('users.verifyEmail'))
.send({ token: 'this-token-does-not-exist' })
.expect(403)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
}));
describe('when a valid token is provided', () => {
let user: TestUser<IUser>;
const email = `verify.email.${Date.now()}@rocket.chat`;
const token = `valid-verification-token-${Date.now()}`;
before(async () => {
user = await createUser({ email, verified: false });
// The verification token is never exposed by any endpoint, so seed a known one directly.
await updateUserInDb(user._id, {
'emails.0.verified': false,
'services.email.verificationTokens': [{ token, address: email, when: new Date() }],
} as unknown as Partial<IUser>);
});
after(() => deleteUser(user));
it('should verify the email and consume the token', async () => {
await request
.post(api('users.verifyEmail'))
.send({ token })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
const updatedUser = await getUserByUsername<IUser>(user.username);
expect(updatedUser.emails[0]).to.have.property('verified', true);
// The token was pulled on success, so reusing it must now be rejected.
await request
.post(api('users.verifyEmail'))
.send({ token })
.expect(403)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
});
describe('[TOTP endpoints]', () => {
let totpUser: TestUser<IUser>;
let totpCredentials: Credentials;
let secret: string;
// speakeasy verify is stateless, so the same code is accepted repeatedly within its window — safe to reuse across the flow
const totpCode = () => speakeasy.totp({ secret, encoding: 'base32' });
// enableTotp/validateTotp carry `twoFactorRequired` to protect users with an existing 2FA method, but the
// API e2e suite runs with TEST_MODE which bypasses the 2FA challenge (see checkCodeForUser), so these tests
// exercise the endpoint logic for a fresh user without a challenge.
before(async () => {
totpUser = await createUser({ username: Random.id(), email: `${Random.id()}@example.com`, verified: true });
totpCredentials = await login(totpUser.username, password);
});
after(async () => deleteUser(totpUser));
describe('[/users.enableTotp]', () => {
it('should fail when unauthenticated', () => request.post(api('users.enableTotp')).expect(401));
it('should return a secret and an otpauth url', () =>
request
.post(api('users.enableTotp'))
.set(totpCredentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('secret').that.is.a('string');
expect(res.body)
.to.have.property('url')
.that.is.a('string')
.and.match(/^otpauth:\/\//);
secret = res.body.secret;
}));
});
describe('[/users.validateTotp]', () => {
it('should fail when unauthenticated', () => request.post(api('users.validateTotp')).send({ code: '000000' }).expect(401));
it('should fail with 400 when the code is missing', () =>
request.post(api('users.validateTotp')).set(totpCredentials).send({}).expect(400));
it('should enable totp and return backup codes for a valid code', () =>
request
.post(api('users.validateTotp'))
.set(totpCredentials)
.send({ code: totpCode() })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('codes').that.is.an('array');
}));
});
describe('[/users.totpCodesRemaining]', () => {
it('should fail when unauthenticated', () => request.get(api('users.totpCodesRemaining')).expect(401));
it('should return the number of remaining backup codes', () =>
request
.get(api('users.totpCodesRemaining'))
.set(totpCredentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('remaining').that.is.a('number');
}));
});
describe('[/users.regenerateTotpCodes]', () => {
it('should fail when unauthenticated', () => request.post(api('users.regenerateTotpCodes')).send({ code: '000000' }).expect(401));
it('should fail with 400 when the code is missing', () =>
request.post(api('users.regenerateTotpCodes')).set(totpCredentials).send({}).expect(400));
it('should fail with 400 when the code is invalid', () =>
request
.post(api('users.regenerateTotpCodes'))
.set(totpCredentials)
.send({ code: '000000' })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
}));
it('should return a fresh set of backup codes for a valid code', () =>
request
.post(api('users.regenerateTotpCodes'))
.set(totpCredentials)
.send({ code: totpCode() })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('codes').that.is.an('array');
}));
});
describe('[/users.disableTotp]', () => {
it('should fail when unauthenticated', () => request.post(api('users.disableTotp')).send({ code: '000000' }).expect(401));
it('should fail with 400 when the code is missing', () =>
request.post(api('users.disableTotp')).set(totpCredentials).send({}).expect(400));
it('should disable totp for a valid code', () =>
request
.post(api('users.disableTotp'))
.set(totpCredentials)
.send({ code: totpCode() })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('disabled', true);
}));
});
});
});