mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
🚨 Optimize UM tests (#3066)
* ⚡ Declutter test logs * 🐛 Fix random passwords length * 🐛 Fix password hashing in test user creation * 🐛 Hash leftover password * ⚡ Improve error message for `compare` * ⚡ Restore `randomInvalidPassword` contant * ⚡ Mock Telemetry module to prevent `--forceExit` * 🔥 Remove unused imports * 🔥 Remove unused import * ⚡ Add util for configuring test SMTP * ⚡ Isolate user creation * 🔥 De-duplicate `createFullUser` * ⚡ Centralize hashing * 🔥 Remove superfluous arg * 🔥 Remove outdated comment * ⚡ Prioritize shared tables during trucation * 🧪 Add login tests * ⚡ Use token helper * ✏️ Improve naming * ⚡ Make `createMemberShell` consistent * 🔥 Remove unneeded helper * 🔥 De-duplicate `beforeEach` * ✏️ Improve naming * 🚚 Move `categorize` to utils * ✏️ Update comment * 🧪 Simplify test * 📘 Improve `User.password` type * ⚡ Silence logger * ⚡ Simplify condition * ⚡ Unhash password in payload * 🐛 Fix comparison against unhashed password * ⚡ Increase timeout for fake SMTP service * 🔥 Remove unneeded import * ⚡ Use `isNull()` * 🧪 Use `Promise.all()` in creds tests * 🧪 Use `Promise.all()` in me tests * 🧪 Use `Promise.all()` in owner tests * 🧪 Use `Promise.all()` in password tests * 🧪 Use `Promise.all()` in users tests * ⚡ Re-set cookie if UM disabled * 🔥 Remove repeated line * ⚡ Refactor out shared owner data * 🔥 Remove unneeded import * 🔥 Remove repeated lines * ⚡ Organize imports * ⚡ Reuse helper * 🚚 Rename tests to match routers * 🚚 Rename `createFullUser()` to `createUser()` * ⚡ Consolidate user shell creation * ⚡ Make hashing async * ⚡ Add email to user shell * ⚡ Optimize array building * 🛠 refactor user shell factory * 🐛 Fix MySQL tests * ⚡ Silence logger in other DBs Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>
This commit is contained in:
parent
e78bf15ba9
commit
1e2d6daaa3
|
@ -31,8 +31,8 @@
|
|||
"start:windows": "cd bin && n8n",
|
||||
"test": "npm run test:sqlite",
|
||||
"test:sqlite": "export N8N_LOG_LEVEL='silent'; export DB_TYPE=sqlite; jest",
|
||||
"test:postgres": "export DB_TYPE=postgresdb && jest",
|
||||
"test:mysql": "export DB_TYPE=mysqldb && jest",
|
||||
"test:postgres": "export N8N_LOG_LEVEL='silent'; export DB_TYPE=postgresdb && jest",
|
||||
"test:mysql": "export N8N_LOG_LEVEL='silent'; export DB_TYPE=mysqldb && jest",
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ../../node_modules/typeorm/cli.js"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { Workflow } from 'n8n-workflow';
|
||||
import { In, IsNull, Not } from 'typeorm';
|
||||
import express = require('express');
|
||||
import { compare } from 'bcryptjs';
|
||||
import { compare, genSaltSync, hash } from 'bcryptjs';
|
||||
|
||||
import { PublicUser } from './Interfaces';
|
||||
import { Db, ResponseHelper } from '..';
|
||||
|
@ -63,11 +63,6 @@ export function getInstanceBaseUrl(): string {
|
|||
return n8nBaseUrl.endsWith('/') ? n8nBaseUrl.slice(0, n8nBaseUrl.length - 1) : n8nBaseUrl;
|
||||
}
|
||||
|
||||
export async function isInstanceOwnerSetup(): Promise<boolean> {
|
||||
const users = await Db.collections.User!.find({ email: Not(IsNull()) });
|
||||
return users.length !== 0;
|
||||
}
|
||||
|
||||
// TODO: Enforce at model level
|
||||
export function validatePassword(password?: string): string {
|
||||
if (!password) {
|
||||
|
@ -223,9 +218,12 @@ export function isAuthenticatedRequest(request: express.Request): request is Aut
|
|||
// hashing
|
||||
// ----------------------------------
|
||||
|
||||
export async function compareHash(str: string, hash: string): Promise<boolean | undefined> {
|
||||
export const hashPassword = async (validPassword: string): Promise<string> =>
|
||||
hash(validPassword, genSaltSync(10));
|
||||
|
||||
export async function compareHash(plaintext: string, hashed: string): Promise<boolean | undefined> {
|
||||
try {
|
||||
return await compare(str, hash);
|
||||
return await compare(plaintext, hashed);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('Invalid salt version')) {
|
||||
error.message +=
|
||||
|
|
|
@ -8,9 +8,10 @@ import { Db, ResponseHelper } from '../..';
|
|||
import { AUTH_COOKIE_NAME } from '../../constants';
|
||||
import { issueCookie, resolveJwt } from '../auth/jwt';
|
||||
import { N8nApp, PublicUser } from '../Interfaces';
|
||||
import { compareHash, isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper';
|
||||
import { compareHash, sanitizeUser } from '../UserManagementHelper';
|
||||
import { User } from '../../databases/entities/User';
|
||||
import type { LoginRequest } from '../../requests';
|
||||
import config = require('../../../config');
|
||||
|
||||
export function authenticationMethods(this: N8nApp): void {
|
||||
/**
|
||||
|
@ -71,13 +72,18 @@ export function authenticationMethods(this: N8nApp): void {
|
|||
// If logged in, return user
|
||||
try {
|
||||
user = await resolveJwt(cookieContents);
|
||||
|
||||
if (!config.get('userManagement.isInstanceOwnerSetUp')) {
|
||||
res.cookie(AUTH_COOKIE_NAME, cookieContents);
|
||||
}
|
||||
|
||||
return sanitizeUser(user);
|
||||
} catch (error) {
|
||||
res.clearCookie(AUTH_COOKIE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
if (await isInstanceOwnerSetup()) {
|
||||
if (config.get('userManagement.isInstanceOwnerSetUp')) {
|
||||
const error = new Error('Not logged in');
|
||||
// @ts-ignore
|
||||
error.httpStatusCode = 401;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable import/no-cycle */
|
||||
|
||||
import { compare, genSaltSync, hashSync } from 'bcryptjs';
|
||||
import express = require('express');
|
||||
import validator from 'validator';
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
|
@ -9,7 +8,7 @@ import { LoggerProxy as Logger } from 'n8n-workflow';
|
|||
import { Db, InternalHooksManager, ResponseHelper } from '../..';
|
||||
import { issueCookie } from '../auth/jwt';
|
||||
import { N8nApp, PublicUser } from '../Interfaces';
|
||||
import { validatePassword, sanitizeUser } from '../UserManagementHelper';
|
||||
import { validatePassword, sanitizeUser, compareHash, hashPassword } from '../UserManagementHelper';
|
||||
import type { AuthenticatedRequest, MeRequest } from '../../requests';
|
||||
import { validateEntity } from '../../GenericHelpers';
|
||||
import { User } from '../../databases/entities/User';
|
||||
|
@ -87,7 +86,7 @@ export function meNamespace(this: N8nApp): void {
|
|||
throw new ResponseHelper.ResponseError('Requesting user not set up.');
|
||||
}
|
||||
|
||||
const isCurrentPwCorrect = await compare(currentPassword, req.user.password);
|
||||
const isCurrentPwCorrect = await compareHash(currentPassword, req.user.password);
|
||||
if (!isCurrentPwCorrect) {
|
||||
throw new ResponseHelper.ResponseError(
|
||||
'Provided current password is incorrect.',
|
||||
|
@ -98,7 +97,7 @@ export function meNamespace(this: N8nApp): void {
|
|||
|
||||
const validPassword = validatePassword(newPassword);
|
||||
|
||||
req.user.password = hashSync(validPassword, genSaltSync(10));
|
||||
req.user.password = await hashPassword(validPassword);
|
||||
|
||||
const user = await Db.collections.User!.save(req.user);
|
||||
Logger.info('Password updated successfully', { userId: user.id });
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { hashSync, genSaltSync } from 'bcryptjs';
|
||||
import * as express from 'express';
|
||||
import validator from 'validator';
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
|
@ -11,7 +10,7 @@ import { validateEntity } from '../../GenericHelpers';
|
|||
import { AuthenticatedRequest, OwnerRequest } from '../../requests';
|
||||
import { issueCookie } from '../auth/jwt';
|
||||
import { N8nApp } from '../Interfaces';
|
||||
import { sanitizeUser, validatePassword } from '../UserManagementHelper';
|
||||
import { hashPassword, sanitizeUser, validatePassword } from '../UserManagementHelper';
|
||||
|
||||
export function ownerNamespace(this: N8nApp): void {
|
||||
/**
|
||||
|
@ -74,7 +73,7 @@ export function ownerNamespace(this: N8nApp): void {
|
|||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password: hashSync(validPassword, genSaltSync(10)),
|
||||
password: await hashPassword(validPassword),
|
||||
});
|
||||
|
||||
await validateEntity(owner);
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
import express = require('express');
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { URL } from 'url';
|
||||
import { genSaltSync, hashSync } from 'bcryptjs';
|
||||
import validator from 'validator';
|
||||
import { IsNull, MoreThanOrEqual, Not } from 'typeorm';
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
|
||||
import { Db, InternalHooksManager, ResponseHelper } from '../..';
|
||||
import { N8nApp } from '../Interfaces';
|
||||
import { getInstanceBaseUrl, validatePassword } from '../UserManagementHelper';
|
||||
import { getInstanceBaseUrl, hashPassword, validatePassword } from '../UserManagementHelper';
|
||||
import * as UserManagementMailer from '../email';
|
||||
import type { PasswordResetRequest } from '../../requests';
|
||||
import { issueCookie } from '../auth/jwt';
|
||||
|
@ -206,7 +205,7 @@ export function passwordResetNamespace(this: N8nApp): void {
|
|||
}
|
||||
|
||||
await Db.collections.User!.update(userId, {
|
||||
password: hashSync(validPassword, genSaltSync(10)),
|
||||
password: await hashPassword(validPassword),
|
||||
resetPasswordToken: null,
|
||||
resetPasswordTokenExpiration: null,
|
||||
});
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { Response } from 'express';
|
||||
import { In } from 'typeorm';
|
||||
import { genSaltSync, hashSync } from 'bcryptjs';
|
||||
import validator from 'validator';
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
|
||||
|
@ -12,6 +11,7 @@ import { N8nApp, PublicUser } from '../Interfaces';
|
|||
import { UserRequest } from '../../requests';
|
||||
import {
|
||||
getInstanceBaseUrl,
|
||||
hashPassword,
|
||||
isEmailSetUp,
|
||||
sanitizeUser,
|
||||
validatePassword,
|
||||
|
@ -349,7 +349,7 @@ export function usersNamespace(this: N8nApp): void {
|
|||
|
||||
invitee.firstName = firstName;
|
||||
invitee.lastName = lastName;
|
||||
invitee.password = hashSync(validPassword, genSaltSync(10));
|
||||
invitee.password = await hashPassword(validPassword);
|
||||
|
||||
const updatedUser = await Db.collections.User!.save(invitee);
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ export class User {
|
|||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ length: 254 })
|
||||
@Column({ length: 254, nullable: true })
|
||||
@Index({ unique: true })
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
@ -81,7 +81,7 @@ export class User {
|
|||
|
||||
@Column({ nullable: true })
|
||||
@IsString({ message: 'Password must be of type string.' })
|
||||
password?: string;
|
||||
password: string;
|
||||
|
||||
@Column({ type: String, nullable: true })
|
||||
resetPasswordToken?: string | null;
|
||||
|
|
283
packages/cli/test/integration/auth.api.test.ts
Normal file
283
packages/cli/test/integration/auth.api.test.ts
Normal file
|
@ -0,0 +1,283 @@
|
|||
import express = require('express');
|
||||
import validator from 'validator';
|
||||
|
||||
import config = require('../../config');
|
||||
import * as utils from './shared/utils';
|
||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||
import { Db } from '../../src';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
import { randomValidPassword } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import { AUTH_COOKIE_NAME } from '../../src/constants';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true });
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate(testDbName);
|
||||
});
|
||||
|
||||
test('POST /login should log user in', async () => {
|
||||
const ownerPassword = randomValidPassword();
|
||||
const owner = await testDb.createUser({
|
||||
password: ownerPassword,
|
||||
globalRole: globalOwnerRole,
|
||||
});
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent.post('/login').send({
|
||||
email: owner.email,
|
||||
password: ownerPassword,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(owner.email);
|
||||
expect(firstName).toBe(owner.firstName);
|
||||
expect(lastName).toBe(owner.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
});
|
||||
|
||||
test('GET /login should return 401 Unauthorized if no cookie', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return cookie if UM is disabled', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(false) },
|
||||
);
|
||||
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerShellAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
});
|
||||
|
||||
test('GET /login should return 401 Unauthorized if invalid cookie', async () => {
|
||||
const invalidAuthAgent = utils.createAgent(app);
|
||||
invalidAuthAgent.jar.setCookie(`${AUTH_COOKIE_NAME}=invalid`);
|
||||
|
||||
const response = await invalidAuthAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return logged-in owner shell', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authMemberAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return logged-in member shell', async () => {
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: memberShell });
|
||||
|
||||
const response = await authMemberAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return logged-in owner', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(owner.email);
|
||||
expect(firstName).toBe(owner.firstName);
|
||||
expect(lastName).toBe(owner.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('GET /login should return logged-in member', async () => {
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
const response = await authMemberAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(member.email);
|
||||
expect(firstName).toBe(member.firstName);
|
||||
expect(lastName).toBe(member.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
test('POST /logout should log user out', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.post('/logout');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
|
@ -1,149 +0,0 @@
|
|||
import { hashSync, genSaltSync } from 'bcryptjs';
|
||||
import express = require('express');
|
||||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import config = require('../../config');
|
||||
import * as utils from './shared/utils';
|
||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||
import { Db } from '../../src';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import { randomEmail, randomValidPassword, randomName } from './shared/random';
|
||||
import { getGlobalOwnerRole } from './shared/testDb';
|
||||
import * as testDb from './shared/testDb';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true });
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.createUser({
|
||||
id: uuid(),
|
||||
email: TEST_USER.email,
|
||||
firstName: TEST_USER.firstName,
|
||||
lastName: TEST_USER.lastName,
|
||||
password: TEST_USER.password,
|
||||
globalRole: globalOwnerRole,
|
||||
});
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate(testDbName);
|
||||
});
|
||||
|
||||
test('POST /login should log user in', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent.post('/login').send({
|
||||
email: TEST_USER.email,
|
||||
password: TEST_USER.password,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
});
|
||||
|
||||
test('GET /login should receive logged in user', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
expect(response.headers['set-cookie']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('POST /logout should log user out', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.post('/logout');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
const TEST_USER = {
|
||||
email: randomEmail(),
|
||||
password: randomValidPassword(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
};
|
|
@ -8,11 +8,13 @@ import {
|
|||
} from './shared/constants';
|
||||
import * as utils from './shared/utils';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({
|
||||
|
@ -21,6 +23,9 @@ beforeAll(async () => {
|
|||
});
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
@ -43,12 +48,9 @@ ROUTES_REQUIRING_AUTHORIZATION.forEach(async (route) => {
|
|||
const [method, endpoint] = getMethodAndEndpoint(route);
|
||||
|
||||
test(`${route} should return 403 Forbidden for member`, async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const response = await authMemberAgent[method](endpoint);
|
||||
if (response.statusCode === 500) {
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
|
@ -4,14 +4,17 @@ import { Db } from '../../src';
|
|||
import { randomName, randomString } from './shared/random';
|
||||
import * as utils from './shared/utils';
|
||||
import type { CredentialPayload, SaveCredentialFunction } from './shared/types';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import { User } from '../../src/databases/entities/User';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
import type { User } from '../../src/databases/entities/User';
|
||||
import * as testDb from './shared/testDb';
|
||||
import { CredentialsEntity } from '../../src/databases/entities/CredentialsEntity';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let saveCredential: SaveCredentialFunction;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -24,19 +27,17 @@ beforeAll(async () => {
|
|||
|
||||
utils.initConfigFile();
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
|
||||
saveCredential = affixRoleToSaveCredential(credentialOwnerRole);
|
||||
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.createOwnerShell();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// do not combine calls - shared table must be cleared first and separately
|
||||
await testDb.truncate(['SharedCredentials'], testDbName);
|
||||
await testDb.truncate(['User', 'Credentials'], testDbName);
|
||||
await testDb.truncate(['User', 'SharedCredentials', 'Credentials'], testDbName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -44,8 +45,9 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
test('POST /credentials should create cred', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const payload = credentialPayload();
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(payload);
|
||||
|
@ -71,26 +73,28 @@ test('POST /credentials should create cred', async () => {
|
|||
where: { credentials: credential },
|
||||
});
|
||||
|
||||
expect(sharedCredential.user.id).toBe(owner.id);
|
||||
expect(sharedCredential.user.id).toBe(ownerShell.id);
|
||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||
});
|
||||
|
||||
test('POST /credentials should fail with invalid inputs', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
for (const invalidPayload of INVALID_PAYLOADS) {
|
||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}
|
||||
await Promise.all(
|
||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/credentials').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /credentials should fail with missing encryption key', async () => {
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockResolvedValue(undefined);
|
||||
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(credentialPayload());
|
||||
|
||||
|
@ -100,8 +104,8 @@ test('POST /credentials should fail with missing encryption key', async () => {
|
|||
});
|
||||
|
||||
test('POST /credentials should ignore ID in payload', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const firstResponse = await authOwnerAgent
|
||||
.post('/credentials')
|
||||
|
@ -117,9 +121,9 @@ test('POST /credentials should ignore ID in payload', async () => {
|
|||
});
|
||||
|
||||
test('DELETE /credentials/:id should delete owned cred for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
||||
|
@ -136,9 +140,9 @@ test('DELETE /credentials/:id should delete owned cred for owner', async () => {
|
|||
});
|
||||
|
||||
test('DELETE /credentials/:id should delete non-owned cred for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: member });
|
||||
|
||||
const response = await authOwnerAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
@ -156,7 +160,7 @@ test('DELETE /credentials/:id should delete non-owned cred for owner', async ()
|
|||
});
|
||||
|
||||
test('DELETE /credentials/:id should delete owned cred for member', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: member });
|
||||
|
||||
|
@ -175,10 +179,10 @@ test('DELETE /credentials/:id should delete owned cred for member', async () =>
|
|||
});
|
||||
|
||||
test('DELETE /credentials/:id should not delete non-owned cred for member', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
const response = await authMemberAgent.delete(`/credentials/${savedCredential.id}`);
|
||||
|
||||
|
@ -194,8 +198,8 @@ test('DELETE /credentials/:id should not delete non-owned cred for member', asyn
|
|||
});
|
||||
|
||||
test('DELETE /credentials/:id should fail if cred not found', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.delete('/credentials/123');
|
||||
|
||||
|
@ -203,9 +207,9 @@ test('DELETE /credentials/:id should fail if cred not found', async () => {
|
|||
});
|
||||
|
||||
test('PATCH /credentials/:id should update owned cred for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
const patchPayload = credentialPayload();
|
||||
|
||||
const response = await authOwnerAgent
|
||||
|
@ -237,9 +241,9 @@ test('PATCH /credentials/:id should update owned cred for owner', async () => {
|
|||
});
|
||||
|
||||
test('PATCH /credentials/:id should update non-owned cred for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: member });
|
||||
const patchPayload = credentialPayload();
|
||||
|
||||
|
@ -272,7 +276,7 @@ test('PATCH /credentials/:id should update non-owned cred for owner', async () =
|
|||
});
|
||||
|
||||
test('PATCH /credentials/:id should update owned cred for member', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: member });
|
||||
const patchPayload = credentialPayload();
|
||||
|
@ -306,10 +310,10 @@ test('PATCH /credentials/:id should update owned cred for member', async () => {
|
|||
});
|
||||
|
||||
test('PATCH /credentials/:id should not update non-owned cred for member', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
const patchPayload = credentialPayload();
|
||||
|
||||
const response = await authMemberAgent
|
||||
|
@ -324,22 +328,24 @@ test('PATCH /credentials/:id should not update non-owned cred for member', async
|
|||
});
|
||||
|
||||
test('PATCH /credentials/:id should fail with invalid inputs', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
for (const invalidPayload of INVALID_PAYLOADS) {
|
||||
const response = await authOwnerAgent
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(invalidPayload);
|
||||
await Promise.all(
|
||||
INVALID_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent
|
||||
.patch(`/credentials/${savedCredential.id}`)
|
||||
.send(invalidPayload);
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
}
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('PATCH /credentials/:id should fail if cred not found', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.patch('/credentials/123').send(credentialPayload());
|
||||
|
||||
|
@ -350,8 +356,8 @@ test('PATCH /credentials/:id should fail with missing encryption key', async ()
|
|||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockResolvedValue(undefined);
|
||||
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(credentialPayload());
|
||||
|
||||
|
@ -361,14 +367,14 @@ test('PATCH /credentials/:id should fail with missing encryption key', async ()
|
|||
});
|
||||
|
||||
test('GET /credentials should retrieve all creds for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await saveCredential(credentialPayload(), { user: owner });
|
||||
await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
}
|
||||
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
await saveCredential(credentialPayload(), { user: member });
|
||||
|
||||
|
@ -377,18 +383,20 @@ test('GET /credentials should retrieve all creds for owner', async () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.length).toBe(4); // 3 owner + 1 member
|
||||
|
||||
for (const credential of response.body.data) {
|
||||
const { name, type, nodesAccess, data: encryptedData } = credential;
|
||||
await Promise.all(
|
||||
response.body.data.map(async (credential: CredentialsEntity) => {
|
||||
const { name, type, nodesAccess, data: encryptedData } = credential;
|
||||
|
||||
expect(typeof name).toBe('string');
|
||||
expect(typeof type).toBe('string');
|
||||
expect(typeof nodesAccess[0].nodeType).toBe('string');
|
||||
expect(encryptedData).toBeUndefined();
|
||||
}
|
||||
expect(typeof name).toBe('string');
|
||||
expect(typeof type).toBe('string');
|
||||
expect(typeof nodesAccess[0].nodeType).toBe('string');
|
||||
expect(encryptedData).toBeUndefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('GET /credentials should retrieve owned creds for member', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
@ -400,23 +408,25 @@ test('GET /credentials should retrieve owned creds for member', async () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.length).toBe(3);
|
||||
|
||||
for (const credential of response.body.data) {
|
||||
const { name, type, nodesAccess, data: encryptedData } = credential;
|
||||
await Promise.all(
|
||||
response.body.data.map(async (credential: CredentialsEntity) => {
|
||||
const { name, type, nodesAccess, data: encryptedData } = credential;
|
||||
|
||||
expect(typeof name).toBe('string');
|
||||
expect(typeof type).toBe('string');
|
||||
expect(typeof nodesAccess[0].nodeType).toBe('string');
|
||||
expect(encryptedData).toBeUndefined();
|
||||
}
|
||||
expect(typeof name).toBe('string');
|
||||
expect(typeof type).toBe('string');
|
||||
expect(typeof nodesAccess[0].nodeType).toBe('string');
|
||||
expect(encryptedData).toBeUndefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('GET /credentials should not retrieve non-owned creds for member', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await saveCredential(credentialPayload(), { user: owner });
|
||||
await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
}
|
||||
|
||||
const response = await authMemberAgent.get('/credentials');
|
||||
|
@ -426,9 +436,9 @@ test('GET /credentials should not retrieve non-owned creds for member', async ()
|
|||
});
|
||||
|
||||
test('GET /credentials/:id should retrieve owned cred for owner', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
const firstResponse = await authOwnerAgent.get(`/credentials/${savedCredential.id}`);
|
||||
|
||||
|
@ -451,7 +461,7 @@ test('GET /credentials/:id should retrieve owned cred for owner', async () => {
|
|||
});
|
||||
|
||||
test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: member });
|
||||
|
||||
|
@ -477,10 +487,10 @@ test('GET /credentials/:id should retrieve owned cred for member', async () => {
|
|||
});
|
||||
|
||||
test('GET /credentials/:id should not retrieve non-owned cred for member', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const member = await testDb.createUser();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
const response = await authMemberAgent.get(`/credentials/${savedCredential.id}`);
|
||||
|
||||
|
@ -489,9 +499,9 @@ test('GET /credentials/:id should not retrieve non-owned cred for member', async
|
|||
});
|
||||
|
||||
test('GET /credentials/:id should fail with missing encryption key', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
const savedCredential = await saveCredential(credentialPayload(), { user: ownerShell });
|
||||
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockResolvedValue(undefined);
|
||||
|
@ -506,8 +516,8 @@ test('GET /credentials/:id should fail with missing encryption key', async () =>
|
|||
});
|
||||
|
||||
test('GET /credentials/:id should return 404 if cred not found', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authMemberAgent.get('/credentials/789');
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { hashSync, genSaltSync } from 'bcryptjs';
|
||||
import express = require('express');
|
||||
import validator from 'validator';
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
import config = require('../../config');
|
||||
import * as utils from './shared/utils';
|
||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import { Db } from '../../src';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
import { randomValidPassword, randomEmail, randomName, randomString } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
|
||||
|
@ -15,6 +15,7 @@ jest.mock('../../src/telemetry');
|
|||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['me'], applyAuth: true });
|
||||
|
@ -22,6 +23,7 @@ beforeAll(async () => {
|
|||
testDbName = initResult.testDbName;
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
@ -32,15 +34,11 @@ afterAll(async () => {
|
|||
|
||||
describe('Owner shell', () => {
|
||||
beforeEach(async () => {
|
||||
await testDb.createOwnerShell();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
});
|
||||
|
||||
test('GET /me should return sanitized owner shell', async () => {
|
||||
const ownerShell = await Db.collections.User!.findOneOrFail();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerShellAgent.get('/me');
|
||||
|
@ -72,7 +70,7 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const ownerShell = await Db.collections.User!.findOneOrFail();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||
|
@ -112,7 +110,7 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me should fail with invalid inputs', async () => {
|
||||
const ownerShell = await Db.collections.User!.findOneOrFail();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
||||
|
@ -127,7 +125,7 @@ describe('Owner shell', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me/password should fail for shell', async () => {
|
||||
const ownerShell = await Db.collections.User!.findOneOrFail();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const validPasswordPayload = {
|
||||
|
@ -135,40 +133,46 @@ describe('Owner shell', () => {
|
|||
newPassword: randomValidPassword(),
|
||||
};
|
||||
|
||||
const payloads = [validPasswordPayload, ...INVALID_PASSWORD_PAYLOADS];
|
||||
const validPayloads = [validPasswordPayload, ...INVALID_PASSWORD_PAYLOADS];
|
||||
|
||||
for (const payload of payloads) {
|
||||
const response = await authOwnerShellAgent.patch('/me/password').send(payload);
|
||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||
await Promise.all(
|
||||
validPayloads.map(async (payload) => {
|
||||
const response = await authOwnerShellAgent.patch('/me/password').send(payload);
|
||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
|
||||
if (payload.newPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.newPassword);
|
||||
}
|
||||
if (payload.currentPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.currentPassword);
|
||||
}
|
||||
}
|
||||
if (payload.newPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.newPassword);
|
||||
}
|
||||
|
||||
if (payload.currentPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.currentPassword);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail();
|
||||
expect(storedOwnerShell.password).toBeNull();
|
||||
});
|
||||
|
||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||
const ownerShell = await Db.collections.User!.findOneOrFail();
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const validPayloads = [SURVEY, {}];
|
||||
|
||||
for (const validPayload of validPayloads) {
|
||||
const response = await authOwnerShellAgent.post('/me/survey').send(validPayload);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
const { personalizationAnswers: storedAnswers } = await Db.collections.User!.findOneOrFail();
|
||||
const storedShellOwner = await Db.collections.User!.findOneOrFail({
|
||||
where: { email: IsNull() },
|
||||
});
|
||||
|
||||
expect(storedAnswers).toEqual(validPayload);
|
||||
expect(storedShellOwner.personalizationAnswers).toEqual(validPayload);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -188,7 +192,7 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('GET /me should return sanitized member', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
const response = await authMemberAgent.get('/me');
|
||||
|
@ -220,7 +224,7 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||
|
@ -260,7 +264,7 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me should fail with invalid inputs', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
for (const invalidPayload of INVALID_PATCH_ME_PAYLOADS) {
|
||||
|
@ -278,6 +282,7 @@ describe('Member', () => {
|
|||
const memberPassword = randomValidPassword();
|
||||
const member = await testDb.createUser({
|
||||
password: memberPassword,
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
|
@ -296,7 +301,7 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me/password should fail with invalid inputs', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
for (const payload of INVALID_PASSWORD_PAYLOADS) {
|
||||
|
@ -315,7 +320,7 @@ describe('Member', () => {
|
|||
});
|
||||
|
||||
test('POST /me/survey should succeed with valid inputs', async () => {
|
||||
const member = await testDb.createUser();
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const authMemberAgent = utils.createAgent(app, { auth: true, user: member });
|
||||
|
||||
const validPayloads = [SURVEY, {}];
|
|
@ -11,26 +11,25 @@ import {
|
|||
randomValidPassword,
|
||||
randomInvalidPassword,
|
||||
} from './shared/random';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true });
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.createOwnerShell();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
});
|
||||
|
||||
|
@ -39,10 +38,17 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
test('POST /owner should create owner and enable isInstanceOwnerSetUp', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.post('/owner').send(TEST_USER);
|
||||
const newOwnerData = {
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const response = await authOwnerAgent.post('/owner').send(newOwnerData);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
|
@ -59,9 +65,9 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
|||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(email).toBe(newOwnerData.email);
|
||||
expect(firstName).toBe(newOwnerData.firstName);
|
||||
expect(lastName).toBe(newOwnerData.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
|
@ -70,10 +76,10 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
|||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
expect(storedOwner.password).not.toBe(TEST_USER.password);
|
||||
expect(storedOwner.email).toBe(TEST_USER.email);
|
||||
expect(storedOwner.firstName).toBe(TEST_USER.firstName);
|
||||
expect(storedOwner.lastName).toBe(TEST_USER.lastName);
|
||||
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email);
|
||||
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
||||
expect(storedOwner.lastName).toBe(newOwnerData.lastName);
|
||||
|
||||
const isInstanceOwnerSetUpConfig = config.get('userManagement.isInstanceOwnerSetUp');
|
||||
expect(isInstanceOwnerSetUpConfig).toBe(true);
|
||||
|
@ -83,18 +89,20 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
|||
});
|
||||
|
||||
test('POST /owner should fail with invalid inputs', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
for (const invalidPayload of INVALID_POST_OWNER_PAYLOADS) {
|
||||
const response = await authOwnerAgent.post('/owner').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}
|
||||
await Promise.all(
|
||||
INVALID_POST_OWNER_PAYLOADS.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/owner').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /owner/skip-setup should persist skipping setup to the DB', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const response = await authOwnerAgent.post('/owner/skip-setup').send();
|
||||
|
||||
|
@ -109,13 +117,6 @@ test('POST /owner/skip-setup should persist skipping setup to the DB', async ()
|
|||
expect(value).toBe('true');
|
||||
});
|
||||
|
||||
const TEST_USER = {
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const INVALID_POST_OWNER_PAYLOADS = [
|
||||
{
|
||||
email: '',
|
|
@ -11,51 +11,35 @@ import {
|
|||
randomName,
|
||||
randomValidPassword,
|
||||
} from './shared/random';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
|
||||
globalOwnerRole = await Db.collections.Role!.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'global',
|
||||
});
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
|
||||
utils.initTestTelemetry();
|
||||
utils.initTestLogger();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.isolateModules(() => {
|
||||
jest.mock('../../config');
|
||||
});
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
|
||||
jest.mock('../../config');
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
config.set('userManagement.emails.mode', '');
|
||||
|
||||
await testDb.createUser({
|
||||
id: INITIAL_TEST_USER.id,
|
||||
email: INITIAL_TEST_USER.email,
|
||||
password: INITIAL_TEST_USER.password,
|
||||
firstName: INITIAL_TEST_USER.firstName,
|
||||
lastName: INITIAL_TEST_USER.lastName,
|
||||
globalRole: globalOwnerRole,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
jest.setTimeout(30000); // fake SMTP service might be slow
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -63,47 +47,38 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
test('POST /forgot-password should send password reset email', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const {
|
||||
user,
|
||||
pass,
|
||||
smtp: { host, port, secure },
|
||||
} = await utils.getSmtpTestAccount();
|
||||
await utils.configureSmtp();
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
config.set('userManagement.emails.smtp.host', host);
|
||||
config.set('userManagement.emails.smtp.port', port);
|
||||
config.set('userManagement.emails.smtp.secure', secure);
|
||||
config.set('userManagement.emails.smtp.auth.user', user);
|
||||
config.set('userManagement.emails.smtp.auth.pass', pass);
|
||||
|
||||
const response = await authlessAgent
|
||||
.post('/forgot-password')
|
||||
.send({ email: INITIAL_TEST_USER.email });
|
||||
const response = await authlessAgent.post('/forgot-password').send({ email: owner.email });
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({});
|
||||
|
||||
const owner = await Db.collections.User!.findOneOrFail({ email: INITIAL_TEST_USER.email });
|
||||
expect(owner.resetPasswordToken).toBeDefined();
|
||||
expect(owner.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeDefined();
|
||||
expect(storedOwner.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
});
|
||||
|
||||
test('POST /forgot-password should fail if emailing is not set up', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent
|
||||
.post('/forgot-password')
|
||||
.send({ email: INITIAL_TEST_USER.email });
|
||||
const response = await authlessAgent.post('/forgot-password').send({ email: owner.email });
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
const owner = await Db.collections.User!.findOneOrFail({ email: INITIAL_TEST_USER.email });
|
||||
expect(owner.resetPasswordToken).toBeNull();
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||
});
|
||||
|
||||
test('POST /forgot-password should fail with invalid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
@ -116,13 +91,15 @@ test('POST /forgot-password should fail with invalid inputs', async () => {
|
|||
[{ email: randomName() }],
|
||||
];
|
||||
|
||||
for (const invalidPayload of invalidPayloads) {
|
||||
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const owner = await Db.collections.User!.findOneOrFail({ email: INITIAL_TEST_USER.email });
|
||||
expect(owner.resetPasswordToken).toBeNull();
|
||||
}
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /forgot-password should fail if user is not found', async () => {
|
||||
|
@ -132,38 +109,39 @@ test('POST /forgot-password should fail if user is not found', async () => {
|
|||
|
||||
const response = await authlessAgent.post('/forgot-password').send({ email: randomEmail() });
|
||||
|
||||
// response should have 200 to not provide any information to the requester
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.statusCode).toBe(200); // expect 200 to remain vague
|
||||
});
|
||||
|
||||
test('GET /resolve-password-token should succeed with valid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(INITIAL_TEST_USER.id, {
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
||||
const response = await authlessAgent
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: INITIAL_TEST_USER.id, token: resetPasswordToken });
|
||||
.query({ userId: owner.id, token: resetPasswordToken });
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('GET /resolve-password-token should fail with invalid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
||||
const first = await authlessAgent.get('/resolve-password-token').query({ token: uuid() });
|
||||
|
||||
const second = await authlessAgent
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: INITIAL_TEST_USER.id });
|
||||
const second = await authlessAgent.get('/resolve-password-token').query({ userId: owner.id });
|
||||
|
||||
for (const response of [first, second]) {
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
@ -171,24 +149,28 @@ test('GET /resolve-password-token should fail with invalid inputs', async () =>
|
|||
});
|
||||
|
||||
test('GET /resolve-password-token should fail if user is not found', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
|
||||
const response = await authlessAgent
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: INITIAL_TEST_USER.id, token: uuid() });
|
||||
.query({ userId: owner.id, token: uuid() });
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('GET /resolve-password-token should fail if token is expired', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||
|
||||
await Db.collections.User!.update(INITIAL_TEST_USER.id, {
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -197,18 +179,20 @@ test('GET /resolve-password-token should fail if token is expired', async () =>
|
|||
|
||||
const response = await authlessAgent
|
||||
.get('/resolve-password-token')
|
||||
.query({ userId: INITIAL_TEST_USER.id, token: resetPasswordToken });
|
||||
.query({ userId: owner.id, token: resetPasswordToken });
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('POST /change-password should succeed with valid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(INITIAL_TEST_USER.id, {
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -217,7 +201,7 @@ test('POST /change-password should succeed with valid inputs', async () => {
|
|||
|
||||
const response = await authlessAgent.post('/change-password').send({
|
||||
token: resetPasswordToken,
|
||||
userId: INITIAL_TEST_USER.id,
|
||||
userId: owner.id,
|
||||
password: passwordToStore,
|
||||
});
|
||||
|
||||
|
@ -226,63 +210,65 @@ test('POST /change-password should succeed with valid inputs', async () => {
|
|||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
|
||||
const { password: storedPassword } = await Db.collections.User!.findOneOrFail(
|
||||
INITIAL_TEST_USER.id,
|
||||
);
|
||||
const { password: storedPassword } = await Db.collections.User!.findOneOrFail(owner.id);
|
||||
|
||||
const comparisonResult = await compare(passwordToStore, storedPassword!);
|
||||
const comparisonResult = await compare(passwordToStore, storedPassword);
|
||||
expect(comparisonResult).toBe(true);
|
||||
expect(storedPassword).not.toBe(passwordToStore);
|
||||
});
|
||||
|
||||
test('POST /change-password should fail with invalid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(INITIAL_TEST_USER.id, {
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
||||
const invalidPayloads = [
|
||||
{ token: uuid() },
|
||||
{ id: INITIAL_TEST_USER.id },
|
||||
{ id: owner.id },
|
||||
{ password: randomValidPassword() },
|
||||
{ token: uuid(), id: INITIAL_TEST_USER.id },
|
||||
{ token: uuid(), id: owner.id },
|
||||
{ token: uuid(), password: randomValidPassword() },
|
||||
{ id: INITIAL_TEST_USER.id, password: randomValidPassword() },
|
||||
{ id: owner.id, password: randomValidPassword() },
|
||||
{
|
||||
id: INITIAL_TEST_USER.id,
|
||||
id: owner.id,
|
||||
password: randomInvalidPassword(),
|
||||
token: resetPasswordToken,
|
||||
},
|
||||
{
|
||||
id: INITIAL_TEST_USER.id,
|
||||
id: owner.id,
|
||||
password: randomValidPassword(),
|
||||
token: uuid(),
|
||||
},
|
||||
];
|
||||
|
||||
const { password: originalHashedPassword } = await Db.collections.User!.findOneOrFail();
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authlessAgent.post('/change-password').query(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
for (const invalidPayload of invalidPayloads) {
|
||||
const response = await authlessAgent.post('/change-password').query(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const { password: fetchedHashedPassword } = await Db.collections.User!.findOneOrFail();
|
||||
expect(originalHashedPassword).toBe(fetchedHashedPassword);
|
||||
}
|
||||
const { password: storedPassword } = await Db.collections.User!.findOneOrFail();
|
||||
expect(owner.password).toBe(storedPassword);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /change-password should fail when token has expired', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||
|
||||
await Db.collections.User!.update(INITIAL_TEST_USER.id, {
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -291,17 +277,9 @@ test('POST /change-password should fail when token has expired', async () => {
|
|||
|
||||
const response = await authlessAgent.post('/change-password').send({
|
||||
token: resetPasswordToken,
|
||||
userId: INITIAL_TEST_USER.id,
|
||||
userId: owner.id,
|
||||
password: passwordToStore,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
const INITIAL_TEST_USER = {
|
||||
id: uuid(),
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { createConnection, getConnection, ConnectionOptions } from 'typeorm';
|
||||
import { createConnection, getConnection, ConnectionOptions, Connection } from 'typeorm';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
|
||||
import config = require('../../../config');
|
||||
|
@ -6,17 +6,17 @@ import { BOOTSTRAP_MYSQL_CONNECTION_NAME, BOOTSTRAP_POSTGRES_CONNECTION_NAME } f
|
|||
import { DatabaseType, Db, ICredentialsDb, IDatabaseCollections } from '../../../src';
|
||||
import { randomEmail, randomName, randomString, randomValidPassword } from './random';
|
||||
import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
||||
|
||||
import { hashPassword } from '../../../src/UserManagement/UserManagementHelper';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '../../../src/constants';
|
||||
import { entities } from '../../../src/databases/entities';
|
||||
import { mysqlMigrations } from '../../../src/databases/mysqldb/migrations';
|
||||
import { postgresMigrations } from '../../../src/databases/postgresdb/migrations';
|
||||
import { sqliteMigrations } from '../../../src/databases/sqlite/migrations';
|
||||
import { categorize } from './utils';
|
||||
|
||||
import type { Role } from '../../../src/databases/entities/Role';
|
||||
import type { User } from '../../../src/databases/entities/User';
|
||||
import type { CredentialPayload } from './types';
|
||||
import { genSaltSync, hashSync } from 'bcryptjs';
|
||||
import type { CollectionName, CredentialPayload } from './types';
|
||||
|
||||
/**
|
||||
* Initialize one test DB per suite run, with bootstrap connection if needed.
|
||||
|
@ -97,22 +97,49 @@ export async function terminate(testDbName: string) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Truncate DB tables for specified entities.
|
||||
* Truncate DB tables for collections.
|
||||
*
|
||||
* @param entities Array of entity names whose tables to truncate.
|
||||
* @param collections Array of entity names whose tables to truncate.
|
||||
* @param testDbName Name of the test DB to truncate tables in.
|
||||
*/
|
||||
export async function truncate(entities: Array<keyof IDatabaseCollections>, testDbName: string) {
|
||||
export async function truncate(collections: CollectionName[], testDbName: string) {
|
||||
const dbType = config.get('database.type');
|
||||
|
||||
const testDb = getConnection(testDbName);
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
const testDb = getConnection(testDbName);
|
||||
await testDb.query('PRAGMA foreign_keys=OFF');
|
||||
await Promise.all(entities.map((entity) => Db.collections[entity]!.clear()));
|
||||
await Promise.all(collections.map((collection) => Db.collections[collection]!.clear()));
|
||||
return testDb.query('PRAGMA foreign_keys=ON');
|
||||
}
|
||||
|
||||
const map: { [K in keyof IDatabaseCollections]: string } = {
|
||||
if (dbType === 'postgresdb') {
|
||||
return Promise.all(
|
||||
collections.map((collection) => {
|
||||
const tableName = toTableName(collection);
|
||||
testDb.query(`TRUNCATE TABLE "${tableName}" RESTART IDENTITY CASCADE;`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL `TRUNCATE` requires enabling and disabling the global variable `foreign_key_checks`,
|
||||
* which cannot be safely manipulated by parallel tests, so use `DELETE` and `AUTO_INCREMENT`.
|
||||
* Clear shared tables first to avoid deadlock: https://stackoverflow.com/a/41174997
|
||||
*/
|
||||
if (dbType === 'mysqldb') {
|
||||
const { pass: isShared, fail: isNotShared } = categorize(
|
||||
collections,
|
||||
(collectionName: CollectionName) => collectionName.toLowerCase().startsWith('shared'),
|
||||
);
|
||||
|
||||
await truncateMySql(testDb, isShared);
|
||||
await truncateMySql(testDb, isNotShared);
|
||||
}
|
||||
}
|
||||
|
||||
function toTableName(collectionName: CollectionName) {
|
||||
return {
|
||||
Credentials: 'credentials_entity',
|
||||
Workflow: 'workflow_entity',
|
||||
Execution: 'execution_entity',
|
||||
|
@ -123,27 +150,17 @@ export async function truncate(entities: Array<keyof IDatabaseCollections>, test
|
|||
SharedCredentials: 'shared_credentials',
|
||||
SharedWorkflow: 'shared_workflow',
|
||||
Settings: 'settings',
|
||||
};
|
||||
}[collectionName];
|
||||
}
|
||||
|
||||
if (dbType === 'postgresdb') {
|
||||
return Promise.all(
|
||||
entities.map((entity) =>
|
||||
getConnection(testDbName).query(
|
||||
`TRUNCATE TABLE "${map[entity]}" RESTART IDENTITY CASCADE;`,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// MySQL truncation requires globals, which cannot be safely manipulated by parallel tests
|
||||
if (dbType === 'mysqldb') {
|
||||
await Promise.all(
|
||||
entities.map(async (entity) => {
|
||||
await Db.collections[entity]!.delete({});
|
||||
await getConnection(testDbName).query(`ALTER TABLE ${map[entity]} AUTO_INCREMENT = 1;`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
function truncateMySql(connection: Connection, collections: Array<keyof IDatabaseCollections>) {
|
||||
return Promise.all(
|
||||
collections.map(async (collection) => {
|
||||
const tableName = toTableName(collection);
|
||||
await connection.query(`DELETE FROM ${tableName};`);
|
||||
await connection.query(`ALTER TABLE ${tableName} AUTO_INCREMENT = 1;`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -179,63 +196,65 @@ export async function saveCredential(
|
|||
}
|
||||
|
||||
// ----------------------------------
|
||||
// user creation
|
||||
// user creation
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* Store a user in the DB, defaulting to a `member`.
|
||||
*/
|
||||
export async function createUser(attributes: Partial<User> = {}): Promise<User> {
|
||||
export async function createUser(attributes: Partial<User> & { globalRole: Role }): Promise<User> {
|
||||
const { email, password, firstName, lastName, globalRole, ...rest } = attributes;
|
||||
|
||||
const user = {
|
||||
email: email ?? randomEmail(),
|
||||
password: hashSync(password ?? randomValidPassword(), genSaltSync(10)),
|
||||
password: await hashPassword(password ?? randomValidPassword()),
|
||||
firstName: firstName ?? randomName(),
|
||||
lastName: lastName ?? randomName(),
|
||||
globalRole: globalRole ?? (await getGlobalMemberRole()),
|
||||
globalRole,
|
||||
...rest,
|
||||
};
|
||||
|
||||
return Db.collections.User!.save(user);
|
||||
}
|
||||
|
||||
export async function createOwnerShell() {
|
||||
const globalRole = await getGlobalOwnerRole();
|
||||
return Db.collections.User!.save({ globalRole });
|
||||
}
|
||||
export function createUserShell(globalRole: Role): Promise<User> {
|
||||
if (globalRole.scope !== 'global') {
|
||||
throw new Error(`Invalid role received: ${JSON.stringify(globalRole)}`);
|
||||
}
|
||||
|
||||
export async function createMemberShell() {
|
||||
const globalRole = await getGlobalMemberRole();
|
||||
return Db.collections.User!.save({ globalRole });
|
||||
const shell: Partial<User> = { globalRole };
|
||||
|
||||
if (globalRole.name !== 'owner') {
|
||||
shell.email = randomEmail();
|
||||
}
|
||||
|
||||
return Db.collections.User!.save(shell);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// role fetchers
|
||||
// ----------------------------------
|
||||
|
||||
export async function getGlobalOwnerRole() {
|
||||
return await Db.collections.Role!.findOneOrFail({
|
||||
export function getGlobalOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'global',
|
||||
});
|
||||
}
|
||||
|
||||
export async function getGlobalMemberRole() {
|
||||
return await Db.collections.Role!.findOneOrFail({
|
||||
export function getGlobalMemberRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
name: 'member',
|
||||
scope: 'global',
|
||||
});
|
||||
}
|
||||
|
||||
export async function getWorkflowOwnerRole() {
|
||||
return await Db.collections.Role!.findOneOrFail({
|
||||
export function getWorkflowOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'workflow',
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCredentialOwnerRole() {
|
||||
return await Db.collections.Role!.findOneOrFail({
|
||||
export function getCredentialOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'credential',
|
||||
});
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n-workflow';
|
||||
import type { ICredentialsDb } from '../../../src';
|
||||
import type { ICredentialsDb, IDatabaseCollections } from '../../../src';
|
||||
import type { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
||||
import type { User } from '../../../src/databases/entities/User';
|
||||
|
||||
export type CollectionName = keyof IDatabaseCollections;
|
||||
|
||||
export type SmtpTestAccount = {
|
||||
user: string;
|
||||
pass: string;
|
||||
|
|
|
@ -23,9 +23,7 @@ import { passwordResetNamespace as passwordResetEndpoints } from '../../../src/U
|
|||
import { issueJWT } from '../../../src/UserManagement/auth/jwt';
|
||||
import { getLogger } from '../../../src/Logger';
|
||||
import { credentialsController } from '../../../src/api/credentials.api';
|
||||
|
||||
import type { User } from '../../../src/databases/entities/User';
|
||||
import { Telemetry } from '../../../src/telemetry';
|
||||
import type { EndpointGroup, SmtpTestAccount } from './types';
|
||||
import type { N8nApp } from '../../../src/UserManagement/Interfaces';
|
||||
|
||||
|
@ -182,9 +180,7 @@ export function prefix(pathSegment: string) {
|
|||
export function getAuthToken(response: request.Response, authCookieName = AUTH_COOKIE_NAME) {
|
||||
const cookies: string[] = response.headers['set-cookie'];
|
||||
|
||||
if (!cookies) {
|
||||
throw new Error("No 'set-cookie' header found in response");
|
||||
}
|
||||
if (!cookies) return undefined;
|
||||
|
||||
const authCookie = cookies.find((c) => c.startsWith(`${authCookieName}=`));
|
||||
|
||||
|
@ -216,5 +212,37 @@ export async function isInstanceOwnerSetUp() {
|
|||
/**
|
||||
* Get an SMTP test account from https://ethereal.email to test sending emails.
|
||||
*/
|
||||
export const getSmtpTestAccount = util.promisify<SmtpTestAccount>(createTestAccount);
|
||||
const getSmtpTestAccount = util.promisify<SmtpTestAccount>(createTestAccount);
|
||||
|
||||
export async function configureSmtp() {
|
||||
const {
|
||||
user,
|
||||
pass,
|
||||
smtp: { host, port, secure },
|
||||
} = await getSmtpTestAccount();
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
config.set('userManagement.emails.smtp.host', host);
|
||||
config.set('userManagement.emails.smtp.port', port);
|
||||
config.set('userManagement.emails.smtp.secure', secure);
|
||||
config.set('userManagement.emails.smtp.auth.user', user);
|
||||
config.set('userManagement.emails.smtp.auth.pass', pass);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// misc
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* Categorize array items into two groups based on whether they pass a test.
|
||||
*/
|
||||
export const categorize = <T>(arr: T[], test: (str: T) => boolean) => {
|
||||
return arr.reduce<{ pass: T[]; fail: T[] }>(
|
||||
(acc, cur) => {
|
||||
test(cur) ? acc.pass.push(cur) : acc.fail.push(cur);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ pass: [], fail: [] },
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import express = require('express');
|
||||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { compare, genSaltSync, hashSync } from 'bcryptjs';
|
||||
|
||||
import { Db } from '../../src';
|
||||
import config = require('../../config');
|
||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import {
|
||||
randomEmail,
|
||||
randomValidPassword,
|
||||
|
@ -15,15 +13,18 @@ import {
|
|||
} from './shared/random';
|
||||
import { CredentialsEntity } from '../../src/databases/entities/CredentialsEntity';
|
||||
import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
import type { User } from '../../src/databases/entities/User';
|
||||
import * as utils from './shared/utils';
|
||||
import * as testDb from './shared/testDb';
|
||||
import { compareHash } from '../../src/UserManagement/UserManagementHelper';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let globalOwnerRole: Role;
|
||||
let workflowOwnerRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
|
||||
|
@ -46,25 +47,17 @@ beforeAll(async () => {
|
|||
|
||||
utils.initTestTelemetry();
|
||||
utils.initTestLogger();
|
||||
|
||||
jest.setTimeout(30000); // fake SMTP service might be slow
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// do not combine calls - shared tables must be cleared first and separately
|
||||
await testDb.truncate(['SharedCredentials', 'SharedWorkflow'], testDbName);
|
||||
await testDb.truncate(['User', 'Workflow', 'Credentials'], testDbName);
|
||||
await testDb.truncate(
|
||||
['User', 'SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials'],
|
||||
testDbName,
|
||||
);
|
||||
|
||||
jest.isolateModules(() => {
|
||||
jest.mock('../../config');
|
||||
});
|
||||
|
||||
await testDb.createUser({
|
||||
id: INITIAL_TEST_USER.id,
|
||||
email: INITIAL_TEST_USER.email,
|
||||
password: INITIAL_TEST_USER.password,
|
||||
firstName: INITIAL_TEST_USER.firstName,
|
||||
lastName: INITIAL_TEST_USER.lastName,
|
||||
globalRole: globalOwnerRole,
|
||||
});
|
||||
jest.mock('../../config');
|
||||
|
||||
config.set('userManagement.disabled', false);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
@ -76,46 +69,48 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
test('GET /users should return all users', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
await testDb.createUser();
|
||||
await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.length).toBe(2);
|
||||
|
||||
for (const user of response.body.data) {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
} = user;
|
||||
await Promise.all(
|
||||
response.body.data.map(async (user: User) => {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
} = user;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeDefined();
|
||||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeDefined();
|
||||
}
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeDefined();
|
||||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeDefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('DELETE /users/:id should delete the user', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const userToDelete = await testDb.createUser();
|
||||
const userToDelete = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const newWorkflow = new WorkflowEntity();
|
||||
|
||||
|
@ -181,7 +176,7 @@ test('DELETE /users/:id should delete the user', async () => {
|
|||
});
|
||||
|
||||
test('DELETE /users/:id should fail to delete self', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.delete(`/users/${owner.id}`);
|
||||
|
@ -193,10 +188,10 @@ test('DELETE /users/:id should fail to delete self', async () => {
|
|||
});
|
||||
|
||||
test('DELETE /users/:id should fail if user to delete is transferee', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const { id: idToDelete } = await testDb.createUser();
|
||||
const { id: idToDelete } = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const response = await authOwnerAgent.delete(`/users/${idToDelete}`).query({
|
||||
transferId: idToDelete,
|
||||
|
@ -209,7 +204,7 @@ test('DELETE /users/:id should fail if user to delete is transferee', async () =
|
|||
});
|
||||
|
||||
test('DELETE /users/:id with transferId should perform transfer', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const userToDelete = await Db.collections.User!.save({
|
||||
|
@ -281,36 +276,34 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|||
});
|
||||
|
||||
test('GET /resolve-signup-token should validate invite token', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const { id: inviteeId } = await testDb.createMemberShell();
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
.query({ inviterId: INITIAL_TEST_USER.id })
|
||||
.query({ inviteeId });
|
||||
.query({ inviterId: owner.id })
|
||||
.query({ inviteeId: memberShell.id });
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
data: {
|
||||
inviter: {
|
||||
firstName: INITIAL_TEST_USER.firstName,
|
||||
lastName: INITIAL_TEST_USER.lastName,
|
||||
firstName: owner.firstName,
|
||||
lastName: owner.lastName,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('GET /resolve-signup-token should fail with invalid inputs', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const { id: inviteeId } = await testDb.createUser();
|
||||
const { id: inviteeId } = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const first = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
.query({ inviterId: INITIAL_TEST_USER.id });
|
||||
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
|
||||
|
||||
const second = await authOwnerAgent.get('/resolve-signup-token').query({ inviteeId });
|
||||
|
||||
|
@ -322,14 +315,14 @@ test('GET /resolve-signup-token should fail with invalid inputs', async () => {
|
|||
// user is already set up, so call should error
|
||||
const fourth = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
.query({ inviterId: INITIAL_TEST_USER.id })
|
||||
.query({ inviterId: owner.id })
|
||||
.query({ inviteeId });
|
||||
|
||||
// cause inconsistent DB state
|
||||
await Db.collections.User!.update(owner.id, { email: '' });
|
||||
const fifth = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
.query({ inviterId: INITIAL_TEST_USER.id })
|
||||
.query({ inviterId: owner.id })
|
||||
.query({ inviteeId });
|
||||
|
||||
for (const response of [first, second, third, fourth, fifth]) {
|
||||
|
@ -338,21 +331,20 @@ test('GET /resolve-signup-token should fail with invalid inputs', async () => {
|
|||
});
|
||||
|
||||
test('POST /users/:id should fill out a user shell', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
|
||||
const memberData = {
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const userToFillOut = await Db.collections.User!.save({
|
||||
email: randomEmail(),
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
const newPassword = randomValidPassword();
|
||||
|
||||
const response = await authlessAgent.post(`/users/${userToFillOut.id}`).send({
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
firstName: INITIAL_TEST_USER.firstName,
|
||||
lastName: INITIAL_TEST_USER.lastName,
|
||||
password: newPassword,
|
||||
});
|
||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(memberData);
|
||||
|
||||
const {
|
||||
id,
|
||||
|
@ -368,8 +360,8 @@ test('POST /users/:id should fill out a user shell', async () => {
|
|||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBe(INITIAL_TEST_USER.firstName);
|
||||
expect(lastName).toBe(INITIAL_TEST_USER.lastName);
|
||||
expect(firstName).toBe(memberData.firstName);
|
||||
expect(lastName).toBe(memberData.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
|
@ -379,69 +371,98 @@ test('POST /users/:id should fill out a user shell', async () => {
|
|||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
|
||||
const filledOutUser = await Db.collections.User!.findOneOrFail(userToFillOut.id);
|
||||
expect(filledOutUser.firstName).toBe(INITIAL_TEST_USER.firstName);
|
||||
expect(filledOutUser.lastName).toBe(INITIAL_TEST_USER.lastName);
|
||||
expect(filledOutUser.password).not.toBe(newPassword);
|
||||
const member = await Db.collections.User!.findOneOrFail(memberShell.id);
|
||||
expect(member.firstName).toBe(memberData.firstName);
|
||||
expect(member.lastName).toBe(memberData.lastName);
|
||||
expect(member.password).not.toBe(memberData.password);
|
||||
});
|
||||
|
||||
test('POST /users/:id should fail with invalid inputs', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const emailToStore = randomEmail();
|
||||
const memberShellEmail = randomEmail();
|
||||
|
||||
const userToFillOut = await Db.collections.User!.save({
|
||||
email: emailToStore,
|
||||
const memberShell = await Db.collections.User!.save({
|
||||
email: memberShellEmail,
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
for (const invalidPayload of INVALID_FILL_OUT_USER_PAYLOADS) {
|
||||
const response = await authlessAgent.post(`/users/${userToFillOut.id}`).send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
const invalidPayloads = [
|
||||
{
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
},
|
||||
{
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomInvalidPassword(),
|
||||
},
|
||||
];
|
||||
|
||||
const user = await Db.collections.User!.findOneOrFail({ where: { email: emailToStore } });
|
||||
expect(user.firstName).toBeNull();
|
||||
expect(user.lastName).toBeNull();
|
||||
expect(user.password).toBeNull();
|
||||
}
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedUser = await Db.collections.User!.findOneOrFail({
|
||||
where: { email: memberShellEmail },
|
||||
});
|
||||
expect(storedUser.firstName).toBeNull();
|
||||
expect(storedUser.lastName).toBeNull();
|
||||
expect(storedUser.password).toBeNull();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /users/:id should fail with already accepted invite', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
|
||||
const globalMemberRole = await Db.collections.Role!.findOneOrFail({
|
||||
name: 'member',
|
||||
scope: 'global',
|
||||
});
|
||||
|
||||
const shell = await Db.collections.User!.save({
|
||||
email: randomEmail(),
|
||||
password: hashSync(randomValidPassword(), genSaltSync(10)), // simulate accepted invite
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
const newPassword = randomValidPassword();
|
||||
|
||||
const response = await authlessAgent.post(`/users/${shell.id}`).send({
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
const newMemberData = {
|
||||
inviterId: owner.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
});
|
||||
};
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent.post(`/users/${member.id}`).send(newMemberData);
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const fetchedShell = await Db.collections.User!.findOneOrFail({ where: { email: shell.email } });
|
||||
expect(fetchedShell.firstName).toBeNull();
|
||||
expect(fetchedShell.lastName).toBeNull();
|
||||
const storedMember = await Db.collections.User!.findOneOrFail({
|
||||
where: { email: member.email },
|
||||
});
|
||||
expect(storedMember.firstName).not.toBe(newMemberData.firstName);
|
||||
expect(storedMember.lastName).not.toBe(newMemberData.lastName);
|
||||
|
||||
const comparisonResult = await compare(shell.password, newPassword);
|
||||
const comparisonResult = await compareHash(member.password, storedMember.password);
|
||||
expect(comparisonResult).toBe(false);
|
||||
expect(newPassword).not.toBe(fetchedShell.password);
|
||||
expect(storedMember.password).not.toBe(newMemberData.password);
|
||||
});
|
||||
|
||||
test('POST /users should fail if emailing is not set up', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.post('/users').send([{ email: randomEmail() }]);
|
||||
|
@ -450,7 +471,7 @@ test('POST /users should fail if emailing is not set up', async () => {
|
|||
});
|
||||
|
||||
test('POST /users should fail if user management is disabled', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
config.set('userManagement.disabled', true);
|
||||
|
@ -461,54 +482,47 @@ test('POST /users should fail if user management is disabled', async () => {
|
|||
});
|
||||
|
||||
test('POST /users should email invites and create user shells', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const {
|
||||
user,
|
||||
pass,
|
||||
smtp: { host, port, secure },
|
||||
} = await utils.getSmtpTestAccount();
|
||||
await utils.configureSmtp();
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
config.set('userManagement.emails.smtp.host', host);
|
||||
config.set('userManagement.emails.smtp.port', port);
|
||||
config.set('userManagement.emails.smtp.secure', secure);
|
||||
config.set('userManagement.emails.smtp.auth.user', user);
|
||||
config.set('userManagement.emails.smtp.auth.pass', pass);
|
||||
const testEmails = [randomEmail(), randomEmail(), randomEmail()];
|
||||
|
||||
const payload = TEST_EMAILS_TO_CREATE_USER_SHELLS.map((e) => ({ email: e }));
|
||||
const payload = testEmails.map((e) => ({ email: e }));
|
||||
|
||||
const response = await authOwnerAgent.post('/users').send(payload);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
for (const {
|
||||
user: { id, email: receivedEmail },
|
||||
error,
|
||||
} of response.body.data) {
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(TEST_EMAILS_TO_CREATE_USER_SHELLS.some((e) => e === receivedEmail)).toBe(true);
|
||||
if (error) {
|
||||
expect(error).toBe('Email could not be sent');
|
||||
}
|
||||
await Promise.all(
|
||||
response.body.data.map(async ({ user, error }: { user: User; error: Error }) => {
|
||||
const { id, email: receivedEmail } = user;
|
||||
|
||||
const user = await Db.collections.User!.findOneOrFail(id);
|
||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } = user;
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(testEmails.some((e) => e === receivedEmail)).toBe(true);
|
||||
if (error) {
|
||||
expect(error).toBe('Email could not be sent');
|
||||
}
|
||||
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).toBeNull();
|
||||
}
|
||||
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||
storedUser;
|
||||
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).toBeNull();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /users should fail with invalid inputs', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
await utils.configureSmtp();
|
||||
|
||||
const invalidPayloads = [
|
||||
randomEmail(),
|
||||
|
@ -518,20 +532,22 @@ test('POST /users should fail with invalid inputs', async () => {
|
|||
[{ email: randomName() }],
|
||||
];
|
||||
|
||||
for (const invalidPayload of invalidPayloads) {
|
||||
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
await Promise.all(
|
||||
invalidPayloads.map(async (invalidPayload) => {
|
||||
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const users = await Db.collections.User!.find();
|
||||
expect(users.length).toBe(1); // DB unaffected
|
||||
}
|
||||
const users = await Db.collections.User!.find();
|
||||
expect(users.length).toBe(1); // DB unaffected
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /users should ignore an empty payload', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
await utils.configureSmtp();
|
||||
|
||||
const response = await authOwnerAgent.post('/users').send([]);
|
||||
|
||||
|
@ -561,42 +577,3 @@ test('POST /users should ignore an empty payload', async () => {
|
|||
|
||||
// expect(response.statusCode).toBe(500);
|
||||
// });
|
||||
|
||||
const INITIAL_TEST_USER = {
|
||||
id: uuid(),
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const INVALID_FILL_OUT_USER_PAYLOADS = [
|
||||
{
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
firstName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
firstName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
},
|
||||
{
|
||||
inviterId: INITIAL_TEST_USER.id,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomInvalidPassword(),
|
||||
},
|
||||
];
|
||||
|
||||
const TEST_EMAILS_TO_CREATE_USER_SHELLS = [randomEmail(), randomEmail(), randomEmail()];
|
Loading…
Reference in a new issue