mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
chore(core): Use roles from database in global roles (#18768)
This commit is contained in:
@@ -2,7 +2,7 @@ import type { ApiKeyWithRawValue } from '@n8n/api-types';
|
||||
import { testDb, randomValidPassword, mockInstance } from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import type { User } from '@n8n/db';
|
||||
import { ApiKeyRepository } from '@n8n/db';
|
||||
import { ApiKeyRepository, GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import {
|
||||
getApiKeyScopesForRole,
|
||||
@@ -59,7 +59,7 @@ describe('Owner shell', () => {
|
||||
let ownerShell: User;
|
||||
|
||||
beforeEach(async () => {
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
});
|
||||
|
||||
test('POST /api-keys should create an api key with no expiration', async () => {
|
||||
@@ -304,9 +304,9 @@ describe('Owner shell', () => {
|
||||
|
||||
const scopes = apiKeyScopesResponse.body.data as ApiKeyScope[];
|
||||
|
||||
const scopesForRole = getApiKeyScopesForRole(ownerShell.role);
|
||||
const scopesForRole = getApiKeyScopesForRole(ownerShell);
|
||||
|
||||
expect(scopes).toEqual(scopesForRole);
|
||||
expect(scopes.sort()).toEqual(scopesForRole.sort());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,7 +317,7 @@ describe('Member', () => {
|
||||
beforeEach(async () => {
|
||||
member = await createUser({
|
||||
password: memberPassword,
|
||||
role: 'global:member',
|
||||
role: GLOBAL_MEMBER_ROLE,
|
||||
});
|
||||
await utils.setInstanceOwnerSetUp(true);
|
||||
});
|
||||
@@ -328,6 +328,7 @@ describe('Member', () => {
|
||||
.post('/api-keys')
|
||||
.send({ label: 'My API Key', expiresAt: null, scopes: ['workflow:create'] });
|
||||
|
||||
console.log(newApiKeyResponse.body);
|
||||
expect(newApiKeyResponse.statusCode).toBe(200);
|
||||
expect(newApiKeyResponse.body.data.apiKey).toBeDefined();
|
||||
expect(newApiKeyResponse.body.data.apiKey).not.toBeNull();
|
||||
@@ -492,8 +493,8 @@ describe('Member', () => {
|
||||
|
||||
const scopes = apiKeyScopesResponse.body.data as ApiKeyScope[];
|
||||
|
||||
const scopesForRole = getApiKeyScopesForRole(member.role);
|
||||
const scopesForRole = getApiKeyScopesForRole(member);
|
||||
|
||||
expect(scopes).toEqual(scopesForRole);
|
||||
expect(scopes.sort()).toEqual(scopesForRole.sort());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomValidPassword, testDb } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, UserRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import validator from 'validator';
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('POST /login', () => {
|
||||
beforeEach(async () => {
|
||||
owner = await createUser({
|
||||
password: ownerPassword,
|
||||
role: 'global:owner',
|
||||
role: GLOBAL_OWNER_ROLE,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('POST /login', () => {
|
||||
license.setQuota('quota:users', 0);
|
||||
const ownerUser = await createUser({
|
||||
password: randomValidPassword(),
|
||||
role: 'global:owner',
|
||||
role: GLOBAL_OWNER_ROLE,
|
||||
});
|
||||
|
||||
const response = await testServer.authAgentFor(ownerUser).get('/login');
|
||||
@@ -182,7 +182,7 @@ describe('GET /login', () => {
|
||||
});
|
||||
|
||||
test('should return logged-in owner shell', async () => {
|
||||
const ownerShell = await createUserShell('global:owner');
|
||||
const ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
|
||||
const response = await testServer.authAgentFor(ownerShell).get('/login');
|
||||
|
||||
@@ -217,7 +217,7 @@ describe('GET /login', () => {
|
||||
});
|
||||
|
||||
test('should return logged-in member shell', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
const response = await testServer.authAgentFor(memberShell).get('/login');
|
||||
|
||||
@@ -252,7 +252,7 @@ describe('GET /login', () => {
|
||||
});
|
||||
|
||||
test('should return logged-in owner', async () => {
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
|
||||
const response = await testServer.authAgentFor(owner).get('/login');
|
||||
|
||||
@@ -287,7 +287,7 @@ describe('GET /login', () => {
|
||||
});
|
||||
|
||||
test('should return logged-in member', async () => {
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
const member = await createUser({ role: { slug: 'global:member' } });
|
||||
|
||||
const response = await testServer.authAgentFor(member).get('/login');
|
||||
|
||||
@@ -326,13 +326,13 @@ describe('GET /resolve-signup-token', () => {
|
||||
beforeEach(async () => {
|
||||
owner = await createUser({
|
||||
password: ownerPassword,
|
||||
role: 'global:owner',
|
||||
role: GLOBAL_OWNER_ROLE,
|
||||
});
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
});
|
||||
|
||||
test('should validate invite token', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
@@ -352,7 +352,7 @@ describe('GET /resolve-signup-token', () => {
|
||||
|
||||
test('should return 403 if user quota reached', async () => {
|
||||
license.setQuota('quota:users', 0);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
@@ -363,7 +363,7 @@ describe('GET /resolve-signup-token', () => {
|
||||
});
|
||||
|
||||
test('should fail with invalid inputs', async () => {
|
||||
const { id: inviteeId } = await createUser({ role: 'global:member' });
|
||||
const { id: inviteeId } = await createUser({ role: { slug: 'global:member' } });
|
||||
|
||||
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
|
||||
|
||||
@@ -396,7 +396,7 @@ describe('GET /resolve-signup-token', () => {
|
||||
|
||||
describe('POST /logout', () => {
|
||||
test('should log user out', async () => {
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
const ownerAgent = testServer.authAgentFor(owner);
|
||||
// @ts-expect-error `accessInfo` types are incorrect
|
||||
const cookie = ownerAgent.jar.getCookie(AUTH_COOKIE_NAME, { path: '/' });
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('Auth Middleware', () => {
|
||||
describe('Routes requiring Authorization', () => {
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
beforeAll(async () => {
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
const member = await createUser({ role: { slug: 'global:member' } });
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('--deleteWorkflowsAndCredentials', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
const memberProject = await getPersonalProject(member);
|
||||
const workflow = await createWorkflow({}, member);
|
||||
const credential = await saveCredential(randomCredentialPayload(), {
|
||||
@@ -166,7 +166,7 @@ describe('--userId', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
|
||||
await expect(command.run([`--userId=${member.id}`])).rejects.toThrowError(
|
||||
`Can't migrate workflows and credentials to the user with the ID ${member.id}. That user was created via LDAP and will be deleted as well.`,
|
||||
@@ -177,7 +177,7 @@ describe('--userId', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
const memberProject = await getPersonalProject(member);
|
||||
const workflow = await createWorkflow({}, member);
|
||||
const credential = await saveCredential(randomCredentialPayload(), {
|
||||
@@ -242,7 +242,7 @@ describe('--projectId', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
const memberProject = await getPersonalProject(member);
|
||||
|
||||
await expect(command.run([`--projectId=${memberProject.id}`])).rejects.toThrowError(
|
||||
@@ -254,7 +254,7 @@ describe('--projectId', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
const memberProject = await getPersonalProject(member);
|
||||
const workflow = await createWorkflow({}, member);
|
||||
const credential = await saveCredential(randomCredentialPayload(), {
|
||||
@@ -310,7 +310,7 @@ describe('--projectId', () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const member = await createLdapUser({ role: 'global:member' }, uuid());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uuid());
|
||||
const memberProject = await getPersonalProject(member);
|
||||
const workflow = await createWorkflow({}, member);
|
||||
const credential = await saveCredential(randomCredentialPayload(), {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SharedCredentialsRepository,
|
||||
SharedWorkflowRepository,
|
||||
UserRepository,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
@@ -35,7 +36,7 @@ test('user-management:reset should reset DB to default user state', async () =>
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
const ownerProject = await getPersonalProject(owner);
|
||||
|
||||
// should be deleted
|
||||
@@ -70,7 +71,7 @@ test('user-management:reset should reset DB to default user state', async () =>
|
||||
|
||||
// check if the owner account was reset:
|
||||
await expect(
|
||||
Container.get(UserRepository).findOneBy({ role: 'global:owner' }),
|
||||
Container.get(UserRepository).findOneBy({ role: { slug: GLOBAL_OWNER_ROLE.slug } }),
|
||||
).resolves.toMatchObject({
|
||||
email: null,
|
||||
firstName: null,
|
||||
@@ -80,7 +81,9 @@ test('user-management:reset should reset DB to default user state', async () =>
|
||||
});
|
||||
|
||||
// all members were deleted:
|
||||
const members = await Container.get(UserRepository).findOneBy({ role: 'global:member' });
|
||||
const members = await Container.get(UserRepository).findOneBy({
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
expect(members).toBeNull();
|
||||
|
||||
// all workflows are owned by the owner:
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
randomValidPassword,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { ProjectRelationRepository, UserRepository } from '@n8n/db';
|
||||
import {
|
||||
GLOBAL_ADMIN_ROLE,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
ProjectRelationRepository,
|
||||
UserRepository,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { Not } from '@n8n/typeorm';
|
||||
|
||||
@@ -52,7 +57,7 @@ describe('InvitationController', () => {
|
||||
|
||||
describe('POST /invitations/:id/accept', () => {
|
||||
test('should fill out a member shell', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
const memberProps = {
|
||||
inviterId: instanceOwner.id,
|
||||
@@ -83,7 +88,7 @@ describe('InvitationController', () => {
|
||||
});
|
||||
|
||||
test('should fill out an admin shell', async () => {
|
||||
const adminShell = await createUserShell('global:admin');
|
||||
const adminShell = await createUserShell(GLOBAL_ADMIN_ROLE);
|
||||
|
||||
const memberProps = {
|
||||
inviterId: instanceOwner.id,
|
||||
@@ -116,7 +121,7 @@ describe('InvitationController', () => {
|
||||
test('should fail with invalid payloads', async () => {
|
||||
const memberShell = await userRepository.save({
|
||||
email: randomEmail(),
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const invalidPaylods = [
|
||||
@@ -374,7 +379,7 @@ describe('InvitationController', () => {
|
||||
mailer.invite.mockResolvedValue({ emailSent: true });
|
||||
|
||||
const member = await createMember();
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const newUserEmail = randomEmail();
|
||||
|
||||
const existingUserEmails = [member.email];
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
mockInstance,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import type { Project, User, ListQueryDb } from '@n8n/db';
|
||||
import { ProjectRepository, SharedCredentialsRepository } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, ProjectRepository, SharedCredentialsRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { ProjectRole } from '@n8n/permissions';
|
||||
import { In } from '@n8n/typeorm';
|
||||
@@ -68,10 +68,10 @@ beforeEach(async () => {
|
||||
admin = await createAdmin();
|
||||
ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
|
||||
|
||||
member = await createUser({ role: 'global:member' });
|
||||
member = await createUser({ role: { slug: 'global:member' } });
|
||||
memberPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(member.id);
|
||||
|
||||
anotherMember = await createUser({ role: 'global:member' });
|
||||
anotherMember = await createUser({ role: { slug: 'global:member' } });
|
||||
anotherMemberPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
anotherMember.id,
|
||||
);
|
||||
@@ -110,7 +110,7 @@ describe('POST /credentials', () => {
|
||||
describe('GET /credentials', () => {
|
||||
test('should return all creds for owner', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
const member1PersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
member1.id,
|
||||
@@ -183,7 +183,7 @@ describe('GET /credentials', () => {
|
||||
|
||||
test('should return only relevant creds for member', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
const member1PersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
member1.id,
|
||||
@@ -579,7 +579,7 @@ describe('GET /credentials/:id', () => {
|
||||
|
||||
test('should retrieve non-owned cred for owner', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
const member1PersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
member1.id,
|
||||
@@ -626,7 +626,7 @@ describe('GET /credentials/:id', () => {
|
||||
|
||||
test('should retrieve owned cred for member', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
const member1PersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
member1.id,
|
||||
@@ -745,7 +745,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const [member1, member2, member3, member4, member5] = await createManyUsers(5, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
// TODO: write helper for getting multiple personal projects by user id
|
||||
const shareWithProjectIds = (
|
||||
@@ -793,7 +793,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
|
||||
test('should share the credential with the provided userIds', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
const projectIds = (
|
||||
await Promise.all([
|
||||
@@ -876,7 +876,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
|
||||
test('should respond 403 for non-owned credentials for non-shared members sharing', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||
const tempUser = await createUser({ role: 'global:member' });
|
||||
const tempUser = await createUser({ role: { slug: 'global:member' } });
|
||||
const tempUserPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
tempUser.id,
|
||||
);
|
||||
@@ -910,7 +910,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
});
|
||||
|
||||
test('should not ignore pending sharee', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const memberShellPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
memberShell.id,
|
||||
);
|
||||
@@ -1019,7 +1019,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
await shareCredentialWithUsers(savedCredential, [member1, member2]);
|
||||
@@ -1045,7 +1045,7 @@ describe('PUT /credentials/:id/share', () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
await shareCredentialWithUsers(savedCredential, [member1, member2]);
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('GET /credentials', () => {
|
||||
|
||||
test('should return only own creds for member', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const [savedCredential1] = await Promise.all([
|
||||
@@ -125,7 +125,7 @@ describe('GET /credentials', () => {
|
||||
|
||||
test('should return scopes when ?includeScopes=true', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const teamProject = await createTeamProject(undefined, member1);
|
||||
@@ -239,7 +239,7 @@ describe('GET /credentials', () => {
|
||||
test('should return data when ?includeData=true', async () => {
|
||||
// ARRANGE
|
||||
const [actor, otherMember] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const teamProjectViewer = await createTeamProject(undefined);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SourceControlledFile } from '@n8n/api-types';
|
||||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { SourceControlPreferencesService } from '@/environments.ee/source-control/source-control-preferences.service.ee';
|
||||
@@ -24,7 +24,7 @@ const testServer = utils.setupTestServer({
|
||||
let sourceControlPreferencesService: SourceControlPreferencesService;
|
||||
|
||||
beforeAll(async () => {
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
sourceControlPreferencesService = Container.get(SourceControlPreferencesService);
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
CredentialsEntity,
|
||||
type Folder,
|
||||
FolderRepository,
|
||||
GLOBAL_ADMIN_ROLE,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
Project,
|
||||
type TagEntity,
|
||||
TagRepository,
|
||||
@@ -217,10 +220,10 @@ describe('SourceControlService', () => {
|
||||
*/
|
||||
|
||||
[globalAdmin, globalOwner, globalMember, projectAdmin] = await Promise.all([
|
||||
await createUser({ role: 'global:admin' }),
|
||||
await createUser({ role: 'global:owner' }),
|
||||
await createUser({ role: 'global:member' }),
|
||||
await createUser({ role: 'global:member' }),
|
||||
createUser({ role: GLOBAL_ADMIN_ROLE }),
|
||||
createUser({ role: GLOBAL_OWNER_ROLE }),
|
||||
createUser({ role: GLOBAL_MEMBER_ROLE }),
|
||||
createUser({ role: GLOBAL_MEMBER_ROLE }),
|
||||
]);
|
||||
|
||||
[projectA, projectB] = await Promise.all([
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { createWorkflow, testDb } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { ProjectRepository, TestRunRepository } from '@n8n/db';
|
||||
import {
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
ProjectRepository,
|
||||
TestRunRepository,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mockInstance } from 'n8n-core/test/utils';
|
||||
import type { IWorkflowBase } from 'n8n-workflow';
|
||||
@@ -25,7 +30,7 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
authOwnerAgent = testServer.authAgentFor(ownerShell);
|
||||
});
|
||||
|
||||
@@ -113,7 +118,7 @@ describe('GET /workflows/:workflowId/test-runs', () => {
|
||||
});
|
||||
|
||||
test('should retrieve list of test runs for a shared workflow', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const memberAgent = testServer.authAgentFor(memberShell);
|
||||
const memberPersonalProject = await Container.get(
|
||||
ProjectRepository,
|
||||
@@ -171,7 +176,7 @@ describe('GET /workflows/:workflowId/test-runs/:id', () => {
|
||||
});
|
||||
|
||||
test('should retrieve test run of a shared workflow', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const memberAgent = testServer.authAgentFor(memberShell);
|
||||
const memberPersonalProject = await Container.get(
|
||||
ProjectRepository,
|
||||
@@ -345,7 +350,7 @@ describe('GET /workflows/:workflowId/test-runs/:id/test-cases', () => {
|
||||
});
|
||||
|
||||
test('should return test cases for a shared workflow', async () => {
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const memberAgent = testServer.authAgentFor(memberShell);
|
||||
const memberPersonalProject = await Container.get(
|
||||
ProjectRepository,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import axios from 'axios';
|
||||
import type {
|
||||
@@ -89,7 +89,7 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
|
||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||
import { ExecutionRecoveryService } from '@/executions/execution-recovery.service';
|
||||
@@ -25,7 +25,7 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
});
|
||||
|
||||
|
||||
@@ -1793,7 +1793,7 @@ describe('PUT /projects/:projectId/folders/:folderId/transfer', () => {
|
||||
test('should not transfer folder if license does not allow it', async () => {
|
||||
testServer.license.disable('feat:folders');
|
||||
|
||||
const admin = await createUser({ role: 'global:admin' });
|
||||
const admin = await createUser({ role: { slug: 'global:admin' } });
|
||||
const sourceProject = await createTeamProject('source project', admin);
|
||||
const destinationProject = await createTeamProject('destination project', member);
|
||||
const sourceFolder1 = await createFolder(sourceProject, { name: 'Source Folder 1' });
|
||||
@@ -1996,7 +1996,7 @@ describe('PUT /projects/:projectId/folders/:folderId/transfer', () => {
|
||||
|
||||
test('owner transfers folder from project they are not part of, e.g. test global cred sharing scope', async () => {
|
||||
// ARRANGE
|
||||
const admin = await createUser({ role: 'global:admin' });
|
||||
const admin = await createUser({ role: { slug: 'global:admin' } });
|
||||
const sourceProject = await createTeamProject('source project', admin);
|
||||
const destinationProject = await createTeamProject('destination project', member);
|
||||
const sourceFolder1 = await createFolder(sourceProject, { name: 'Source Folder 1' });
|
||||
@@ -2078,7 +2078,7 @@ describe('PUT /projects/:projectId/folders/:folderId/transfer', () => {
|
||||
|
||||
test('admin transfers folder from project they are not part of, e.g. test global cred sharing scope', async () => {
|
||||
// ARRANGE
|
||||
const admin = await createUser({ role: 'global:admin' });
|
||||
const admin = await createUser({ role: { slug: 'global:admin' } });
|
||||
const sourceProject = await createTeamProject('source project', owner);
|
||||
const destinationProject = await createTeamProject('destination project', owner);
|
||||
const sourceFolder1 = await createFolder(sourceProject, { name: 'Source Folder 1' });
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createCompactedInsightsEvent } from '@/modules/insights/database/entiti
|
||||
import { createUser } from '../shared/db/users';
|
||||
import type { SuperAgentTest } from '../shared/types';
|
||||
import * as utils from '../shared/utils';
|
||||
import { GLOBAL_ADMIN_ROLE, GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE } from '@n8n/db';
|
||||
|
||||
mockInstance(Telemetry);
|
||||
|
||||
@@ -20,9 +21,9 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const admin = await createUser({ role: 'global:admin' });
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
const admin = await createUser({ role: GLOBAL_ADMIN_ROLE });
|
||||
const member = await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
agents.owner = testServer.authAgentFor(owner);
|
||||
agents.admin = testServer.authAgentFor(admin);
|
||||
agents.member = testServer.authAgentFor(member);
|
||||
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { LDAP_DEFAULT_CONFIGURATION } from '@n8n/constants';
|
||||
import type { User } from '@n8n/db';
|
||||
import { AuthProviderSyncHistoryRepository, UserRepository } from '@n8n/db';
|
||||
import {
|
||||
AuthProviderSyncHistoryRepository,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
UserRepository,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { Not } from '@n8n/typeorm';
|
||||
import type { Entry as LdapUser } from 'ldapts';
|
||||
@@ -37,7 +42,7 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
defaultLdapConfig.bindingAdminPassword = Container.get(Cipher).encrypt(
|
||||
@@ -65,7 +70,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
test('Member role should not be able to access ldap routes', async () => {
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
const member = await createUser({ role: { slug: 'global:member' } });
|
||||
const authAgent = testServer.authAgentFor(member);
|
||||
await authAgent.get('/ldap/config').expect(403);
|
||||
await authAgent.put('/ldap/config').expect(403);
|
||||
@@ -137,7 +142,7 @@ describe('PUT /ldap/config', () => {
|
||||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uniqueId());
|
||||
|
||||
const configuration = ldapConfig;
|
||||
|
||||
@@ -250,7 +255,7 @@ describe('POST /ldap/sync', () => {
|
||||
const ldapUserId = uniqueId();
|
||||
|
||||
const member = await createLdapUser(
|
||||
{ role: 'global:member', email: ldapUserEmail },
|
||||
{ role: { slug: 'global:member' }, email: ldapUserEmail },
|
||||
ldapUserId,
|
||||
);
|
||||
|
||||
@@ -279,7 +284,7 @@ describe('POST /ldap/sync', () => {
|
||||
const ldapUserId = uniqueId();
|
||||
|
||||
const member = await createLdapUser(
|
||||
{ role: 'global:member', email: ldapUserEmail },
|
||||
{ role: { slug: 'global:member' }, email: ldapUserEmail },
|
||||
ldapUserId,
|
||||
);
|
||||
|
||||
@@ -364,7 +369,7 @@ describe('POST /ldap/sync', () => {
|
||||
|
||||
await createLdapUser(
|
||||
{
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: randomName(),
|
||||
@@ -397,7 +402,7 @@ describe('POST /ldap/sync', () => {
|
||||
|
||||
await createLdapUser(
|
||||
{
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: ldapUser.sn,
|
||||
@@ -426,7 +431,7 @@ describe('POST /ldap/sync', () => {
|
||||
});
|
||||
|
||||
test('should remove user instance access once the user is disabled during synchronization', async () => {
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uniqueId());
|
||||
|
||||
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue([]);
|
||||
|
||||
@@ -485,7 +490,7 @@ describe('POST /ldap/sync', () => {
|
||||
// Create user with valid email first
|
||||
await createLdapUser(
|
||||
{
|
||||
role: 'global:member',
|
||||
role: GLOBAL_MEMBER_ROLE,
|
||||
email: originalEmail,
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
@@ -603,7 +608,7 @@ describe('POST /login', () => {
|
||||
|
||||
await createLdapUser(
|
||||
{
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: ldapUser.mail,
|
||||
firstName: 'firstname',
|
||||
lastName: 'lastname',
|
||||
@@ -637,7 +642,7 @@ describe('POST /login', () => {
|
||||
};
|
||||
|
||||
await createUser({
|
||||
role: 'global:member',
|
||||
role: GLOBAL_MEMBER_ROLE,
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: 'lastname',
|
||||
@@ -652,7 +657,7 @@ describe('Instance owner should able to delete LDAP users', () => {
|
||||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uniqueId());
|
||||
|
||||
await authOwnerAgent.post(`/users/${member.id}`);
|
||||
});
|
||||
@@ -661,7 +666,7 @@ describe('Instance owner should able to delete LDAP users', () => {
|
||||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
const member = await createLdapUser({ role: { slug: 'global:member' } }, uniqueId());
|
||||
|
||||
// delete the LDAP member and transfer its workflows/credentials to instance owner
|
||||
await authOwnerAgent.post(`/users/${member.id}?transferId=${owner.id}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { testDb } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
import nock from 'nock';
|
||||
|
||||
import config from '@/config';
|
||||
@@ -21,8 +21,8 @@ let authMemberAgent: SuperAgentTest;
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['license'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
owner = await createUserShell('global:owner');
|
||||
member = await createUserShell('global:member');
|
||||
owner = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
member = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import type { User } from '@n8n/db';
|
||||
import { ProjectRepository, UserRepository } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, ProjectRepository, UserRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { IPersonalizationSurveyAnswersV4 } from 'n8n-workflow';
|
||||
import validator from 'validator';
|
||||
@@ -32,7 +32,7 @@ describe('Owner shell', () => {
|
||||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
beforeEach(async () => {
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
|
||||
});
|
||||
|
||||
@@ -139,7 +139,7 @@ describe('Member', () => {
|
||||
beforeEach(async () => {
|
||||
member = await createUser({
|
||||
password: memberPassword,
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
await utils.setInstanceOwnerSetUp(true);
|
||||
@@ -243,7 +243,7 @@ describe('Member', () => {
|
||||
|
||||
describe('Owner', () => {
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
const authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||
|
||||
@@ -30,6 +30,11 @@ beforeEach(async () => {
|
||||
|
||||
owner = await createOwner();
|
||||
|
||||
owner = await Container.get(UserRepository).findOneOrFail({
|
||||
where: { id: owner.id },
|
||||
relations: ['role'],
|
||||
});
|
||||
|
||||
externalHooks.run.mockReset();
|
||||
|
||||
config.set('userManagement.disabled', false);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
testDb,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, UserRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import validator from 'validator';
|
||||
|
||||
@@ -20,7 +20,7 @@ const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });
|
||||
let ownerShell: User;
|
||||
|
||||
beforeEach(async () => {
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
mockInstance,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, UserRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { compare } from 'bcryptjs';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
@@ -39,8 +39,8 @@ let authService: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
member = await createUser({ role: 'global:member' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
member = await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
externalHooks.run.mockReset();
|
||||
jest.replaceProperty(mailer, 'isEmailSetUp', true);
|
||||
authService = Container.get(AuthService);
|
||||
@@ -50,7 +50,7 @@ describe('POST /forgot-password', () => {
|
||||
test('should send password reset email', async () => {
|
||||
const member = await createUser({
|
||||
email: 'test@test.com',
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
@@ -76,7 +76,7 @@ describe('POST /forgot-password', () => {
|
||||
await setCurrentAuthenticationMethod('saml');
|
||||
const member = await createUser({
|
||||
email: 'test@test.com',
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
await testServer.authlessAgent
|
||||
|
||||
@@ -222,7 +222,7 @@ describe('Public API endpoints with feat:apiKeyScopes enabled', () => {
|
||||
expect(returnedUser.id).toBe(storedUser.id);
|
||||
expect(returnedUser.email).toBe(storedUser.email);
|
||||
expect(returnedUser.email).toBe(payloadUser.email);
|
||||
expect(storedUser.role).toBe(payloadUser.role);
|
||||
expect(storedUser.role.slug).toBe(payloadUser.role);
|
||||
});
|
||||
|
||||
test('should fail to create user when API key doesn\'t have "user:create" scope', async () => {
|
||||
@@ -267,7 +267,7 @@ describe('Public API endpoints with feat:apiKeyScopes enabled', () => {
|
||||
*/
|
||||
expect(response.status).toBe(204);
|
||||
const storedUser = await getUserById(member.id);
|
||||
expect(storedUser.role).toBe(payload.newRoleName);
|
||||
expect(storedUser.role.slug).toBe(payload.newRoleName);
|
||||
});
|
||||
|
||||
test('should fail to change role when API key doesn\'t have "user:changeRole" scope', async () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
testDb,
|
||||
mockInstance,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, type User } from '@n8n/db';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import validator from 'validator';
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('With license unlimited quota:users', () => {
|
||||
test('should return a pending user', async () => {
|
||||
const owner = await createOwnerWithApiKey();
|
||||
|
||||
const { id: memberId } = await createUserShell('global:member');
|
||||
const { id: memberId } = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
const response = await authOwnerAgent.get(`/users/${memberId}`).expect(200);
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('Users in Public API', () => {
|
||||
expect(returnedUser.id).toBe(storedUser.id);
|
||||
expect(returnedUser.email).toBe(storedUser.email);
|
||||
expect(returnedUser.email).toBe(payloadUser.email);
|
||||
expect(storedUser.role).toBe(payloadUser.role);
|
||||
expect(storedUser.role.slug).toBe(payloadUser.role);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,7 +275,7 @@ describe('Users in Public API', () => {
|
||||
*/
|
||||
expect(response.status).toBe(204);
|
||||
const storedUser = await getUserById(member.id);
|
||||
expect(storedUser.role).toBe(payload.newRoleName);
|
||||
expect(storedUser.role.slug).toBe(payload.newRoleName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { randomEmail, randomName, randomValidPassword } from '@n8n/backend-test-utils';
|
||||
import { AuthIdentity, AuthIdentityRepository, UserRepository } from '@n8n/db';
|
||||
import {
|
||||
AuthIdentity,
|
||||
AuthIdentityRepository,
|
||||
GLOBAL_ADMIN_ROLE,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
type Role,
|
||||
UserRepository,
|
||||
} from '@n8n/db';
|
||||
import { type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { ApiKeyScope, GlobalRole } from '@n8n/permissions';
|
||||
import type { ApiKeyScope } from '@n8n/permissions';
|
||||
import { getApiKeyScopesForRole } from '@n8n/permissions';
|
||||
import { hash } from 'bcryptjs';
|
||||
|
||||
import { MfaService } from '@/mfa/mfa.service';
|
||||
import { TOTPService } from '@/mfa/totp.service';
|
||||
import { PublicApiKeyService } from '@/services/public-api-key.service';
|
||||
import type { DeepPartial } from '@n8n/typeorm';
|
||||
|
||||
type ApiKeyOptions = {
|
||||
expiresAt?: number | null;
|
||||
@@ -32,26 +41,26 @@ async function handlePasswordSetup(password: string | null | undefined): Promise
|
||||
}
|
||||
|
||||
/** Store a new user object, defaulting to a `member` */
|
||||
export async function newUser(attributes: Partial<User> = {}): Promise<User> {
|
||||
export async function newUser(attributes: DeepPartial<User> = {}): Promise<User> {
|
||||
const { email, password, firstName, lastName, role, ...rest } = attributes;
|
||||
return Container.get(UserRepository).create({
|
||||
email: email ?? randomEmail(),
|
||||
password: await handlePasswordSetup(password),
|
||||
firstName: firstName ?? randomName(),
|
||||
lastName: lastName ?? randomName(),
|
||||
role: role ?? 'global:member',
|
||||
role: role ?? GLOBAL_MEMBER_ROLE,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
|
||||
/** Store a user object in the DB */
|
||||
export async function createUser(attributes: Partial<User> = {}): Promise<User> {
|
||||
export async function createUser(attributes: DeepPartial<User> = {}): Promise<User> {
|
||||
const userInstance = await newUser(attributes);
|
||||
const { user } = await Container.get(UserRepository).createUserWithProject(userInstance);
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function createLdapUser(attributes: Partial<User>, ldapId: string): Promise<User> {
|
||||
export async function createLdapUser(attributes: DeepPartial<User>, ldapId: string): Promise<User> {
|
||||
const user = await createUser(attributes);
|
||||
await Container.get(AuthIdentityRepository).save(AuthIdentity.create(user, ldapId, 'ldap'));
|
||||
return user;
|
||||
@@ -105,7 +114,7 @@ export const addApiKey = async (
|
||||
return await Container.get(PublicApiKeyService).createPublicApiKeyForUser(user, {
|
||||
label: randomName(),
|
||||
expiresAt,
|
||||
scopes: scopes.length ? scopes : getApiKeyScopesForRole(user.role),
|
||||
scopes: scopes.length ? scopes : getApiKeyScopesForRole(user),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -134,21 +143,21 @@ export async function createAdminWithApiKey({ expiresAt = null, scopes = [] }: A
|
||||
}
|
||||
|
||||
export async function createOwner() {
|
||||
return await createUser({ role: 'global:owner' });
|
||||
return await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
}
|
||||
|
||||
export async function createMember() {
|
||||
return await createUser({ role: 'global:member' });
|
||||
return await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
}
|
||||
|
||||
export async function createAdmin() {
|
||||
return await createUser({ role: 'global:admin' });
|
||||
return await createUser({ role: GLOBAL_ADMIN_ROLE });
|
||||
}
|
||||
|
||||
export async function createUserShell(role: GlobalRole): Promise<User> {
|
||||
const shell: Partial<User> = { role };
|
||||
export async function createUserShell(role: Role): Promise<User> {
|
||||
const shell: DeepPartial<User> = { role };
|
||||
|
||||
if (role !== 'global:owner') {
|
||||
if (role.slug !== GLOBAL_OWNER_ROLE.slug) {
|
||||
shell.email = randomEmail();
|
||||
}
|
||||
|
||||
@@ -161,7 +170,7 @@ export async function createUserShell(role: GlobalRole): Promise<User> {
|
||||
*/
|
||||
export async function createManyUsers(
|
||||
amount: number,
|
||||
attributes: Partial<User> = {},
|
||||
attributes: DeepPartial<User> = {},
|
||||
): Promise<User[]> {
|
||||
const result = await Promise.all(
|
||||
Array(amount)
|
||||
@@ -176,13 +185,13 @@ export async function createManyUsers(
|
||||
|
||||
export const getAllUsers = async () =>
|
||||
await Container.get(UserRepository).find({
|
||||
relations: ['authIdentities'],
|
||||
relations: ['authIdentities', 'role'],
|
||||
});
|
||||
|
||||
export const getUserById = async (id: string) =>
|
||||
await Container.get(UserRepository).findOneOrFail({
|
||||
where: { id },
|
||||
relations: ['authIdentities'],
|
||||
relations: ['authIdentities', 'role'],
|
||||
});
|
||||
|
||||
export const getLdapIdentities = async () =>
|
||||
@@ -192,5 +201,8 @@ export const getLdapIdentities = async () =>
|
||||
});
|
||||
|
||||
export async function getGlobalOwner() {
|
||||
return await Container.get(UserRepository).findOneByOrFail({ role: 'global:owner' });
|
||||
return await Container.get(UserRepository).findOneOrFail({
|
||||
where: { role: { slug: GLOBAL_OWNER_ROLE.slug } },
|
||||
relations: ['role'],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { testDb } from '@n8n/backend-test-utils';
|
||||
import { TagRepository } from '@n8n/db';
|
||||
import { GLOBAL_OWNER_ROLE, TagRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { createUserShell } from './shared/db/users';
|
||||
@@ -11,7 +11,7 @@ let authOwnerAgent: SuperAgentTest;
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['tags'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
const ownerShell = await createUserShell('global:owner');
|
||||
const ownerShell = await createUserShell(GLOBAL_OWNER_ROLE);
|
||||
authOwnerAgent = testServer.authAgentFor(ownerShell);
|
||||
});
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('UserRepository', () => {
|
||||
test('should create personal project for a user', async () => {
|
||||
const { user, project } = await userRepository.createUserWithProject({
|
||||
email: randomEmail(),
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const projectRelation = await Container.get(ProjectRelationRepository).findOneOrFail({
|
||||
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
import type { PublicUser, User } from '@n8n/db';
|
||||
import {
|
||||
FolderRepository,
|
||||
GLOBAL_ADMIN_ROLE,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
GLOBAL_OWNER_ROLE,
|
||||
ProjectRelationRepository,
|
||||
ProjectRepository,
|
||||
SharedCredentialsRepository,
|
||||
@@ -61,26 +64,26 @@ describe('GET /users', () => {
|
||||
userRepository = Container.get(UserRepository);
|
||||
|
||||
owner = await createUser({
|
||||
role: 'global:owner',
|
||||
role: GLOBAL_OWNER_ROLE,
|
||||
email: 'owner@n8n.io',
|
||||
firstName: 'OwnerFirstName',
|
||||
lastName: 'OwnerLastName',
|
||||
});
|
||||
member1 = await createUser({
|
||||
role: 'global:member',
|
||||
role: GLOBAL_MEMBER_ROLE,
|
||||
email: 'member1@n8n.io',
|
||||
firstName: 'Member1FirstName',
|
||||
lastName: 'Member1LastName',
|
||||
mfaEnabled: true,
|
||||
});
|
||||
member2 = await createUser({
|
||||
role: 'global:member',
|
||||
role: GLOBAL_MEMBER_ROLE,
|
||||
email: 'member2@n8n.io',
|
||||
firstName: 'Member2FirstName',
|
||||
lastName: 'Member2LastName',
|
||||
});
|
||||
await createUser({
|
||||
role: 'global:admin',
|
||||
role: GLOBAL_ADMIN_ROLE,
|
||||
email: 'admin@n8n.io',
|
||||
firstName: 'AdminFirstName',
|
||||
lastName: 'AdminLastName',
|
||||
@@ -575,7 +578,7 @@ describe('GET /users', () => {
|
||||
let pendingUser: User;
|
||||
beforeAll(async () => {
|
||||
pendingUser = await createUser({
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: 'pending@n8n.io',
|
||||
firstName: 'PendingFirstName',
|
||||
lastName: 'PendingLastName',
|
||||
@@ -723,14 +726,14 @@ describe('GET /users', () => {
|
||||
|
||||
test('should sort by firstName and lastName combined', async () => {
|
||||
const user1 = await createUser({
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: 'memberz1@n8n.io',
|
||||
firstName: 'ZZZFirstName',
|
||||
lastName: 'ZZZLastName',
|
||||
});
|
||||
|
||||
const user2 = await createUser({
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
email: 'memberz2@n8n.io',
|
||||
firstName: 'ZZZFirstName',
|
||||
lastName: 'ZZYLastName',
|
||||
@@ -1423,7 +1426,7 @@ describe('PATCH /users/:id/role', () => {
|
||||
|
||||
const user = await getUserById(otherAdmin.id);
|
||||
|
||||
expect(user.role).toBe('global:member');
|
||||
expect(user.role.slug).toBe('global:member');
|
||||
|
||||
// restore other admin
|
||||
|
||||
@@ -1441,7 +1444,7 @@ describe('PATCH /users/:id/role', () => {
|
||||
|
||||
const user = await getUserById(admin.id);
|
||||
|
||||
expect(user.role).toBe('global:member');
|
||||
expect(user.role.slug).toBe('global:member');
|
||||
|
||||
// restore admin
|
||||
|
||||
@@ -1457,9 +1460,9 @@ describe('PATCH /users/:id/role', () => {
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data).toStrictEqual({ success: true });
|
||||
|
||||
const user = await getUserById(admin.id);
|
||||
const user = await getUserById(member.id);
|
||||
|
||||
expect(user.role).toBe('global:admin');
|
||||
expect(user.role.slug).toBe('global:admin');
|
||||
|
||||
// restore member
|
||||
|
||||
@@ -1508,7 +1511,7 @@ describe('PATCH /users/:id/role', () => {
|
||||
|
||||
const user = await getUserById(admin.id);
|
||||
|
||||
expect(user.role).toBe('global:admin');
|
||||
expect(user.role.slug).toBe('global:admin');
|
||||
|
||||
// restore member
|
||||
|
||||
@@ -1526,7 +1529,7 @@ describe('PATCH /users/:id/role', () => {
|
||||
|
||||
const user = await getUserById(admin.id);
|
||||
|
||||
expect(user.role).toBe('global:member');
|
||||
expect(user.role.slug).toBe('global:member');
|
||||
|
||||
// restore admin
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { createWorkflow, shareWorkflowWithUsers, testDb } from '@n8n/backend-test-utils';
|
||||
import type { User } from '@n8n/db';
|
||||
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
@@ -17,9 +17,9 @@ let projectService: ProjectService;
|
||||
|
||||
beforeAll(async () => {
|
||||
await testDb.init();
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
member = await createUser({ role: 'global:member' });
|
||||
anotherMember = await createUser({ role: 'global:member' });
|
||||
owner = await createUser({ role: GLOBAL_OWNER_ROLE });
|
||||
member = await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
anotherMember = await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
const licenseMock = mock<LicenseState>();
|
||||
licenseMock.isSharingLicensed.mockReturnValue(true);
|
||||
licenseMock.getMaxTeamProjects.mockReturnValue(-1);
|
||||
@@ -39,7 +39,6 @@ afterAll(async () => {
|
||||
describe('WorkflowSharingService', () => {
|
||||
describe('getSharedWorkflowIds', () => {
|
||||
it('should show all workflows to owners', async () => {
|
||||
owner.role = 'global:owner';
|
||||
const workflow1 = await createWorkflow({}, member);
|
||||
const workflow2 = await createWorkflow({}, anotherMember);
|
||||
const sharedWorkflowIds = await workflowSharingService.getSharedWorkflowIds(owner, {
|
||||
@@ -51,7 +50,6 @@ describe('WorkflowSharingService', () => {
|
||||
});
|
||||
|
||||
it('should show shared workflows to users', async () => {
|
||||
member.role = 'global:member';
|
||||
const workflow1 = await createWorkflow({}, anotherMember);
|
||||
const workflow2 = await createWorkflow({}, anotherMember);
|
||||
const workflow3 = await createWorkflow({}, anotherMember);
|
||||
|
||||
@@ -21,7 +21,7 @@ const testServer = utils.setupTestServer({
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
member = await createUser({ role: 'global:member' });
|
||||
member = await createUser({ role: { slug: 'global:member' } });
|
||||
|
||||
await utils.initNodeTypes();
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
WorkflowHistoryRepository,
|
||||
SharedWorkflowRepository,
|
||||
WorkflowRepository,
|
||||
GLOBAL_MEMBER_ROLE,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { ProjectRole } from '@n8n/permissions';
|
||||
@@ -70,9 +71,9 @@ beforeAll(async () => {
|
||||
owner = await createOwner();
|
||||
admin = await createAdmin();
|
||||
ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
|
||||
member = await createUser({ role: 'global:member' });
|
||||
member = await createUser({ role: { slug: 'global:member' } });
|
||||
memberPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(member.id);
|
||||
anotherMember = await createUser({ role: 'global:member' });
|
||||
anotherMember = await createUser({ role: { slug: 'global:member' } });
|
||||
anotherMemberPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
anotherMember.id,
|
||||
);
|
||||
@@ -159,7 +160,7 @@ describe('PUT /workflows/:workflowId/share', () => {
|
||||
|
||||
test('should allow sharing with pending users', async () => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE);
|
||||
const memberShellPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
memberShell.id,
|
||||
);
|
||||
@@ -272,7 +273,7 @@ describe('PUT /workflows/:workflowId/share', () => {
|
||||
test('should not allow sharing by another non-shared member', async () => {
|
||||
const workflow = await createWorkflow({}, member);
|
||||
|
||||
const tempUser = await createUser({ role: 'global:member' });
|
||||
const tempUser = await createUser({ role: { slug: 'global:member' } });
|
||||
const tempUserPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||
tempUser.id,
|
||||
);
|
||||
|
||||
@@ -657,7 +657,7 @@ describe('GET /workflows', () => {
|
||||
|
||||
test('should return workflows with scopes when ?includeScopes=true', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const teamProject = await createTeamProject(undefined, member1);
|
||||
@@ -1474,7 +1474,7 @@ describe('GET /workflows?includeFolders=true', () => {
|
||||
|
||||
test('should return workflows with scopes and folders when ?includeScopes=true', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
role: 'global:member',
|
||||
role: { slug: 'global:member' },
|
||||
});
|
||||
|
||||
const teamProject = await createTeamProject(undefined, member1);
|
||||
@@ -2165,6 +2165,8 @@ describe('PATCH /workflows/:workflowId', () => {
|
||||
|
||||
const response = await authOwnerAgent.patch(`/workflows/${workflow.id}`).send(payload);
|
||||
|
||||
console.log(response.body);
|
||||
|
||||
const {
|
||||
data: { id },
|
||||
} = response.body;
|
||||
|
||||
Reference in New Issue
Block a user