mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
Co-authored-by: Nuno Góis <github@nunogois.com> Co-authored-by: Gard Rimestad <gardleopard@gmail.com>
581 lines
18 KiB
TypeScript
581 lines
18 KiB
TypeScript
import dbInit, { type ITestDb } from '../helpers/database-init';
|
|
import getLogger from '../../fixtures/no-logger';
|
|
import UserService from '../../../lib/services/user-service';
|
|
import { AccessService } from '../../../lib/services/access-service';
|
|
import ResetTokenService from '../../../lib/services/reset-token-service';
|
|
import { EmailService } from '../../../lib/services/email-service';
|
|
import { createTestConfig } from '../../config/test-config';
|
|
import SessionService from '../../../lib/services/session-service';
|
|
import NotFoundError from '../../../lib/error/notfound-error';
|
|
import type { IRole } from '../../../lib/types/stores/access-store';
|
|
import { RoleName } from '../../../lib/types/model';
|
|
import SettingService from '../../../lib/services/setting-service';
|
|
import { simpleAuthSettingsKey } from '../../../lib/types/settings/simple-auth-settings';
|
|
import { addDays, minutesToMilliseconds } from 'date-fns';
|
|
import { GroupService } from '../../../lib/services/group-service';
|
|
import { BadDataError } from '../../../lib/error';
|
|
import PasswordMismatch from '../../../lib/error/password-mismatch';
|
|
import { EventService } from '../../../lib/services';
|
|
import {
|
|
CREATE_ADDON,
|
|
type IUnleashStores,
|
|
type IUserStore,
|
|
SYSTEM_USER_AUDIT,
|
|
TEST_AUDIT_USER,
|
|
USER_CREATED,
|
|
USER_DELETED,
|
|
USER_UPDATED,
|
|
} from '../../../lib/types';
|
|
import { CUSTOM_ROOT_ROLE_TYPE } from '../../../lib/util';
|
|
import { PasswordPreviouslyUsedError } from '../../../lib/error/password-previously-used';
|
|
|
|
let db: ITestDb;
|
|
let stores: IUnleashStores;
|
|
let userService: UserService;
|
|
let userStore: IUserStore;
|
|
let adminRole: IRole;
|
|
let viewerRole: IRole;
|
|
let customRole: IRole;
|
|
let sessionService: SessionService;
|
|
let settingService: SettingService;
|
|
let eventService: EventService;
|
|
let accessService: AccessService;
|
|
|
|
beforeAll(async () => {
|
|
db = await dbInit('user_service_serial', getLogger);
|
|
stores = db.stores;
|
|
const config = createTestConfig();
|
|
eventService = new EventService(stores, config);
|
|
const groupService = new GroupService(stores, config, eventService);
|
|
accessService = new AccessService(
|
|
stores,
|
|
config,
|
|
groupService,
|
|
eventService,
|
|
);
|
|
const resetTokenService = new ResetTokenService(stores, config);
|
|
const emailService = new EmailService(config);
|
|
sessionService = new SessionService(stores, config);
|
|
settingService = new SettingService(stores, config, eventService);
|
|
|
|
userService = new UserService(stores, config, {
|
|
accessService,
|
|
resetTokenService,
|
|
emailService,
|
|
eventService,
|
|
sessionService,
|
|
settingService,
|
|
});
|
|
userStore = stores.userStore;
|
|
const rootRoles = await accessService.getRootRoles();
|
|
adminRole = rootRoles.find((r) => r.name === RoleName.ADMIN)!;
|
|
viewerRole = rootRoles.find((r) => r.name === RoleName.VIEWER)!;
|
|
customRole = await accessService.createRole(
|
|
{
|
|
name: 'Custom role',
|
|
type: CUSTOM_ROOT_ROLE_TYPE,
|
|
description: 'A custom role',
|
|
permissions: [
|
|
{
|
|
name: CREATE_ADDON,
|
|
},
|
|
],
|
|
createdByUserId: 1,
|
|
},
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await db.destroy();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await userStore.deleteAll();
|
|
});
|
|
|
|
test('should create initial admin user', async () => {
|
|
await userService.initAdminUser({
|
|
createAdminUser: true,
|
|
});
|
|
await expect(async () =>
|
|
userService.loginUser('admin', 'wrong-password'),
|
|
).rejects.toThrow(Error);
|
|
await expect(async () =>
|
|
userService.loginUser('admin', 'unleash4all'),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
test('should not init default user if we already have users', async () => {
|
|
await userService.createUser(
|
|
{
|
|
username: 'test',
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
SYSTEM_USER_AUDIT,
|
|
);
|
|
await userService.initAdminUser({
|
|
createAdminUser: true,
|
|
});
|
|
const users = await userService.getAll();
|
|
expect(users).toHaveLength(1);
|
|
expect(users[0].username).toBe('test');
|
|
await expect(async () =>
|
|
userService.loginUser('admin', 'unleash4all'),
|
|
).rejects.toThrow(Error);
|
|
});
|
|
|
|
test('should not be allowed to create existing user', async () => {
|
|
await userStore.insert({ username: 'test', name: 'Hans Mola' });
|
|
await expect(async () =>
|
|
userService.createUser(
|
|
{ username: 'test', rootRole: adminRole.id },
|
|
TEST_AUDIT_USER,
|
|
),
|
|
).rejects.toThrow(Error);
|
|
});
|
|
|
|
test('should create user with password', async () => {
|
|
await userService.createUser(
|
|
{
|
|
username: 'test',
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
const user = await userService.loginUser(
|
|
'test',
|
|
'A very strange P4ssw0rd_',
|
|
);
|
|
expect(user.username).toBe('test');
|
|
});
|
|
|
|
test('should create user with rootRole in audit-log', async () => {
|
|
const user = await userService.createUser(
|
|
{
|
|
username: 'test',
|
|
rootRole: viewerRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
const { events } = await eventService.getEvents();
|
|
expect(events[0].type).toBe(USER_CREATED);
|
|
expect(events[0].data.id).toBe(user.id);
|
|
expect(events[0].data.username).toBe('test');
|
|
expect(events[0].data.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should update user with rootRole in audit-log', async () => {
|
|
const user = await userService.createUser(
|
|
{
|
|
username: 'test',
|
|
rootRole: viewerRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.updateUser(
|
|
{ id: user.id, rootRole: adminRole.id },
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
const { events } = await eventService.getEvents();
|
|
expect(events[0].type).toBe(USER_UPDATED);
|
|
expect(events[0].data.id).toBe(user.id);
|
|
expect(events[0].data.username).toBe('test');
|
|
expect(events[0].data.rootRole).toBe(adminRole.id);
|
|
});
|
|
|
|
test('should remove user with rootRole in audit-log', async () => {
|
|
const user = await userService.createUser(
|
|
{
|
|
username: 'test',
|
|
rootRole: viewerRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.deleteUser(user.id, TEST_AUDIT_USER);
|
|
|
|
const { events } = await eventService.getEvents();
|
|
expect(events[0].type).toBe(USER_DELETED);
|
|
expect(events[0].preData.id).toBe(user.id);
|
|
expect(events[0].preData.username).toBe('test');
|
|
expect(events[0].preData.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should not be able to login with deleted user', async () => {
|
|
const user = await userService.createUser(
|
|
{
|
|
username: 'deleted_user',
|
|
password: 'unleash4all',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.deleteUser(user.id, TEST_AUDIT_USER);
|
|
|
|
await expect(
|
|
userService.loginUser('deleted_user', 'unleash4all'),
|
|
).rejects.toThrow(
|
|
new PasswordMismatch(
|
|
'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('should not be able to login without password_hash on user', async () => {
|
|
const user = await userService.createUser(
|
|
{
|
|
username: 'deleted_user',
|
|
password: 'unleash4all',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
/*@ts-ignore: we are testing for null on purpose! */
|
|
await userStore.setPasswordHash(user.id, null);
|
|
|
|
await expect(
|
|
userService.loginUser('deleted_user', 'anything-should-fail'),
|
|
).rejects.toThrow(
|
|
new PasswordMismatch(
|
|
'The combination of password and username you provided is invalid. If you have forgotten your password, visit /forgotten-password or get in touch with your instance administrator.',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('should not login user if simple auth is disabled', async () => {
|
|
await settingService.insert(
|
|
simpleAuthSettingsKey,
|
|
{ disabled: true },
|
|
TEST_AUDIT_USER,
|
|
true,
|
|
);
|
|
|
|
await userService.createUser(
|
|
{
|
|
username: 'test_no_pass',
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await expect(async () => {
|
|
await userService.loginUser('test_no_pass', 'A very strange P4ssw0rd_');
|
|
}).rejects.toThrowError(
|
|
'Logging in with username/password has been disabled.',
|
|
);
|
|
});
|
|
|
|
test('should login for user _without_ password', async () => {
|
|
const email = 'some@test.com';
|
|
await userService.createUser(
|
|
{
|
|
email,
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
const user = await userService.loginUserWithoutPassword(email);
|
|
expect(user.email).toBe(email);
|
|
});
|
|
|
|
test('should get user with root role', async () => {
|
|
const email = 'some@test.com';
|
|
const u = await userService.createUser(
|
|
{
|
|
email,
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
const user = await userService.getUser(u.id);
|
|
expect(user.email).toBe(email);
|
|
expect(user.id).toBe(u.id);
|
|
expect(user.rootRole).toBe(adminRole.id);
|
|
});
|
|
|
|
test('should get user with root role by name', async () => {
|
|
const email = 'some2@test.com';
|
|
const u = await userService.createUser(
|
|
{
|
|
email,
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: RoleName.ADMIN,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
const user = await userService.getUser(u.id);
|
|
expect(user.email).toBe(email);
|
|
expect(user.id).toBe(u.id);
|
|
expect(user.rootRole).toBe(adminRole.id);
|
|
});
|
|
|
|
test("deleting a user should delete the user's sessions", async () => {
|
|
const email = 'some@test.com';
|
|
const user = await userService.createUser(
|
|
{
|
|
email,
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
const testComSession = {
|
|
sid: 'xyz321',
|
|
sess: {
|
|
cookie: {
|
|
originalMaxAge: minutesToMilliseconds(48),
|
|
expires: addDays(Date.now(), 1).toDateString(),
|
|
secure: false,
|
|
httpOnly: true,
|
|
path: '/',
|
|
},
|
|
user,
|
|
},
|
|
};
|
|
await sessionService.insertSession(testComSession);
|
|
const userSessions = await sessionService.getSessionsForUser(user.id);
|
|
expect(userSessions.length).toBe(1);
|
|
await userService.deleteUser(user.id, TEST_AUDIT_USER);
|
|
await expect(async () =>
|
|
sessionService.getSessionsForUser(user.id),
|
|
).rejects.toThrow(NotFoundError);
|
|
});
|
|
|
|
test('updating a user without an email should not strip the email', async () => {
|
|
const email = 'some@test.com';
|
|
const user = await userService.createUser(
|
|
{
|
|
email,
|
|
password: 'A very strange P4ssw0rd_',
|
|
rootRole: adminRole.id,
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.updateUser(
|
|
{
|
|
id: user.id,
|
|
name: 'some',
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
const updatedUser = await userService.getUser(user.id);
|
|
expect(updatedUser.email).toBe(email);
|
|
});
|
|
|
|
test('should login and create user via SSO', async () => {
|
|
const email = 'some@test.com';
|
|
const user = await userService.loginUserSSO({
|
|
email,
|
|
rootRole: RoleName.VIEWER,
|
|
name: 'some',
|
|
autoCreate: true,
|
|
});
|
|
|
|
const userWithRole = await userService.getUser(user.id);
|
|
expect(user.email).toBe(email);
|
|
expect(user.name).toBe('some');
|
|
expect(userWithRole.name).toBe('some');
|
|
expect(userWithRole.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should throw if rootRole is wrong via SSO', async () => {
|
|
expect.assertions(1);
|
|
|
|
await expect(
|
|
userService.loginUserSSO({
|
|
email: 'some@test.com',
|
|
rootRole: RoleName.MEMBER,
|
|
name: 'some',
|
|
autoCreate: true,
|
|
}),
|
|
).rejects.toThrow(new BadDataError('Could not find rootRole=Member'));
|
|
});
|
|
|
|
test('should update user name when signing in via SSO', async () => {
|
|
const email = 'some@test.com';
|
|
const originalUser = await userService.createUser(
|
|
{
|
|
email,
|
|
rootRole: RoleName.VIEWER,
|
|
name: 'some',
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.loginUserSSO({
|
|
email,
|
|
rootRole: RoleName.ADMIN,
|
|
name: 'New name!',
|
|
autoCreate: true,
|
|
});
|
|
|
|
const actualUser = await userService.getUser(originalUser.id);
|
|
|
|
expect(actualUser.email).toBe(email);
|
|
expect(actualUser.name).toBe('New name!');
|
|
expect(actualUser.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should update name if it is different via SSO', async () => {
|
|
const email = 'some@test.com';
|
|
const originalUser = await userService.createUser(
|
|
{
|
|
email,
|
|
rootRole: RoleName.VIEWER,
|
|
name: 'some',
|
|
},
|
|
TEST_AUDIT_USER,
|
|
);
|
|
|
|
await userService.loginUserSSO({
|
|
email,
|
|
rootRole: RoleName.ADMIN,
|
|
name: 'New name!',
|
|
autoCreate: false,
|
|
});
|
|
|
|
const actualUser = await userService.getUser(originalUser.id);
|
|
|
|
expect(actualUser.email).toBe(email);
|
|
expect(actualUser.name).toBe('New name!');
|
|
expect(actualUser.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should throw if autoCreate is false via SSO', async () => {
|
|
expect.assertions(1);
|
|
|
|
await expect(
|
|
userService.loginUserSSO({
|
|
email: 'some@test.com',
|
|
rootRole: RoleName.MEMBER,
|
|
name: 'some',
|
|
autoCreate: false,
|
|
}),
|
|
).rejects.toThrow(new NotFoundError('No user found'));
|
|
});
|
|
|
|
test('should support a root role id when logging in and creating user via SSO', async () => {
|
|
const name = 'root-role-id';
|
|
const email = `${name}@test.com`;
|
|
const user = await userService.loginUserSSO({
|
|
email,
|
|
rootRole: viewerRole.id,
|
|
name,
|
|
autoCreate: true,
|
|
});
|
|
|
|
const userWithRole = await userService.getUser(user.id);
|
|
expect(user.email).toBe(email);
|
|
expect(user.name).toBe(name);
|
|
expect(userWithRole.name).toBe(name);
|
|
expect(userWithRole.rootRole).toBe(viewerRole.id);
|
|
});
|
|
|
|
test('should support a custom root role id when logging in and creating user via SSO', async () => {
|
|
const name = 'custom-root-role-id';
|
|
const email = `${name}@test.com`;
|
|
const user = await userService.loginUserSSO({
|
|
email,
|
|
rootRole: customRole.id,
|
|
name,
|
|
autoCreate: true,
|
|
});
|
|
|
|
const userWithRole = await userService.getUser(user.id);
|
|
expect(user.email).toBe(email);
|
|
expect(user.name).toBe(name);
|
|
expect(userWithRole.name).toBe(name);
|
|
expect(userWithRole.rootRole).toBe(customRole.id);
|
|
|
|
const permissions = await accessService.getPermissionsForUser(user);
|
|
expect(permissions).toHaveLength(1);
|
|
expect(permissions[0].permission).toBe(CREATE_ADDON);
|
|
});
|
|
|
|
describe('Should not be able to use any of previous 5 passwords', () => {
|
|
test('throws exception when trying to use a previously used password', async () => {
|
|
const name = 'same-password-is-not-allowed';
|
|
const email = `${name}@test.com`;
|
|
const password = 'externalScreaming$123';
|
|
const user = await userService.createUser({
|
|
email,
|
|
rootRole: customRole.id,
|
|
name,
|
|
password,
|
|
});
|
|
await expect(
|
|
userService.changePasswordWithPreviouslyUsedPasswordCheck(
|
|
user.id,
|
|
password,
|
|
),
|
|
).rejects.toThrow(new PasswordPreviouslyUsedError());
|
|
});
|
|
test('Is still able to change password to one not used', async () => {
|
|
const name = 'new-password-is-allowed';
|
|
const email = `${name}@test.com`;
|
|
const password = 'externalScreaming$123';
|
|
const user = await userService.createUser({
|
|
email,
|
|
rootRole: customRole.id,
|
|
name,
|
|
password,
|
|
});
|
|
await expect(
|
|
userService.changePasswordWithPreviouslyUsedPasswordCheck(
|
|
user.id,
|
|
'internalScreaming$123',
|
|
),
|
|
).resolves.not.toThrow();
|
|
});
|
|
test('Remembers 5 passwords', async () => {
|
|
const name = 'remembers-5-passwords-like-a-boss';
|
|
const email = `${name}@test.com`;
|
|
const password = 'externalScreaming$123';
|
|
const user = await userService.createUser({
|
|
email,
|
|
rootRole: customRole.id,
|
|
name,
|
|
password,
|
|
});
|
|
for (let i = 0; i < 5; i++) {
|
|
await userService.changePasswordWithPreviouslyUsedPasswordCheck(
|
|
user.id,
|
|
`${password}${i}`,
|
|
);
|
|
}
|
|
await expect(
|
|
userService.changePasswordWithPreviouslyUsedPasswordCheck(
|
|
user.id,
|
|
`${password}`,
|
|
),
|
|
).resolves.not.toThrow(); // We've added 5 new passwords, so the original should work again
|
|
});
|
|
test('Can bypass check by directly calling the changePassword method', async () => {
|
|
const name = 'can-bypass-check-like-a-boss';
|
|
const email = `${name}@test.com`;
|
|
const password = 'externalScreaming$123';
|
|
const user = await userService.createUser({
|
|
email,
|
|
rootRole: customRole.id,
|
|
name,
|
|
password,
|
|
});
|
|
await expect(
|
|
userService.changePassword(user.id, `${password}`),
|
|
).resolves.not.toThrow(); // By bypassing the check, we can still set the same password as currently set
|
|
});
|
|
});
|