diff --git a/src/lib/db/access-store.ts b/src/lib/db/access-store.ts index e4ed66c868..0494a03aba 100644 --- a/src/lib/db/access-store.ts +++ b/src/lib/db/access-store.ts @@ -12,7 +12,7 @@ import { IUserRole, IUserWithProjectRoles, } from '../types/stores/access-store'; -import { IPermission, IUserAccessOverview } from '../types/model'; +import { IPermission, IUserAccessOverview, RoleType } from '../types/model'; import NotFoundError from '../error/notfound-error'; import { ENVIRONMENT_PERMISSION_TYPE, @@ -360,6 +360,7 @@ export class AccessStore implements IAccessStore { .andWhere('ru.project', projectId); return rows.map((r) => ({ userId: r.user_id, + roleId, addedAt: r.created_at, })); } @@ -400,6 +401,16 @@ export class AccessStore implements IAccessStore { .where('ru.user_id', '=', userId); } + async getRootRoleForUser(userId: number): Promise { + return this.db + .select(['id', 'name', 'type', 'description']) + .from(T.ROLES) + .innerJoin(`${T.ROLE_USER} as ru`, 'ru.role_id', 'id') + .where('ru.user_id', '=', userId) + .andWhere('type', '=', RoleType.ROOT) + .first(); + } + async getUserIdsForRole(roleId: number): Promise { const rows = await this.db .select(['user_id']) diff --git a/src/lib/routes/admin-api/user/user.ts b/src/lib/routes/admin-api/user/user.ts index 94bb5170a5..a74c4fd8c1 100644 --- a/src/lib/routes/admin-api/user/user.ts +++ b/src/lib/routes/admin-api/user/user.ts @@ -170,8 +170,7 @@ class UserController extends Controller { const projects = await this.projectService.getProjectsByUser(user.id); - const roles = await this.accessService.getUserRootRoles(user.id); - const { project, ...rootRole } = roles[0]; + const rootRole = await this.accessService.getRootRoleForUser(user.id); const responseData: ProfileSchema = { projects, rootRole, diff --git a/src/lib/routes/public-invite.test.ts b/src/lib/routes/public-invite.test.ts index c76bf6aff4..cd511aaa8d 100644 --- a/src/lib/routes/public-invite.test.ts +++ b/src/lib/routes/public-invite.test.ts @@ -18,6 +18,13 @@ describe('Public Signup API', () => { ...stores.accessStore, addUserToRole: jest.fn(), removeRolesOfTypeForUser: jest.fn(), + getRolesForUserId: () => Promise.resolve([]), + getRootRoleForUser: () => + Promise.resolve({ + id: -1, + name: RoleName.VIEWER, + type: RoleType.ROOT, + }), }; const services = createServices(stores, config); diff --git a/src/lib/services/access-service.test.ts b/src/lib/services/access-service.test.ts index 149a3954b9..e9ef00443a 100644 --- a/src/lib/services/access-service.test.ts +++ b/src/lib/services/access-service.test.ts @@ -190,9 +190,8 @@ test('user with custom root role should get a user root role', async () => { }; await accessService.setUserRootRole(user.id, customRootRole.id); - const roles = await accessService.getUserRootRoles(user.id); - expect(roles).toHaveLength(1); - expect(roles[0].name).toBe('custom-root-role'); + const role = await accessService.getRootRoleForUser(user.id); + expect(role.name).toBe('custom-root-role'); const events = await eventStore.getEvents(); expect(events).toHaveLength(1); expect(events[0]).toEqual({ diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts index 642b7c1f77..0e2a0e17c0 100644 --- a/src/lib/services/access-service.ts +++ b/src/lib/services/access-service.ts @@ -8,7 +8,6 @@ import { IRole, IRoleDescriptor, IRoleWithPermissions, - IRoleWithProject, IUserPermission, IUserRole, IUserWithProjectRoles, @@ -362,9 +361,13 @@ export class AccessService { } } - async getUserRootRoles(userId: number): Promise { - const userRoles = await this.store.getRolesForUserId(userId); - return userRoles.filter(({ type }) => ROOT_ROLE_TYPES.includes(type)); + async getRootRoleForUser(userId: number): Promise { + const rootRole = await this.store.getRootRoleForUser(userId); + if (!rootRole) { + const defaultRole = await this.getPredefinedRole(RoleName.VIEWER); + return defaultRole; + } + return rootRole; } async removeUserFromRole( @@ -602,9 +605,20 @@ export class AccessService { return role; } - async getRootRole(roleName: RoleName): Promise { - const roles = await this.roleStore.getRootRoles(); - return roles.find((r) => r.name === roleName); + /* + This method is intended to give a predicable way to fetch + pre-defined roles defined in the RoleName enum. This method + should not be used to fetch custom root or project roles. + */ + async getPredefinedRole(roleName: RoleName): Promise { + const roles = await this.roleStore.getRoles(); + const role = roles.find((r) => r.name === roleName); + if (!role) { + throw new BadDataError( + `Could not find pre-defined role with name ${RoleName}`, + ); + } + return role; } async getAllRoles(): Promise { diff --git a/src/lib/services/account-service.ts b/src/lib/services/account-service.ts index 78aecaf48c..9f5a10f6cb 100644 --- a/src/lib/services/account-service.ts +++ b/src/lib/services/account-service.ts @@ -34,7 +34,7 @@ export class AccountService { async getAll(): Promise { const accounts = await this.store.getAll(); - const defaultRole = await this.accessService.getRootRole( + const defaultRole = await this.accessService.getPredefinedRole( RoleName.VIEWER, ); const userRoles = await this.accessService.getRootRoleForAllUsers(); diff --git a/src/lib/services/user-service.ts b/src/lib/services/user-service.ts index 4fc55480c3..54a6187dc0 100644 --- a/src/lib/services/user-service.ts +++ b/src/lib/services/user-service.ts @@ -4,7 +4,7 @@ import Joi from 'joi'; import { URL } from 'url'; import { Logger } from '../logger'; -import User, { IUser } from '../types/user'; +import User, { IUser, IUserWithRootRole } from '../types/user'; import isEmail from '../util/is-email'; import { AccessService } from './access-service'; import ResetTokenService from './reset-token-service'; @@ -16,7 +16,14 @@ import { IAuthOption, IUnleashConfig } from '../types/option'; import SessionService from './session-service'; import { IUnleashStores } from '../types/stores'; import PasswordUndefinedError from '../error/password-undefined'; -import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events'; +import { + USER_UPDATED, + USER_CREATED, + USER_DELETED, + UserCreatedEvent, + UserUpdatedEvent, + UserDeletedEvent, +} from '../types/events'; import { IUserStore } from '../types/stores/user-store'; import { RoleName } from '../types/model'; import SettingService from './setting-service'; @@ -53,10 +60,6 @@ export interface ILoginUserRequest { autoCreate?: boolean; } -interface IUserWithRole extends IUser { - rootRole: number; -} - const saltRounds = 10; class UserService { @@ -173,9 +176,9 @@ class UserService { } } - async getAll(): Promise { + async getAll(): Promise { const users = await this.store.getAll(); - const defaultRole = await this.accessService.getRootRole( + const defaultRole = await this.accessService.getPredefinedRole( RoleName.VIEWER, ); const userRoles = await this.accessService.getRootRoleForAllUsers(); @@ -187,14 +190,10 @@ class UserService { return usersWithRootRole; } - async getUser(id: number): Promise { - const roles = await this.accessService.getUserRootRoles(id); - const defaultRole = await this.accessService.getRootRole( - RoleName.VIEWER, - ); - const roleId = roles.length > 0 ? roles[0].id : defaultRole.id; + async getUser(id: number): Promise { const user = await this.store.get(id); - return { ...user, rootRole: roleId }; + const rootRole = await this.accessService.getRootRoleForUser(id); + return { ...user, rootRole: rootRole.id }; } async search(query: string): Promise { @@ -208,7 +207,7 @@ class UserService { async createUser( { username, email, name, password, rootRole }: ICreateUser, updatedBy?: IUser, - ): Promise { + ): Promise { if (!username && !email) { throw new BadDataError('You must specify username or email'); } @@ -235,36 +234,27 @@ class UserService { await this.store.setPasswordHash(user.id, passwordHash); } - await this.eventService.storeEvent({ - type: USER_CREATED, - createdBy: this.getCreatedBy(updatedBy), - data: this.mapUserToData(user), - }); + const userCreated = await this.getUser(user.id); - return user; + await this.eventService.storeEvent( + new UserCreatedEvent({ + createdBy: this.getCreatedBy(updatedBy), + userCreated, + }), + ); + + return userCreated; } private getCreatedBy(updatedBy: IUser = systemUser) { return updatedBy.username || updatedBy.email; } - private mapUserToData(user?: IUser): any { - if (!user) { - return undefined; - } - return { - id: user.id, - name: user.name, - username: user.username, - email: user.email, - }; - } - async updateUser( { id, name, email, rootRole }: IUpdateUser, updatedBy?: IUser, - ): Promise { - const preUser = await this.store.get(id); + ): Promise { + const preUser = await this.getUser(id); if (email) { Joi.assert(email, Joi.string().email(), 'Email'); @@ -284,28 +274,32 @@ class UserService { ? await this.store.update(id, payload) : preUser; - await this.eventService.storeEvent({ - type: USER_UPDATED, - createdBy: this.getCreatedBy(updatedBy), - data: this.mapUserToData(user), - preData: this.mapUserToData(preUser), - }); + const storedUser = await this.getUser(user.id); - return user; + await this.eventService.storeEvent( + new UserUpdatedEvent({ + createdBy: this.getCreatedBy(updatedBy), + preUser: preUser, + postUser: storedUser, + }), + ); + + return storedUser; } async deleteUser(userId: number, updatedBy?: IUser): Promise { - const user = await this.store.get(userId); + const user = await this.getUser(userId); await this.accessService.wipeUserPermissions(userId); await this.sessionService.deleteSessionsForUser(userId); await this.store.delete(userId); - await this.eventService.storeEvent({ - type: USER_DELETED, - createdBy: this.getCreatedBy(updatedBy), - preData: this.mapUserToData(user), - }); + await this.eventService.storeEvent( + new UserDeletedEvent({ + createdBy: this.getCreatedBy(updatedBy), + deletedUser: user, + }), + ); } async loginUser(usernameOrEmail: string, password: string): Promise { @@ -479,5 +473,4 @@ class UserService { } } -module.exports = UserService; export default UserService; diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index 7d89bfba34..b4a205f8bd 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -1,7 +1,7 @@ import { extractUsernameFromUser } from '../util'; import { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model'; import { IApiToken } from './models/api-token'; -import { IUser } from './user'; +import { IUser, IUserWithRootRole } from './user'; export const APPLICATION_CREATED = 'application-created' as const; @@ -1096,3 +1096,52 @@ export class PotentiallyStaleOnEvent extends BaseEvent { this.project = eventData.project; } } + +export class UserCreatedEvent extends BaseEvent { + readonly data: IUserWithRootRole; + + constructor(eventData: { + createdBy: string | IUser; + userCreated: IUserWithRootRole; + }) { + super(USER_CREATED, eventData.createdBy); + this.data = mapUserToData(eventData.userCreated); + } +} + +export class UserUpdatedEvent extends BaseEvent { + readonly data: IUserWithRootRole; + readonly preData: IUserWithRootRole; + + constructor(eventData: { + createdBy: string | IUser; + preUser: IUserWithRootRole; + postUser: IUserWithRootRole; + }) { + super(USER_UPDATED, eventData.createdBy); + this.preData = mapUserToData(eventData.preUser); + this.data = mapUserToData(eventData.postUser); + } +} + +export class UserDeletedEvent extends BaseEvent { + readonly preData: IUserWithRootRole; + + constructor(eventData: { + createdBy: string | IUser; + deletedUser: IUserWithRootRole; + }) { + super(USER_DELETED, eventData.createdBy); + this.preData = mapUserToData(eventData.deletedUser); + } +} + +function mapUserToData(user: IUserWithRootRole): any { + return { + id: user.id, + name: user.name, + username: user.username, + email: user.email, + rootRole: user.rootRole, + }; +} diff --git a/src/lib/types/stores/access-store.ts b/src/lib/types/stores/access-store.ts index e5c4b39b0f..f0a830ae8d 100644 --- a/src/lib/types/stores/access-store.ts +++ b/src/lib/types/stores/access-store.ts @@ -49,7 +49,7 @@ export interface IAccessInfo { } export interface IUserRole { - roleId?: number; + roleId: number; userId: number; addedAt?: Date; } @@ -188,6 +188,7 @@ export interface IAccessStore extends Store { projectId: string, userId: number, ): Promise; + getRootRoleForUser(userId: number): Promise; setProjectRolesForGroup( projectId: string, groupId: number, diff --git a/src/lib/types/user.ts b/src/lib/types/user.ts index 5697a41cb0..32ff121019 100644 --- a/src/lib/types/user.ts +++ b/src/lib/types/user.ts @@ -92,4 +92,8 @@ export default class User implements IUser { } } +export interface IUserWithRootRole extends IUser { + rootRole: number; +} + module.exports = User; diff --git a/src/test/e2e/api/admin/api-token.auth.e2e.test.ts b/src/test/e2e/api/admin/api-token.auth.e2e.test.ts index b772366afe..0c37d9b32b 100644 --- a/src/test/e2e/api/admin/api-token.auth.e2e.test.ts +++ b/src/test/e2e/api/admin/api-token.auth.e2e.test.ts @@ -37,7 +37,7 @@ test('editor users should only get client or frontend tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.EDITOR); + const role = await accessService.getPredefinedRole(RoleName.EDITOR); const user = await userService.createUser({ email: 'editor@example.com', rootRole: role.id, @@ -85,7 +85,7 @@ test('viewer users should not be allowed to fetch tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.VIEWER); + const role = await accessService.getPredefinedRole(RoleName.VIEWER); const user = await userService.createUser({ email: 'viewer@example.com', rootRole: role.id, @@ -122,7 +122,7 @@ test('Only token-admins should be allowed to create token', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.EDITOR); + const role = await accessService.getPredefinedRole(RoleName.EDITOR); req.user = await userService.createUser({ email: 'editor2@example.com', rootRole: role.id, @@ -150,7 +150,7 @@ test('Token-admin should be allowed to create token', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.ADMIN); + const role = await accessService.getPredefinedRole(RoleName.ADMIN); req.user = await userService.createUser({ email: 'admin@example.com', rootRole: role.id, @@ -185,7 +185,9 @@ test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', asyn }: { userService: UserService; accessService: AccessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = (await accessService.getRootRole(RoleName.VIEWER))!; + const role = (await accessService.getPredefinedRole( + RoleName.VIEWER, + ))!; const user = await userService.createUser({ email: 'powerpuffgirls_viewer@example.com', rootRole: role.id, @@ -230,7 +232,7 @@ describe('Fine grained API token permissions', () => { test('should be allowed to create client tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const builtInRole = await accessService.getRootRole( + const builtInRole = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -275,7 +277,7 @@ describe('Fine grained API token permissions', () => { test('should NOT be allowed to create frontend tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -319,7 +321,7 @@ describe('Fine grained API token permissions', () => { test('should NOT be allowed to create ADMIN tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -365,7 +367,7 @@ describe('Fine grained API token permissions', () => { test('READ_FRONTEND_API_TOKEN should be able to see FRONTEND tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -429,7 +431,7 @@ describe('Fine grained API token permissions', () => { test('READ_CLIENT_API_TOKEN should be able to see CLIENT tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -488,7 +490,7 @@ describe('Fine grained API token permissions', () => { test('Admin users should be able to see all tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.ADMIN, ); const user = await userService.createUser({ @@ -531,7 +533,7 @@ describe('Fine grained API token permissions', () => { test('Editor users should be able to see all tokens except ADMIN tokens', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.EDITOR, ); const user = await userService.createUser({ @@ -585,7 +587,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -634,7 +636,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -684,7 +686,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -737,7 +739,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -786,7 +788,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ @@ -835,7 +837,7 @@ describe('Fine grained API token permissions', () => { { userService, accessService }, ) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole( + const role = await accessService.getPredefinedRole( RoleName.VIEWER, ); const user = await userService.createUser({ diff --git a/src/test/e2e/api/admin/public-signup-token.e2e.test.ts b/src/test/e2e/api/admin/public-signup-token.e2e.test.ts index 45d5b93406..7af09f0e21 100644 --- a/src/test/e2e/api/admin/public-signup-token.e2e.test.ts +++ b/src/test/e2e/api/admin/public-signup-token.e2e.test.ts @@ -35,7 +35,7 @@ test('admin users should be able to create a token', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.ADMIN); + const role = await accessService.getPredefinedRole(RoleName.ADMIN); const user = await userService.createUser({ email: 'admin@example.com', rootRole: role.id, @@ -69,7 +69,7 @@ test('admin users should be able to create a token', async () => { test('no permission to validate a token', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const admin = await accessService.getRootRole(RoleName.ADMIN); + const admin = await accessService.getPredefinedRole(RoleName.ADMIN); await userService.createUser({ email: 'admin@example.com', username: 'admin@example.com', @@ -97,7 +97,7 @@ test('no permission to validate a token', async () => { test('should return 400 if token can not be validate', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const admin = await accessService.getRootRole(RoleName.ADMIN); + const admin = await accessService.getPredefinedRole(RoleName.ADMIN); await userService.createUser({ email: 'admin@example.com', username: 'admin@example.com', @@ -119,7 +119,7 @@ test('users can signup with invite-link', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const admin = await accessService.getRootRole(RoleName.ADMIN); + const admin = await accessService.getPredefinedRole(RoleName.ADMIN); await userService.createUser({ email: 'admin@example.com', username: 'admin@example.com', @@ -164,7 +164,7 @@ test('can get a token with users', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.ADMIN); + const role = await accessService.getPredefinedRole(RoleName.ADMIN); const user = await userService.createUser({ email: 'admin@example.com', rootRole: role.id, @@ -209,7 +209,7 @@ test('can get a token with users', async () => { test('should not be able to set expiry further than 1 month', async () => { const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.ADMIN); + const role = await accessService.getPredefinedRole(RoleName.ADMIN); const user = await userService.createUser({ email: 'admin@example.com', rootRole: role.id, diff --git a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts index f1600874c5..8233ff0bae 100644 --- a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts +++ b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts @@ -80,13 +80,13 @@ beforeAll(async () => { settingService, }); resetTokenService = new ResetTokenService(stores, config); - const adminRole = (await accessService.getRootRole(RoleName.ADMIN))!; + const adminRole = (await accessService.getPredefinedRole(RoleName.ADMIN))!; adminUser = await userService.createUser({ username: 'admin@test.com', rootRole: adminRole.id, })!; - const userRole = (await accessService.getRootRole(RoleName.EDITOR))!; + const userRole = (await accessService.getPredefinedRole(RoleName.EDITOR))!; user = await userService.createUser({ username: 'test@test.com', email: 'test@test.com', diff --git a/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts b/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts index 6ca0a4e9b6..a7a2bd241a 100644 --- a/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts +++ b/src/test/e2e/api/auth/simple-password-provider.e2e.test.ts @@ -56,7 +56,7 @@ beforeAll(async () => { sessionService, settingService, }); - const adminRole = await accessService.getRootRole(RoleName.ADMIN); + const adminRole = await accessService.getPredefinedRole(RoleName.ADMIN); adminUser = await userService.createUser({ username: 'admin@test.com', email: 'admin@test.com', diff --git a/src/test/e2e/custom-auth.test.ts b/src/test/e2e/custom-auth.test.ts index 9676b4d96a..05223104ab 100644 --- a/src/test/e2e/custom-auth.test.ts +++ b/src/test/e2e/custom-auth.test.ts @@ -7,7 +7,7 @@ let stores; const preHook = (app, config, { userService, accessService }) => { app.use('/api/admin/', async (req, res, next) => { - const role = await accessService.getRootRole(RoleName.EDITOR); + const role = await accessService.getPredefinedRole(RoleName.EDITOR); req.user = await userService.createUser({ email: 'editor2@example.com', rootRole: role.id, diff --git a/src/test/e2e/services/user-service.e2e.test.ts b/src/test/e2e/services/user-service.e2e.test.ts index 508f627172..b699934333 100644 --- a/src/test/e2e/services/user-service.e2e.test.ts +++ b/src/test/e2e/services/user-service.e2e.test.ts @@ -18,6 +18,7 @@ import { randomId } from '../../../lib/util/random-id'; import { BadDataError } from '../../../lib/error'; import PasswordMismatch from '../../../lib/error/password-mismatch'; import { EventService } from '../../../lib/services'; +import { USER_CREATED, USER_DELETED, USER_UPDATED } from '../../../lib/types'; let db; let stores; @@ -27,12 +28,13 @@ let adminRole: IRole; let viewerRole: IRole; let sessionService: SessionService; let settingService: SettingService; +let eventService: EventService; beforeAll(async () => { db = await dbInit('user_service_serial', getLogger); stores = db.stores; const config = createTestConfig(); - const eventService = new EventService(stores, config); + eventService = new EventService(stores, config); const groupService = new GroupService(stores, config, eventService); const accessService = new AccessService( stores, @@ -124,6 +126,49 @@ test('should create user with password', async () => { 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, + }); + + 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, + }); + + await userService.updateUser({ id: user.id, rootRole: adminRole.id }); + + 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, + }); + + await userService.deleteUser(user.id); + + 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', diff --git a/src/test/fixtures/access-service-mock.ts b/src/test/fixtures/access-service-mock.ts index ec3b27252d..5d7f1100f2 100644 --- a/src/test/fixtures/access-service-mock.ts +++ b/src/test/fixtures/access-service-mock.ts @@ -7,7 +7,7 @@ import { import User from '../../lib/types/user'; import noLoggerProvider from './no-logger'; import { IRole } from '../../lib/types/stores/access-store'; -import { IAvailablePermissions } from '../../lib/types/model'; +import { IAvailablePermissions, RoleName } from '../../lib/types/model'; class AccessServiceMock extends AccessService { constructor() { @@ -69,7 +69,11 @@ class AccessServiceMock extends AccessService { } getRolesForUser(userId: number): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve([{ id: 1, name: 'Admin', type: 'root' }]); + } + + getUserRootRoles(userId: number): Promise { + return Promise.resolve([{ id: 1, name: 'Admin', type: 'root' }]); } getUsersForRole(roleId: any): Promise { @@ -87,6 +91,14 @@ class AccessServiceMock extends AccessService { removeDefaultProjectRoles(owner: User, projectId: string): Promise { throw new Error('Method not implemented.'); } + + getRootRole(roleName: RoleName): Promise { + return Promise.resolve({ id: 1, name: roleName, type: 'root' }); + } + + getRootRoleForUser(userId: number): Promise { + return Promise.resolve({ id: 1, name: RoleName.VIEWER, type: 'root' }); + } } export default AccessServiceMock; diff --git a/src/test/fixtures/fake-access-store.ts b/src/test/fixtures/fake-access-store.ts index aca538d3ed..1043288ce6 100644 --- a/src/test/fixtures/fake-access-store.ts +++ b/src/test/fixtures/fake-access-store.ts @@ -9,8 +9,13 @@ import { IUserRole, IUserWithProjectRoles, } from '../../lib/types/stores/access-store'; -import { IPermission } from 'lib/types/model'; -import { IRoleStore, IUserAccessOverview } from 'lib/types'; +import { IPermission } from '../../lib/types/model'; +import { + IRoleStore, + IUserAccessOverview, + RoleName, + RoleType, +} from '../../lib/types'; import FakeRoleStore from './fake-role-store'; import { PermissionRef } from 'lib/services/access-service'; import { P } from 'ts-toolbelt/out/Object/_api'; @@ -302,6 +307,18 @@ class AccessStoreMock implements IAccessStore { getUserAccessOverview(): Promise { throw new Error('Method not implemented.'); } + getRootRoleForUser(userId: number): Promise { + const roleId = this.userToRoleMap.get(userId); + if (roleId !== undefined) { + return Promise.resolve(this.fakeRolesStore.get(roleId)); + } else { + return Promise.resolve({ + id: -1, + name: RoleName.VIEWER, + type: RoleType.ROOT, + }); + } + } } module.exports = AccessStoreMock;