mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 04:47:29 -08:00
refactor(core): Remove roleId indirection (no-changelog) (#8413)
This commit is contained in:
parent
1affebd85e
commit
d6deceacde
|
@ -84,7 +84,7 @@ export class ActiveWebhooks implements IWebhookManager {
|
|||
|
||||
const workflowData = await this.workflowRepository.findOne({
|
||||
where: { id: webhook.workflowId },
|
||||
relations: ['shared', 'shared.user', 'shared.user.globalRole'],
|
||||
relations: ['shared', 'shared.user'],
|
||||
});
|
||||
|
||||
if (workflowData === null) {
|
||||
|
|
|
@ -229,7 +229,7 @@ export class ActiveWorkflowRunner {
|
|||
async clearWebhooks(workflowId: string) {
|
||||
const workflowData = await this.workflowRepository.findOne({
|
||||
where: { id: workflowId },
|
||||
relations: ['shared', 'shared.user', 'shared.user.globalRole'],
|
||||
relations: ['shared', 'shared.user'],
|
||||
});
|
||||
|
||||
if (workflowData === null) {
|
||||
|
@ -615,7 +615,7 @@ export class ActiveWorkflowRunner {
|
|||
);
|
||||
}
|
||||
|
||||
const sharing = dbWorkflow.shared.find((shared) => shared.role.name === 'owner');
|
||||
const sharing = dbWorkflow.shared.find((shared) => shared.role === 'workflow:owner');
|
||||
|
||||
if (!sharing) {
|
||||
throw new WorkflowActivationError(`Workflow ${dbWorkflow.display()} has no owner`);
|
||||
|
|
|
@ -786,15 +786,9 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
|
||||
const credential = await this.sharedCredentialsRepository.findOne({
|
||||
where: {
|
||||
role: {
|
||||
scope: 'credential',
|
||||
name: 'owner',
|
||||
},
|
||||
role: 'credential:owner',
|
||||
user: {
|
||||
globalRole: {
|
||||
scope: 'global',
|
||||
name: 'owner',
|
||||
},
|
||||
role: 'global:owner',
|
||||
},
|
||||
credentials: {
|
||||
id: nodeCredential.id,
|
||||
|
|
|
@ -97,6 +97,16 @@ export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions {
|
|||
}
|
||||
}
|
||||
|
||||
export async function setSchema(conn: Connection) {
|
||||
const schema = config.getEnv('database.postgresdb.schema');
|
||||
const searchPath = ['public'];
|
||||
if (schema !== 'public') {
|
||||
await conn.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
||||
searchPath.unshift(schema);
|
||||
}
|
||||
await conn.query(`SET search_path TO ${searchPath.join(',')};`);
|
||||
}
|
||||
|
||||
export async function init(testConnectionOptions?: ConnectionOptions): Promise<void> {
|
||||
if (connectionState.connected) return;
|
||||
|
||||
|
@ -130,13 +140,7 @@ export async function init(testConnectionOptions?: ConnectionOptions): Promise<v
|
|||
await connection.initialize();
|
||||
|
||||
if (dbType === 'postgresdb') {
|
||||
const schema = config.getEnv('database.postgresdb.schema');
|
||||
const searchPath = ['public'];
|
||||
if (schema !== 'public') {
|
||||
await connection.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
||||
searchPath.unshift(schema);
|
||||
}
|
||||
await connection.query(`SET search_path TO ${searchPath.join(',')};`);
|
||||
await setSchema(connection);
|
||||
}
|
||||
|
||||
connectionState.connected = true;
|
||||
|
|
|
@ -35,10 +35,9 @@ import type { ChildProcess } from 'child_process';
|
|||
|
||||
import type { DatabaseType } from '@db/types';
|
||||
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import type { TagEntity } from '@db/entities/TagEntity';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { GlobalRole, User } from '@db/entities/User';
|
||||
import type { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import type { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
import type { UserRepository } from '@db/repositories/user.repository';
|
||||
|
@ -681,7 +680,7 @@ export interface PublicUser {
|
|||
createdAt: Date;
|
||||
isPending: boolean;
|
||||
hasRecoveryCodesLeft: boolean;
|
||||
globalRole?: Role;
|
||||
role?: GlobalRole;
|
||||
globalScopes?: Scope[];
|
||||
signInType: AuthProviderType;
|
||||
disabled: boolean;
|
||||
|
|
|
@ -22,11 +22,11 @@ import { Telemetry } from '@/telemetry';
|
|||
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
||||
import { eventBus } from './eventbus';
|
||||
import { EventsService } from '@/services/events.service';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { GlobalRole, User } from '@db/entities/User';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { NodeTypes } from './NodeTypes';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
|
||||
import { RoleService } from './services/role.service';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import type { EventPayloadWorkflow } from './eventbus/EventMessageClasses/EventMessageWorkflow';
|
||||
import { determineFinalExecutionStatus } from './executionLifecycleHooks/shared/sharedHookFunctions';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
@ -36,14 +36,14 @@ function userToPayload(user: User): {
|
|||
_email: string;
|
||||
_firstName: string;
|
||||
_lastName: string;
|
||||
globalRole?: string;
|
||||
globalRole: GlobalRole;
|
||||
} {
|
||||
return {
|
||||
userId: user.id,
|
||||
_email: user.email,
|
||||
_firstName: user.firstName,
|
||||
_lastName: user.lastName,
|
||||
globalRole: user.globalRole?.name,
|
||||
globalRole: user.role,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class InternalHooks {
|
|||
constructor(
|
||||
private telemetry: Telemetry,
|
||||
private nodeTypes: NodeTypes,
|
||||
private roleService: RoleService,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
eventsService: EventsService,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
) {
|
||||
|
@ -166,9 +166,9 @@ export class InternalHooks {
|
|||
|
||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||
if (user.id && workflow.id) {
|
||||
const role = await this.roleService.findRoleByUserAndWorkflow(user.id, workflow.id);
|
||||
const role = await this.sharedWorkflowRepository.findSharingRole(user.id, workflow.id);
|
||||
if (role) {
|
||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||
userRole = role === 'workflow:owner' ? 'owner' : 'sharee';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,9 +371,9 @@ export class InternalHooks {
|
|||
|
||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||
if (userId) {
|
||||
const role = await this.roleService.findRoleByUserAndWorkflow(userId, workflow.id);
|
||||
const role = await this.sharedWorkflowRepository.findSharingRole(userId, workflow.id);
|
||||
if (role) {
|
||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||
userRole = role === 'workflow:owner' ? 'owner' : 'sharee';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Container } from 'typedi';
|
|||
import { validate } from 'jsonschema';
|
||||
import * as Db from '@/Db';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { User } from '@db/entities/User';
|
||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
||||
|
@ -18,7 +17,6 @@ import {
|
|||
} from './constants';
|
||||
import type { ConnectionSecurity, LdapConfig } from './types';
|
||||
import { License } from '@/License';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { AuthProviderSyncHistoryRepository } from '@db/repositories/authProviderSyncHistory.repository';
|
||||
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
|
||||
|
@ -47,13 +45,6 @@ export const randomPassword = (): string => {
|
|||
return Math.random().toString(36).slice(-8);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the user role to be assigned to LDAP users
|
||||
*/
|
||||
export const getLdapUserRole = async (): Promise<Role> => {
|
||||
return await Container.get(RoleService).findGlobalMemberRole();
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the structure of the LDAP configuration schema
|
||||
*/
|
||||
|
@ -102,7 +93,7 @@ export const getAuthIdentityByLdapId = async (
|
|||
idAttributeValue: string,
|
||||
): Promise<AuthIdentity | null> => {
|
||||
return await Container.get(AuthIdentityRepository).findOne({
|
||||
relations: ['user', 'user.globalRole'],
|
||||
relations: ['user'],
|
||||
where: {
|
||||
providerId: idAttributeValue,
|
||||
providerType: 'ldap',
|
||||
|
@ -113,7 +104,6 @@ export const getAuthIdentityByLdapId = async (
|
|||
export const getUserByEmail = async (email: string): Promise<User | null> => {
|
||||
return await Container.get(UserRepository).findOne({
|
||||
where: { email },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -164,13 +154,13 @@ export const getLdapUsers = async (): Promise<User[]> => {
|
|||
export const mapLdapUserToDbUser = (
|
||||
ldapUser: LdapUser,
|
||||
ldapConfig: LdapConfig,
|
||||
role?: Role,
|
||||
toCreate = false,
|
||||
): [string, User] => {
|
||||
const user = new User();
|
||||
const [ldapId, data] = mapLdapAttributesToUser(ldapUser, ldapConfig);
|
||||
Object.assign(user, data);
|
||||
if (role) {
|
||||
user.globalRole = role;
|
||||
if (toCreate) {
|
||||
user.role = 'global:member';
|
||||
user.password = randomPassword();
|
||||
user.disabled = false;
|
||||
} else {
|
||||
|
@ -270,10 +260,10 @@ export const createLdapAuthIdentity = async (user: User, ldapId: string) => {
|
|||
return await Container.get(AuthIdentityRepository).save(AuthIdentity.create(user, ldapId));
|
||||
};
|
||||
|
||||
export const createLdapUserOnLocalDb = async (role: Role, data: Partial<User>, ldapId: string) => {
|
||||
export const createLdapUserOnLocalDb = async (data: Partial<User>, ldapId: string) => {
|
||||
const user = await Container.get(UserRepository).save({
|
||||
password: randomPassword(),
|
||||
globalRole: role,
|
||||
role: 'global:member',
|
||||
...data,
|
||||
});
|
||||
await createLdapAuthIdentity(user, ldapId);
|
||||
|
|
|
@ -7,7 +7,6 @@ import { ApplicationError, jsonParse } from 'n8n-workflow';
|
|||
import { Cipher } from 'n8n-core';
|
||||
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { RunningMode, SyncStatus } from '@db/entities/AuthProviderSyncHistory';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
|
@ -30,7 +29,6 @@ import {
|
|||
escapeFilter,
|
||||
formatUrl,
|
||||
getLdapIds,
|
||||
getLdapUserRole,
|
||||
getLdapUsers,
|
||||
getMappingAttributes,
|
||||
mapLdapUserToDbUser,
|
||||
|
@ -346,12 +344,9 @@ export class LdapService {
|
|||
|
||||
const localAdUsers = await getLdapIds();
|
||||
|
||||
const role = await getLdapUserRole();
|
||||
|
||||
const { usersToCreate, usersToUpdate, usersToDisable } = this.getUsersToProcess(
|
||||
adUsers,
|
||||
localAdUsers,
|
||||
role,
|
||||
);
|
||||
|
||||
this.logger.debug('LDAP - Users processed', {
|
||||
|
@ -407,14 +402,13 @@ export class LdapService {
|
|||
private getUsersToProcess(
|
||||
adUsers: LdapUser[],
|
||||
localAdUsers: string[],
|
||||
role: Role,
|
||||
): {
|
||||
usersToCreate: Array<[string, User]>;
|
||||
usersToUpdate: Array<[string, User]>;
|
||||
usersToDisable: string[];
|
||||
} {
|
||||
return {
|
||||
usersToCreate: this.getUsersToCreate(adUsers, localAdUsers, role),
|
||||
usersToCreate: this.getUsersToCreate(adUsers, localAdUsers),
|
||||
usersToUpdate: this.getUsersToUpdate(adUsers, localAdUsers),
|
||||
usersToDisable: this.getUsersToDisable(adUsers, localAdUsers),
|
||||
};
|
||||
|
@ -424,11 +418,10 @@ export class LdapService {
|
|||
private getUsersToCreate(
|
||||
remoteAdUsers: LdapUser[],
|
||||
localLdapIds: string[],
|
||||
role: Role,
|
||||
): Array<[string, User]> {
|
||||
return remoteAdUsers
|
||||
.filter((adUser) => !localLdapIds.includes(adUser[this.config.ldapIdAttribute] as string))
|
||||
.map((adUser) => mapLdapUserToDbUser(adUser, this.config, role));
|
||||
.map((adUser) => mapLdapUserToDbUser(adUser, this.config, true));
|
||||
}
|
||||
|
||||
/** Get users in LDAP that are already in the database */
|
||||
|
|
|
@ -98,7 +98,6 @@ async function createApiRouter(
|
|||
const apiKey = req.headers[schema.name.toLowerCase()] as string;
|
||||
const user = await Container.get(UserRepository).findOne({
|
||||
where: { apiKey },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
if (!user) return false;
|
||||
|
|
|
@ -3,8 +3,6 @@ import type { IDataObject, ExecutionStatus } from 'n8n-workflow';
|
|||
|
||||
import type { User } from '@db/entities/User';
|
||||
|
||||
import type { Role } from '@db/entities/Role';
|
||||
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
|
||||
import type { UserManagementMailer } from '@/UserManagement/email';
|
||||
|
@ -25,7 +23,6 @@ export type AuthenticatedRequest<
|
|||
RequestQuery = {},
|
||||
> = express.Request<RouteParams, ResponseBody, RequestBody, RequestQuery> & {
|
||||
user: User;
|
||||
globalMemberRole?: Role;
|
||||
mailer?: UserManagementMailer;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import Container from 'typedi';
|
|||
|
||||
export = {
|
||||
generateAudit: [
|
||||
authorize(['owner', 'admin']),
|
||||
authorize(['global:owner', 'global:admin']),
|
||||
async (req: AuditRequest.Generate, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { SecurityAuditService } = await import('@/security-audit/SecurityAudit.service');
|
||||
|
|
|
@ -23,7 +23,7 @@ import { Container } from 'typedi';
|
|||
|
||||
export = {
|
||||
createCredential: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
validCredentialType,
|
||||
validCredentialsProperties,
|
||||
async (
|
||||
|
@ -47,7 +47,7 @@ export = {
|
|||
},
|
||||
],
|
||||
deleteCredential: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (
|
||||
req: CredentialRequest.Delete,
|
||||
res: express.Response,
|
||||
|
@ -55,13 +55,10 @@ export = {
|
|||
const { id: credentialId } = req.params;
|
||||
let credential: CredentialsEntity | undefined;
|
||||
|
||||
if (!['owner', 'admin'].includes(req.user.globalRole.name)) {
|
||||
const shared = await getSharedCredentials(req.user.id, credentialId, [
|
||||
'credentials',
|
||||
'role',
|
||||
]);
|
||||
if (!['global:owner', 'global:admin'].includes(req.user.role)) {
|
||||
const shared = await getSharedCredentials(req.user.id, credentialId);
|
||||
|
||||
if (shared?.role.name === 'owner') {
|
||||
if (shared?.role === 'credential:owner') {
|
||||
credential = shared.credentials;
|
||||
}
|
||||
} else {
|
||||
|
@ -78,7 +75,7 @@ export = {
|
|||
],
|
||||
|
||||
getCredentialType: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: CredentialTypeRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||
const { credentialTypeName } = req.params;
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import { ExternalHooks } from '@/ExternalHooks';
|
|||
import type { IDependency, IJsonSchema } from '../../../types';
|
||||
import type { CredentialRequest } from '@/requests';
|
||||
import { Container } from 'typedi';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
|
||||
|
@ -20,14 +19,13 @@ export async function getCredentials(credentialId: string): Promise<ICredentials
|
|||
export async function getSharedCredentials(
|
||||
userId: string,
|
||||
credentialId: string,
|
||||
relations?: string[],
|
||||
): Promise<SharedCredentials | null> {
|
||||
return await Container.get(SharedCredentialsRepository).findOne({
|
||||
where: {
|
||||
userId,
|
||||
credentialsId: credentialId,
|
||||
},
|
||||
relations,
|
||||
relations: ['credentials'],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -60,8 +58,6 @@ export async function saveCredential(
|
|||
user: User,
|
||||
encryptedData: ICredentialsDb,
|
||||
): Promise<CredentialsEntity> {
|
||||
const role = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
await Container.get(ExternalHooks).run('credentials.create', [encryptedData]);
|
||||
|
||||
return await Db.transaction(async (transactionManager) => {
|
||||
|
@ -72,7 +68,7 @@ export async function saveCredential(
|
|||
const newSharedCredential = new SharedCredentials();
|
||||
|
||||
Object.assign(newSharedCredential, {
|
||||
role,
|
||||
role: 'credential:owner',
|
||||
user,
|
||||
credentials: savedCredential,
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ExecutionRepository } from '@db/repositories/execution.repository';
|
|||
|
||||
export = {
|
||||
deleteExecution: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: ExecutionRequest.Delete, res: express.Response): Promise<express.Response> => {
|
||||
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
|
||||
|
||||
|
@ -44,7 +44,7 @@ export = {
|
|||
},
|
||||
],
|
||||
getExecution: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: ExecutionRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
|
||||
|
||||
|
@ -75,7 +75,7 @@ export = {
|
|||
},
|
||||
],
|
||||
getExecutions: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
validCursor,
|
||||
async (req: ExecutionRequest.GetAll, res: express.Response): Promise<express.Response> => {
|
||||
const {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { InternalHooks } from '@/InternalHooks';
|
|||
|
||||
export = {
|
||||
pull: [
|
||||
authorize(['owner', 'admin']),
|
||||
authorize(['global:owner', 'global:admin']),
|
||||
async (
|
||||
req: PublicSourceControlRequest.Pull,
|
||||
res: express.Response,
|
||||
|
|
|
@ -36,5 +36,7 @@ properties:
|
|||
description: Last time the user was updated.
|
||||
format: date-time
|
||||
readOnly: true
|
||||
globalRole:
|
||||
$ref: './role.yml'
|
||||
role:
|
||||
type: string
|
||||
example: owner
|
||||
readOnly: true
|
||||
|
|
|
@ -15,7 +15,7 @@ import { InternalHooks } from '@/InternalHooks';
|
|||
export = {
|
||||
getUser: [
|
||||
validLicenseWithUserQuota,
|
||||
authorize(['owner', 'admin']),
|
||||
authorize(['global:owner', 'global:admin']),
|
||||
async (req: UserRequest.Get, res: express.Response) => {
|
||||
const { includeRole = false } = req.query;
|
||||
const { id } = req.params;
|
||||
|
@ -41,7 +41,7 @@ export = {
|
|||
getUsers: [
|
||||
validLicenseWithUserQuota,
|
||||
validCursor,
|
||||
authorize(['owner', 'admin']),
|
||||
authorize(['global:owner', 'global:admin']),
|
||||
async (req: UserRequest.Get, res: express.Response) => {
|
||||
const { offset = 0, limit = 100, includeRole = false } = req.query;
|
||||
|
||||
|
|
|
@ -4,24 +4,21 @@ import type { User } from '@db/entities/User';
|
|||
import pick from 'lodash/pick';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
|
||||
export const getSelectableProperties = (table: 'user' | 'role'): string[] => {
|
||||
return {
|
||||
user: ['id', 'email', 'firstName', 'lastName', 'createdAt', 'updatedAt', 'isPending'],
|
||||
role: ['id', 'name', 'scope', 'createdAt', 'updatedAt'],
|
||||
}[table];
|
||||
};
|
||||
|
||||
export async function getUser(data: {
|
||||
withIdentifier: string;
|
||||
includeRole?: boolean;
|
||||
}): Promise<User | null> {
|
||||
return await Container.get(UserRepository).findOne({
|
||||
where: {
|
||||
...(uuidValidate(data.withIdentifier) && { id: data.withIdentifier }),
|
||||
...(!uuidValidate(data.withIdentifier) && { email: data.withIdentifier }),
|
||||
},
|
||||
relations: data?.includeRole ? ['globalRole'] : undefined,
|
||||
});
|
||||
return await Container.get(UserRepository)
|
||||
.findOne({
|
||||
where: {
|
||||
...(uuidValidate(data.withIdentifier) && { id: data.withIdentifier }),
|
||||
...(!uuidValidate(data.withIdentifier) && { email: data.withIdentifier }),
|
||||
},
|
||||
})
|
||||
.then((user) => {
|
||||
if (user && !data?.includeRole) delete (user as Partial<User>).role;
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllUsersAndCount(data: {
|
||||
|
@ -31,19 +28,29 @@ export async function getAllUsersAndCount(data: {
|
|||
}): Promise<[User[], number]> {
|
||||
const users = await Container.get(UserRepository).find({
|
||||
where: {},
|
||||
relations: data?.includeRole ? ['globalRole'] : undefined,
|
||||
skip: data.offset,
|
||||
take: data.limit,
|
||||
});
|
||||
if (!data?.includeRole) {
|
||||
users.forEach((user) => {
|
||||
delete (user as Partial<User>).role;
|
||||
});
|
||||
}
|
||||
const count = await Container.get(UserRepository).count();
|
||||
return [users, count];
|
||||
}
|
||||
|
||||
const userProperties = [
|
||||
'id',
|
||||
'email',
|
||||
'firstName',
|
||||
'lastName',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'isPending',
|
||||
];
|
||||
function pickUserSelectableProperties(user: User, options?: { includeRole: boolean }) {
|
||||
return pick(
|
||||
user,
|
||||
getSelectableProperties('user').concat(options?.includeRole ? ['globalRole'] : []),
|
||||
);
|
||||
return pick(user, userProperties.concat(options?.includeRole ? ['role'] : []));
|
||||
}
|
||||
|
||||
export function clean(user: User, options?: { includeRole: boolean }): Partial<User>;
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from './workflows.service';
|
||||
import { WorkflowService } from '@/workflows/workflow.service';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { WorkflowHistoryService } from '@/workflows/workflowHistory/workflowHistory.service.ee';
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository';
|
||||
import { TagRepository } from '@/databases/repositories/tag.repository';
|
||||
|
@ -31,7 +30,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository
|
|||
|
||||
export = {
|
||||
createWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Create, res: express.Response): Promise<express.Response> => {
|
||||
const workflow = req.body;
|
||||
|
||||
|
@ -42,9 +41,7 @@ export = {
|
|||
|
||||
addNodeIds(workflow);
|
||||
|
||||
const role = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
const createdWorkflow = await createWorkflow(workflow, req.user, role);
|
||||
const createdWorkflow = await createWorkflow(workflow, req.user, 'workflow:owner');
|
||||
|
||||
await Container.get(WorkflowHistoryService).saveVersion(
|
||||
req.user,
|
||||
|
@ -59,7 +56,7 @@ export = {
|
|||
},
|
||||
],
|
||||
deleteWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||
const { id: workflowId } = req.params;
|
||||
|
||||
|
@ -74,7 +71,7 @@ export = {
|
|||
},
|
||||
],
|
||||
getWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
|
||||
|
@ -95,7 +92,7 @@ export = {
|
|||
},
|
||||
],
|
||||
getWorkflows: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
validCursor,
|
||||
async (req: WorkflowRequest.GetAll, res: express.Response): Promise<express.Response> => {
|
||||
const { offset = 0, limit = 100, active = undefined, tags = undefined } = req.query;
|
||||
|
@ -104,7 +101,7 @@ export = {
|
|||
...(active !== undefined && { active }),
|
||||
};
|
||||
|
||||
if (['owner', 'admin'].includes(req.user.globalRole.name)) {
|
||||
if (['global:owner', 'global:admin'].includes(req.user.role)) {
|
||||
if (tags) {
|
||||
const workflowIds = await Container.get(TagRepository).getWorkflowIdsViaTags(
|
||||
parseTagNames(tags),
|
||||
|
@ -159,7 +156,7 @@ export = {
|
|||
},
|
||||
],
|
||||
updateWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Update, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
const updateData = new WorkflowEntity();
|
||||
|
@ -221,7 +218,7 @@ export = {
|
|||
},
|
||||
],
|
||||
activateWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
|
||||
|
@ -255,7 +252,7 @@ export = {
|
|||
},
|
||||
],
|
||||
deactivateWorkflow: [
|
||||
authorize(['owner', 'admin', 'member']),
|
||||
authorize(['global:owner', 'global:admin', 'global:member']),
|
||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Container } from 'typedi';
|
||||
import * as Db from '@/Db';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { SharedWorkflow, type WorkflowSharingRole } from '@db/entities/SharedWorkflow';
|
||||
import config from '@/config';
|
||||
import Container from 'typedi';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
|
||||
|
@ -13,7 +12,7 @@ function insertIf(condition: boolean, elements: string[]): string[] {
|
|||
}
|
||||
|
||||
export async function getSharedWorkflowIds(user: User): Promise<string[]> {
|
||||
const where = ['owner', 'admin'].includes(user.globalRole.name) ? {} : { userId: user.id };
|
||||
const where = ['global:owner', 'global:admin'].includes(user.role) ? {} : { userId: user.id };
|
||||
const sharedWorkflows = await Container.get(SharedWorkflowRepository).find({
|
||||
where,
|
||||
select: ['workflowId'],
|
||||
|
@ -27,7 +26,7 @@ export async function getSharedWorkflow(
|
|||
): Promise<SharedWorkflow | null> {
|
||||
return await Container.get(SharedWorkflowRepository).findOne({
|
||||
where: {
|
||||
...(!['owner', 'admin'].includes(user.globalRole.name) && { userId: user.id }),
|
||||
...(!['global:owner', 'global:admin'].includes(user.role) && { userId: user.id }),
|
||||
...(workflowId && { workflowId }),
|
||||
},
|
||||
relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'],
|
||||
|
@ -43,7 +42,7 @@ export async function getWorkflowById(id: string): Promise<WorkflowEntity | null
|
|||
export async function createWorkflow(
|
||||
workflow: WorkflowEntity,
|
||||
user: User,
|
||||
role: Role,
|
||||
role: WorkflowSharingRole,
|
||||
): Promise<WorkflowEntity> {
|
||||
return await Db.transaction(async (transactionManager) => {
|
||||
const newWorkflow = new WorkflowEntity();
|
||||
|
|
|
@ -6,20 +6,18 @@ import { Container } from 'typedi';
|
|||
import type { AuthenticatedRequest, PaginatedRequest } from '../../../types';
|
||||
import { decodeCursor } from '../services/pagination.service';
|
||||
import { License } from '@/License';
|
||||
import type { RoleNames } from '@/databases/entities/Role';
|
||||
import type { GlobalRole } from '@db/entities/User';
|
||||
|
||||
const UNLIMITED_USERS_QUOTA = -1;
|
||||
|
||||
export const authorize =
|
||||
(authorizedRoles: readonly RoleNames[]) =>
|
||||
(authorizedRoles: readonly GlobalRole[]) =>
|
||||
(
|
||||
req: AuthenticatedRequest,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
const { name } = req.user.globalRole;
|
||||
|
||||
if (!authorizedRoles.includes(name)) {
|
||||
if (!authorizedRoles.includes(req.user.role)) {
|
||||
return res.status(403).json({ message: 'Forbidden' });
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,6 @@ import { OrchestrationController } from './controllers/orchestration.controller'
|
|||
import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee';
|
||||
import { InvitationController } from './controllers/invitation.controller';
|
||||
import { CollaborationService } from './collaboration/collaboration.service';
|
||||
import { RoleController } from './controllers/role.controller';
|
||||
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
||||
import { OrchestrationService } from '@/services/orchestration.service';
|
||||
|
||||
|
@ -228,7 +227,6 @@ export class Server extends AbstractServer {
|
|||
VariablesController,
|
||||
InvitationController,
|
||||
VariablesController,
|
||||
RoleController,
|
||||
ActiveWorkflowsController,
|
||||
WorkflowsController,
|
||||
ExecutionsController,
|
||||
|
|
|
@ -5,7 +5,6 @@ import { NodeOperationError, WorkflowOperationError } from 'n8n-workflow';
|
|||
import config from '@/config';
|
||||
import { License } from '@/License';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
|
@ -16,7 +15,6 @@ export class PermissionChecker {
|
|||
private readonly userRepository: UserRepository,
|
||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly ownershipService: OwnershipService,
|
||||
private readonly license: License,
|
||||
) {}
|
||||
|
@ -37,7 +35,6 @@ export class PermissionChecker {
|
|||
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
where: { id: userId },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
if (user.hasGlobalScope('workflow:execute')) return;
|
||||
|
@ -56,12 +53,8 @@ export class PermissionChecker {
|
|||
workflowUserIds = workflowSharings.map((s) => s.userId);
|
||||
}
|
||||
|
||||
const roleId = await this.roleService.findCredentialOwnerRoleId();
|
||||
|
||||
const credentialSharings = await this.sharedCredentialsRepository.findSharings(
|
||||
workflowUserIds,
|
||||
roleId,
|
||||
);
|
||||
const credentialSharings =
|
||||
await this.sharedCredentialsRepository.findOwnedSharings(workflowUserIds);
|
||||
|
||||
const accessibleCredIds = credentialSharings.map((s) => s.credentialsId);
|
||||
|
||||
|
|
|
@ -56,7 +56,6 @@ export const createPasswordSha = (user: User) =>
|
|||
export async function resolveJwtContent(jwtPayload: JwtPayload): Promise<User> {
|
||||
const user = await Container.get(UserRepository).findOne({
|
||||
where: { id: jwtPayload.id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
let passwordHash = null;
|
||||
|
|
|
@ -12,7 +12,7 @@ export const handleEmailLogin = async (
|
|||
): Promise<User | undefined> => {
|
||||
const user = await Container.get(UserRepository).findOne({
|
||||
where: { email },
|
||||
relations: ['globalRole', 'authIdentities'],
|
||||
relations: ['authIdentities'],
|
||||
});
|
||||
|
||||
if (user?.password && (await Container.get(PasswordUtility).compare(password, user.password))) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import { InternalHooks } from '@/InternalHooks';
|
|||
import { LdapService } from '@/Ldap/ldap.service';
|
||||
import {
|
||||
createLdapUserOnLocalDb,
|
||||
getLdapUserRole,
|
||||
getUserByEmail,
|
||||
getAuthIdentityByLdapId,
|
||||
isLdapEnabled,
|
||||
|
@ -50,8 +49,7 @@ export const handleLdapLogin = async (
|
|||
const identity = await createLdapAuthIdentity(emailUser, ldapId);
|
||||
await updateLdapUserOnLocalDb(identity, ldapAttributesValues);
|
||||
} else {
|
||||
const role = await getLdapUserRole();
|
||||
const user = await createLdapUserOnLocalDb(role, ldapAttributesValues, ldapId);
|
||||
const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId);
|
||||
void Container.get(InternalHooks).onUserSignup(user, {
|
||||
user_type: 'ldap',
|
||||
was_disabled_ldap_user: false,
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { DataSourceOptions as ConnectionOptions } from 'typeorm';
|
|||
import { DataSource as Connection } from 'typeorm';
|
||||
import { Container } from 'typedi';
|
||||
import { Logger } from '@/Logger';
|
||||
import { getConnectionOptions } from '@/Db';
|
||||
import { getConnectionOptions, setSchema } from '@/Db';
|
||||
import type { Migration } from '@db/types';
|
||||
import { wrapMigration } from '@db/utils/migrationHelpers';
|
||||
import config from '@/config';
|
||||
|
@ -40,6 +40,7 @@ export class DbRevertMigrationCommand extends Command {
|
|||
|
||||
this.connection = new Connection(connectionOptions);
|
||||
await this.connection.initialize();
|
||||
if (dbType === 'postgresdb') await setSchema(this.connection);
|
||||
await this.connection.undoLastMigration();
|
||||
await this.connection.destroy();
|
||||
}
|
||||
|
|
|
@ -8,13 +8,11 @@ import type { EntityManager } from 'typeorm';
|
|||
import * as Db from '@/Db';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
import type { ICredentialsEncrypted } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UM_FIX_INSTRUCTION } from '@/constants';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
|
||||
|
@ -42,8 +40,6 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
}),
|
||||
};
|
||||
|
||||
private ownerCredentialRole: Role;
|
||||
|
||||
private transactionManager: EntityManager;
|
||||
|
||||
async init() {
|
||||
|
@ -71,7 +67,6 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
let totalImported = 0;
|
||||
|
||||
const cipher = Container.get(Cipher);
|
||||
await this.initOwnerCredentialRole();
|
||||
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
|
||||
|
||||
if (flags.separate) {
|
||||
|
@ -145,16 +140,6 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
);
|
||||
}
|
||||
|
||||
private async initOwnerCredentialRole() {
|
||||
const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
if (!ownerCredentialRole) {
|
||||
throw new ApplicationError(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
this.ownerCredentialRole = ownerCredentialRole;
|
||||
}
|
||||
|
||||
private async storeCredential(credential: Partial<CredentialsEntity>, user: User) {
|
||||
if (!credential.nodesAccess) {
|
||||
credential.nodesAccess = [];
|
||||
|
@ -165,19 +150,14 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
{
|
||||
credentialsId: result.identifiers[0].id as string,
|
||||
userId: user.id,
|
||||
roleId: this.ownerCredentialRole.id,
|
||||
role: 'credential:owner',
|
||||
},
|
||||
['credentialsId', 'userId'],
|
||||
);
|
||||
}
|
||||
|
||||
private async getOwner() {
|
||||
const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||
|
||||
const owner =
|
||||
ownerGlobalRole &&
|
||||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole.id }));
|
||||
|
||||
const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
|
||||
if (!owner) {
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import { generateNanoId } from '@db/utils/generators';
|
|||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import type { IWorkflowToImport } from '@/Interfaces';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { ImportService } from '@/services/import.service';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
|
||||
|
@ -138,12 +137,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
|||
}
|
||||
|
||||
private async getOwner() {
|
||||
const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||
|
||||
const owner =
|
||||
ownerGlobalRole &&
|
||||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole?.id }));
|
||||
|
||||
const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
|
||||
if (!owner) {
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { SettingsRepository } from '@db/repositories/settings.repository';
|
|||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
|
||||
const defaultUserProps = {
|
||||
|
@ -14,6 +13,7 @@ const defaultUserProps = {
|
|||
lastName: null,
|
||||
email: null,
|
||||
password: null,
|
||||
role: 'global:owner',
|
||||
};
|
||||
|
||||
export class Reset extends BaseCommand {
|
||||
|
@ -24,14 +24,8 @@ export class Reset extends BaseCommand {
|
|||
async run(): Promise<void> {
|
||||
const owner = await this.getInstanceOwner();
|
||||
|
||||
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
const credentialOwnerRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
await Container.get(SharedWorkflowRepository).makeOwnerOfAllWorkflows(owner, workflowOwnerRole);
|
||||
await Container.get(SharedCredentialsRepository).makeOwnerOfAllCredentials(
|
||||
owner,
|
||||
credentialOwnerRole,
|
||||
);
|
||||
await Container.get(SharedWorkflowRepository).makeOwnerOfAllWorkflows(owner);
|
||||
await Container.get(SharedCredentialsRepository).makeOwnerOfAllCredentials(owner);
|
||||
|
||||
await Container.get(UserRepository).deleteAllExcept(owner);
|
||||
await Container.get(UserRepository).save(Object.assign(owner, defaultUserProps));
|
||||
|
@ -45,7 +39,7 @@ export class Reset extends BaseCommand {
|
|||
Container.get(SharedCredentialsRepository).create({
|
||||
credentials,
|
||||
user: owner,
|
||||
role: credentialOwnerRole,
|
||||
role: 'credential:owner',
|
||||
}),
|
||||
);
|
||||
await Container.get(SharedCredentialsRepository).save(newSharedCredentials);
|
||||
|
@ -59,19 +53,17 @@ export class Reset extends BaseCommand {
|
|||
}
|
||||
|
||||
async getInstanceOwner(): Promise<User> {
|
||||
const globalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||
|
||||
const owner = await Container.get(UserRepository).findOneBy({ globalRoleId: globalRole.id });
|
||||
const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
|
||||
|
||||
if (owner) return owner;
|
||||
|
||||
const user = new User();
|
||||
|
||||
Object.assign(user, { ...defaultUserProps, globalRole });
|
||||
Object.assign(user, defaultUserProps);
|
||||
|
||||
await Container.get(UserRepository).save(user);
|
||||
|
||||
return await Container.get(UserRepository).findOneByOrFail({ globalRoleId: globalRole.id });
|
||||
return await Container.get(UserRepository).findOneByOrFail({ role: 'global:owner' });
|
||||
}
|
||||
|
||||
async catch(error: Error): Promise<void> {
|
||||
|
|
|
@ -55,7 +55,7 @@ export class AuthController {
|
|||
const preliminaryUser = await handleEmailLogin(email, password);
|
||||
// if the user is an owner, continue with the login
|
||||
if (
|
||||
preliminaryUser?.globalRole?.name === 'owner' ||
|
||||
preliminaryUser?.role === 'global:owner' ||
|
||||
preliminaryUser?.settings?.allowSSOManualLogin
|
||||
) {
|
||||
user = preliminaryUser;
|
||||
|
@ -65,7 +65,7 @@ export class AuthController {
|
|||
}
|
||||
} else if (isLdapCurrentAuthenticationMethod()) {
|
||||
const preliminaryUser = await handleEmailLogin(email, password);
|
||||
if (preliminaryUser?.globalRole?.name === 'owner') {
|
||||
if (preliminaryUser?.role === 'global:owner') {
|
||||
user = preliminaryUser;
|
||||
usedAuthenticationMethod = 'email';
|
||||
} else {
|
||||
|
@ -138,7 +138,7 @@ export class AuthController {
|
|||
}
|
||||
|
||||
try {
|
||||
user = await this.userRepository.findOneOrFail({ where: {}, relations: ['globalRole'] });
|
||||
user = await this.userRepository.findOneOrFail({ where: {} });
|
||||
} catch (error) {
|
||||
throw new InternalServerError(
|
||||
'No users found in database - did you wipe the users table? Create at least one user.',
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Request } from 'express';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { RoleRepository } from '@db/repositories/role.repository';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
|
@ -39,7 +37,6 @@ const tablesToTruncate = [
|
|||
'installed_packages',
|
||||
'installed_nodes',
|
||||
'user',
|
||||
'role',
|
||||
'variables',
|
||||
];
|
||||
|
||||
|
@ -87,7 +84,6 @@ export class E2EController {
|
|||
|
||||
constructor(
|
||||
license: License,
|
||||
private readonly roleRepo: RoleRepository,
|
||||
private readonly settingsRepo: SettingsRepository,
|
||||
private readonly userRepo: UserRepository,
|
||||
private readonly workflowRunner: ActiveWorkflowRunner,
|
||||
|
@ -148,7 +144,7 @@ export class E2EController {
|
|||
private async truncateAll() {
|
||||
for (const table of tablesToTruncate) {
|
||||
try {
|
||||
const { connection } = this.roleRepo.manager;
|
||||
const { connection } = this.settingsRepo.manager;
|
||||
await connection.query(
|
||||
`DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`,
|
||||
);
|
||||
|
@ -163,27 +159,12 @@ export class E2EController {
|
|||
members: UserSetupPayload[],
|
||||
admin: UserSetupPayload,
|
||||
) {
|
||||
const roles: Array<[Role['name'], Role['scope']]> = [
|
||||
['owner', 'global'],
|
||||
['member', 'global'],
|
||||
['admin', 'global'],
|
||||
['owner', 'workflow'],
|
||||
['owner', 'credential'],
|
||||
['user', 'credential'],
|
||||
['editor', 'workflow'],
|
||||
];
|
||||
|
||||
const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }, { id: globalAdminRoleId }] =
|
||||
await this.roleRepo.save(
|
||||
roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })),
|
||||
);
|
||||
|
||||
const instanceOwner = {
|
||||
const instanceOwner = this.userRepo.create({
|
||||
id: uuid(),
|
||||
...owner,
|
||||
password: await this.passwordUtility.hash(owner.password),
|
||||
globalRoleId: globalOwnerRoleId,
|
||||
};
|
||||
role: 'global:owner',
|
||||
});
|
||||
|
||||
if (owner?.mfaSecret && owner.mfaRecoveryCodes?.length) {
|
||||
const { encryptedRecoveryCodes, encryptedSecret } =
|
||||
|
@ -192,12 +173,12 @@ export class E2EController {
|
|||
instanceOwner.mfaRecoveryCodes = encryptedRecoveryCodes;
|
||||
}
|
||||
|
||||
const adminUser = {
|
||||
const adminUser = this.userRepo.create({
|
||||
id: uuid(),
|
||||
...admin,
|
||||
password: await this.passwordUtility.hash(admin.password),
|
||||
globalRoleId: globalAdminRoleId,
|
||||
};
|
||||
role: 'global:admin',
|
||||
});
|
||||
|
||||
const users = [];
|
||||
|
||||
|
@ -209,7 +190,7 @@ export class E2EController {
|
|||
id: uuid(),
|
||||
...payload,
|
||||
password: await this.passwordUtility.hash(password),
|
||||
globalRoleId: globalMemberRoleId,
|
||||
role: 'global:member',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Response } from 'express';
|
||||
import validator from 'validator';
|
||||
|
||||
import config from '@/config';
|
||||
import { Authorized, NoAuthRequired, Post, RequireGlobalScope, RestController } from '@/decorators';
|
||||
|
@ -12,12 +13,11 @@ import { isSamlLicensedAndEnabled } from '@/sso/saml/samlHelpers';
|
|||
import { PasswordUtility } from '@/services/password.utility';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
import validator from 'validator';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
@Authorized()
|
||||
@RestController('/invitations')
|
||||
|
@ -91,13 +91,13 @@ export class InvitationController {
|
|||
);
|
||||
}
|
||||
|
||||
if (invite.role && !['member', 'admin'].includes(invite.role)) {
|
||||
if (invite.role && !['global:member', 'global:admin'].includes(invite.role)) {
|
||||
throw new BadRequestError(
|
||||
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'member' or 'admin'.`,
|
||||
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'global:member' or 'global:admin'.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (invite.role === 'admin' && !this.license.isAdvancedPermissionsLicensed()) {
|
||||
if (invite.role === 'global:admin' && !this.license.isAdvancedPermissionsLicensed()) {
|
||||
throw new UnauthorizedError(
|
||||
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
|
||||
);
|
||||
|
@ -106,7 +106,7 @@ export class InvitationController {
|
|||
|
||||
const attributes = req.body.map(({ email, role }) => ({
|
||||
email,
|
||||
role: role ?? 'member',
|
||||
role: role ?? 'global:member',
|
||||
}));
|
||||
|
||||
const { usersInvited, usersCreated } = await this.userService.inviteUsers(req.user, attributes);
|
||||
|
|
|
@ -80,7 +80,6 @@ export class MeController {
|
|||
await this.userService.update(userId, payload);
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
where: { id: userId },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
this.logger.info('User updated successfully', { userId });
|
||||
|
@ -235,7 +234,6 @@ export class MeController {
|
|||
const user = await this.userRepository.findOneOrFail({
|
||||
select: ['settings'],
|
||||
where: { id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
return user.settings;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
@Authorized(['global', 'owner'])
|
||||
@Authorized('global:owner')
|
||||
@RestController('/owner')
|
||||
export class OwnerController {
|
||||
constructor(
|
||||
|
@ -35,7 +35,7 @@ export class OwnerController {
|
|||
@Post('/setup')
|
||||
async setupOwner(req: OwnerRequest.Post, res: Response) {
|
||||
const { email, firstName, lastName, password } = req.body;
|
||||
const { id: userId, globalRole } = req.user;
|
||||
const { id: userId } = req.user;
|
||||
|
||||
if (config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
||||
this.logger.debug(
|
||||
|
@ -65,17 +65,6 @@ export class OwnerController {
|
|||
throw new BadRequestError('First and last names are mandatory');
|
||||
}
|
||||
|
||||
// TODO: This check should be in a middleware outside this class
|
||||
if (globalRole.scope === 'global' && globalRole.name !== 'owner') {
|
||||
this.logger.debug(
|
||||
'Request to claim instance ownership failed because user shell does not exist or has wrong role!',
|
||||
{
|
||||
userId,
|
||||
},
|
||||
);
|
||||
throw new BadRequestError('Invalid request');
|
||||
}
|
||||
|
||||
let owner = req.user;
|
||||
|
||||
Object.assign(owner, {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import { License } from '@/License';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
|
||||
@RestController('/roles')
|
||||
export class RoleController {
|
||||
constructor(
|
||||
private readonly roleService: RoleService,
|
||||
private readonly license: License,
|
||||
) {}
|
||||
|
||||
@Get('/')
|
||||
async listRoles() {
|
||||
return this.roleService.listRoles().map((role) => {
|
||||
if (role.scope === 'global' && role.name === 'admin') {
|
||||
return { ...role, isAvailable: this.license.isAdvancedPermissionsLicensed() };
|
||||
}
|
||||
|
||||
return { ...role, isAvailable: true };
|
||||
});
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.
|
|||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserService } from '@/services/user.service';
|
||||
import { listQueryMiddleware } from '@/middlewares';
|
||||
import { Logger } from '@/Logger';
|
||||
|
@ -45,7 +44,6 @@ export class UsersController {
|
|||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
|
@ -70,7 +68,7 @@ export class UsersController {
|
|||
}
|
||||
|
||||
if (filter?.isOwner) {
|
||||
for (const user of publicUsers) delete user.globalRole;
|
||||
for (const user of publicUsers) delete user.role;
|
||||
}
|
||||
|
||||
// remove computed fields (unselectable)
|
||||
|
@ -92,12 +90,7 @@ export class UsersController {
|
|||
async listUsers(req: ListQuery.Request) {
|
||||
const { listQueryOptions } = req;
|
||||
|
||||
const globalOwner = await this.roleService.findGlobalOwnerRole();
|
||||
|
||||
const findManyOptions = await this.userRepository.toFindManyOptions(
|
||||
listQueryOptions,
|
||||
globalOwner.id,
|
||||
);
|
||||
const findManyOptions = await this.userRepository.toFindManyOptions(listQueryOptions);
|
||||
|
||||
const users = await this.userRepository.find(findManyOptions);
|
||||
|
||||
|
@ -118,7 +111,6 @@ export class UsersController {
|
|||
async getUserPasswordResetLink(req: UserRequest.PasswordResetLink) {
|
||||
const user = await this.userRepository.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
if (!user) {
|
||||
throw new NotFoundError('User not found');
|
||||
|
@ -140,7 +132,6 @@ export class UsersController {
|
|||
const user = await this.userRepository.findOneOrFail({
|
||||
select: ['settings'],
|
||||
where: { id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
return user.settings;
|
||||
|
@ -194,11 +185,6 @@ export class UsersController {
|
|||
telemetryData.migration_user_id = transferId;
|
||||
}
|
||||
|
||||
const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([
|
||||
this.roleService.findWorkflowOwnerRole(),
|
||||
this.roleService.findCredentialOwnerRole(),
|
||||
]);
|
||||
|
||||
if (transferId) {
|
||||
const transferee = users.find((user) => user.id === transferId);
|
||||
|
||||
|
@ -208,7 +194,7 @@ export class UsersController {
|
|||
.getRepository(SharedWorkflow)
|
||||
.find({
|
||||
select: ['workflowId'],
|
||||
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'workflow:owner' },
|
||||
})
|
||||
.then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId));
|
||||
|
||||
|
@ -223,7 +209,7 @@ export class UsersController {
|
|||
// Transfer ownership of owned workflows
|
||||
await transactionManager.update(
|
||||
SharedWorkflow,
|
||||
{ user: userToDelete, role: workflowOwnerRole },
|
||||
{ user: userToDelete, role: 'workflow:owner' },
|
||||
{ user: transferee },
|
||||
);
|
||||
|
||||
|
@ -234,7 +220,7 @@ export class UsersController {
|
|||
.getRepository(SharedCredentials)
|
||||
.find({
|
||||
select: ['credentialsId'],
|
||||
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'credential:owner' },
|
||||
})
|
||||
.then((sharedCredentials) => sharedCredentials.map(({ credentialsId }) => credentialsId));
|
||||
|
||||
|
@ -249,7 +235,7 @@ export class UsersController {
|
|||
// Transfer ownership of owned credentials
|
||||
await transactionManager.update(
|
||||
SharedCredentials,
|
||||
{ user: userToDelete, role: credentialOwnerRole },
|
||||
{ user: userToDelete, role: 'credential:owner' },
|
||||
{ user: transferee },
|
||||
);
|
||||
|
||||
|
@ -271,11 +257,11 @@ export class UsersController {
|
|||
const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([
|
||||
this.sharedWorkflowRepository.find({
|
||||
relations: ['workflow'],
|
||||
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'workflow:owner' },
|
||||
}),
|
||||
this.sharedCredentialsRepository.find({
|
||||
relations: ['credentials'],
|
||||
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
|
||||
where: { userId: userToDelete.id, role: 'credential:owner' },
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -318,23 +304,20 @@ export class UsersController {
|
|||
|
||||
const targetUser = await this.userRepository.findOne({
|
||||
where: { id: req.params.id },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
if (targetUser === null) {
|
||||
throw new NotFoundError(NO_USER);
|
||||
}
|
||||
|
||||
if (req.user.globalRole.name === 'admin' && targetUser.globalRole.name === 'owner') {
|
||||
if (req.user.role === 'global:admin' && targetUser.role === 'global:owner') {
|
||||
throw new UnauthorizedError(NO_ADMIN_ON_OWNER);
|
||||
}
|
||||
|
||||
if (req.user.globalRole.name === 'owner' && targetUser.globalRole.name === 'owner') {
|
||||
if (req.user.role === 'global:owner' && targetUser.role === 'global:owner') {
|
||||
throw new UnauthorizedError(NO_OWNER_ON_OWNER);
|
||||
}
|
||||
|
||||
const roleToSet = await this.roleService.findCached('global', payload.newRoleName);
|
||||
|
||||
await this.userService.update(targetUser.id, { globalRoleId: roleToSet.id });
|
||||
await this.userService.update(targetUser.id, { role: payload.newRoleName });
|
||||
|
||||
void this.internalHooks.onUserRoleChange({
|
||||
user: req.user,
|
||||
|
|
|
@ -45,7 +45,7 @@ EECredentialsController.get(
|
|||
|
||||
let credential = await Container.get(CredentialsRepository).findOne({
|
||||
where: { id: credentialId },
|
||||
relations: ['shared', 'shared.role', 'shared.user'],
|
||||
relations: ['shared', 'shared.user'],
|
||||
});
|
||||
|
||||
if (!credential) {
|
||||
|
@ -62,7 +62,7 @@ EECredentialsController.get(
|
|||
|
||||
credential = Container.get(OwnershipService).addOwnedByAndSharedWith(credential);
|
||||
|
||||
if (!includeDecryptedData || !userSharing || userSharing.role.name !== 'owner') {
|
||||
if (!includeDecryptedData || !userSharing || userSharing.role !== 'credential:owner') {
|
||||
const { data: _, ...rest } = credential;
|
||||
return { ...rest };
|
||||
}
|
||||
|
@ -151,10 +151,9 @@ EECredentialsController.put(
|
|||
const ownerIds = (
|
||||
await EECredentials.getSharings(Db.getConnection().createEntityManager(), credentialId, [
|
||||
'shared',
|
||||
'shared.role',
|
||||
])
|
||||
)
|
||||
.filter((e) => e.role.name === 'owner')
|
||||
.filter((e) => e.role === 'credential:owner')
|
||||
.map((e) => e.userId);
|
||||
|
||||
let amountRemoved: number | null = null;
|
||||
|
|
|
@ -147,7 +147,7 @@ credentialsController.patch(
|
|||
allowGlobalScope: true,
|
||||
globalScope: 'credential:update',
|
||||
},
|
||||
['credentials', 'role'],
|
||||
['credentials'],
|
||||
);
|
||||
|
||||
if (!sharing) {
|
||||
|
@ -163,7 +163,7 @@ credentialsController.patch(
|
|||
);
|
||||
}
|
||||
|
||||
if (sharing.role.name !== 'owner' && !req.user.hasGlobalScope('credential:update')) {
|
||||
if (sharing.role !== 'credential:owner' && !req.user.hasGlobalScope('credential:update')) {
|
||||
Container.get(Logger).info(
|
||||
'Attempt to update credential blocked due to lack of permissions',
|
||||
{
|
||||
|
@ -216,7 +216,7 @@ credentialsController.delete(
|
|||
allowGlobalScope: true,
|
||||
globalScope: 'credential:delete',
|
||||
},
|
||||
['credentials', 'role'],
|
||||
['credentials'],
|
||||
);
|
||||
|
||||
if (!sharing) {
|
||||
|
@ -232,7 +232,7 @@ credentialsController.delete(
|
|||
);
|
||||
}
|
||||
|
||||
if (sharing.role.name !== 'owner' && !req.user.hasGlobalScope('credential:delete')) {
|
||||
if (sharing.role !== 'credential:owner' && !req.user.hasGlobalScope('credential:delete')) {
|
||||
Container.get(Logger).info(
|
||||
'Attempt to delete credential blocked due to lack of permissions',
|
||||
{
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Container } from 'typedi';
|
||||
import type { EntityManager, FindOptionsWhere } from 'typeorm';
|
||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import type { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { CredentialsService, type CredentialsGetSharedOptions } from './credentials.service';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import Container from 'typedi';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
|
||||
|
@ -15,10 +14,9 @@ export class EECredentialsService extends CredentialsService {
|
|||
): Promise<{ ownsCredential: boolean; credential?: CredentialsEntity }> {
|
||||
const sharing = await this.getSharing(user, credentialId, { allowGlobalScope: false }, [
|
||||
'credentials',
|
||||
'role',
|
||||
]);
|
||||
|
||||
if (!sharing || sharing.role.name !== 'owner') return { ownsCredential: false };
|
||||
if (!sharing || sharing.role !== 'credential:owner') return { ownsCredential: false };
|
||||
|
||||
const { credentials: credential } = sharing;
|
||||
|
||||
|
@ -67,7 +65,6 @@ export class EECredentialsService extends CredentialsService {
|
|||
shareWithIds: string[],
|
||||
): Promise<SharedCredentials[]> {
|
||||
const users = await Container.get(UserRepository).getByIds(transaction, shareWithIds);
|
||||
const role = await Container.get(RoleService).findCredentialUserRole();
|
||||
|
||||
const newSharedCredentials = users
|
||||
.filter((user) => !user.isPending)
|
||||
|
@ -75,7 +72,7 @@ export class EECredentialsService extends CredentialsService {
|
|||
Container.get(SharedCredentialsRepository).create({
|
||||
credentialsId: credential.id,
|
||||
userId: user.id,
|
||||
roleId: role?.id,
|
||||
role: 'credential:user',
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import { ExternalHooks } from '@/ExternalHooks';
|
|||
import type { User } from '@db/entities/User';
|
||||
import type { CredentialRequest, ListQuery } from '@/requests';
|
||||
import { CredentialTypes } from '@/CredentialTypes';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import { Logger } from '@/Logger';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
|
@ -85,13 +84,8 @@ export class CredentialsService {
|
|||
// global credential permissions. This allows the user to
|
||||
// access credentials they don't own.
|
||||
if (!options.allowGlobalScope || !user.hasGlobalScope(options.globalScope)) {
|
||||
Object.assign(where, {
|
||||
userId: user.id,
|
||||
role: { name: 'owner' },
|
||||
});
|
||||
if (!relations.includes('role')) {
|
||||
relations.push('role');
|
||||
}
|
||||
where.userId = user.id;
|
||||
where.role = 'credential:owner';
|
||||
}
|
||||
|
||||
return await Container.get(SharedCredentialsRepository).findOne({ where, relations });
|
||||
|
@ -194,8 +188,6 @@ export class CredentialsService {
|
|||
|
||||
await Container.get(ExternalHooks).run('credentials.create', [encryptedData]);
|
||||
|
||||
const role = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
const result = await Db.transaction(async (transactionManager) => {
|
||||
const savedCredential = await transactionManager.save<CredentialsEntity>(newCredential);
|
||||
|
||||
|
@ -204,7 +196,7 @@ export class CredentialsService {
|
|||
const newSharedCredential = new SharedCredentials();
|
||||
|
||||
Object.assign(newSharedCredential, {
|
||||
role,
|
||||
role: 'credential:owner',
|
||||
user,
|
||||
credentials: savedCredential,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { TableForeignKeyOptions, TableIndexOptions, QueryRunner } from 'typeorm';
|
||||
import { Table, TableColumn } from 'typeorm';
|
||||
import { Table, TableColumn, TableForeignKey } from 'typeorm';
|
||||
import LazyPromise from 'p-lazy';
|
||||
import { Column } from './Column';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
@ -118,6 +118,42 @@ export class DropColumns extends TableOperation {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class ForeignKeyOperation extends TableOperation {
|
||||
protected foreignKey: TableForeignKey;
|
||||
|
||||
constructor(
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
[referencedTableName, referencedColumnName]: [string, string],
|
||||
prefix: string,
|
||||
queryRunner: QueryRunner,
|
||||
customConstraintName?: string,
|
||||
) {
|
||||
super(tableName, prefix, queryRunner);
|
||||
|
||||
this.foreignKey = new TableForeignKey({
|
||||
name: customConstraintName,
|
||||
columnNames: [columnName],
|
||||
referencedTableName: `${prefix}${referencedTableName}`,
|
||||
referencedColumnNames: [referencedColumnName],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AddForeignKey extends ForeignKeyOperation {
|
||||
async execute(queryRunner: QueryRunner) {
|
||||
const { tableName, prefix } = this;
|
||||
return await queryRunner.createForeignKey(`${prefix}${tableName}`, this.foreignKey);
|
||||
}
|
||||
}
|
||||
|
||||
export class DropForeignKey extends ForeignKeyOperation {
|
||||
async execute(queryRunner: QueryRunner) {
|
||||
const { tableName, prefix } = this;
|
||||
return await queryRunner.dropForeignKey(`${prefix}${tableName}`, this.foreignKey);
|
||||
}
|
||||
}
|
||||
|
||||
class ModifyNotNull extends TableOperation {
|
||||
constructor(
|
||||
tableName: string,
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import type { QueryRunner } from 'typeorm';
|
||||
import { Column } from './Column';
|
||||
import { AddColumns, AddNotNull, CreateTable, DropColumns, DropNotNull, DropTable } from './Table';
|
||||
import {
|
||||
AddColumns,
|
||||
AddForeignKey,
|
||||
AddNotNull,
|
||||
CreateTable,
|
||||
DropColumns,
|
||||
DropForeignKey,
|
||||
DropNotNull,
|
||||
DropTable,
|
||||
} from './Table';
|
||||
import { CreateIndex, DropIndex } from './Indices';
|
||||
|
||||
export const createSchemaBuilder = (tablePrefix: string, queryRunner: QueryRunner) => ({
|
||||
|
@ -26,6 +35,36 @@ export const createSchemaBuilder = (tablePrefix: string, queryRunner: QueryRunne
|
|||
dropIndex: (tableName: string, columnNames: string[], customIndexName?: string) =>
|
||||
new DropIndex(tableName, columnNames, tablePrefix, queryRunner, customIndexName),
|
||||
|
||||
addForeignKey: (
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
reference: [string, string],
|
||||
customConstraintName?: string,
|
||||
) =>
|
||||
new AddForeignKey(
|
||||
tableName,
|
||||
columnName,
|
||||
reference,
|
||||
tablePrefix,
|
||||
queryRunner,
|
||||
customConstraintName,
|
||||
),
|
||||
|
||||
dropForeignKey: (
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
reference: [string, string],
|
||||
customConstraintName?: string,
|
||||
) =>
|
||||
new DropForeignKey(
|
||||
tableName,
|
||||
columnName,
|
||||
reference,
|
||||
tablePrefix,
|
||||
queryRunner,
|
||||
customConstraintName,
|
||||
),
|
||||
|
||||
addNotNull: (tableName: string, columnName: string) =>
|
||||
new AddNotNull(tableName, columnName, tablePrefix, queryRunner),
|
||||
dropNotNull: (tableName: string, columnName: string) =>
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import { Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm';
|
||||
import { IsString, Length } from 'class-validator';
|
||||
|
||||
import type { User } from './User';
|
||||
import type { SharedWorkflow } from './SharedWorkflow';
|
||||
import type { SharedCredentials } from './SharedCredentials';
|
||||
import { WithTimestamps } from './AbstractEntity';
|
||||
import { idStringifier } from '../utils/transformers';
|
||||
|
||||
export type RoleNames = 'owner' | 'member' | 'user' | 'editor' | 'admin';
|
||||
export type RoleScopes = 'global' | 'workflow' | 'credential';
|
||||
|
||||
@Entity()
|
||||
@Unique(['scope', 'name'])
|
||||
export class Role extends WithTimestamps {
|
||||
@PrimaryColumn({ transformer: idStringifier })
|
||||
id: string;
|
||||
|
||||
@Column({ length: 32 })
|
||||
@IsString({ message: 'Role name must be of type string.' })
|
||||
@Length(1, 32, { message: 'Role name must be 1 to 32 characters long.' })
|
||||
name: RoleNames;
|
||||
|
||||
@Column()
|
||||
scope: RoleScopes;
|
||||
|
||||
@OneToMany('User', 'globalRole')
|
||||
globalForUsers: User[];
|
||||
|
||||
@OneToMany('SharedWorkflow', 'role')
|
||||
sharedWorkflows: SharedWorkflow[];
|
||||
|
||||
@OneToMany('SharedCredentials', 'role')
|
||||
sharedCredentials: SharedCredentials[];
|
||||
|
||||
get cacheKey() {
|
||||
return `role:${this.scope}:${this.name}`;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { CredentialsEntity } from './CredentialsEntity';
|
||||
import { User } from './User';
|
||||
import { Role } from './Role';
|
||||
import { WithTimestamps } from './AbstractEntity';
|
||||
|
||||
export type CredentialSharingRole = 'credential:owner' | 'credential:user';
|
||||
|
||||
@Entity()
|
||||
export class SharedCredentials extends WithTimestamps {
|
||||
@ManyToOne('Role', 'sharedCredentials', { nullable: false })
|
||||
role: Role;
|
||||
|
||||
@Column()
|
||||
roleId: string;
|
||||
role: CredentialSharingRole;
|
||||
|
||||
@ManyToOne('User', 'sharedCredentials')
|
||||
user: User;
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { WorkflowEntity } from './WorkflowEntity';
|
||||
import { User } from './User';
|
||||
import { Role } from './Role';
|
||||
import { WithTimestamps } from './AbstractEntity';
|
||||
|
||||
export type WorkflowSharingRole = 'workflow:owner' | 'workflow:editor' | 'workflow:user';
|
||||
|
||||
@Entity()
|
||||
export class SharedWorkflow extends WithTimestamps {
|
||||
@ManyToOne('Role', 'sharedWorkflows', { nullable: false })
|
||||
role: Role;
|
||||
|
||||
@Column()
|
||||
roleId: string;
|
||||
role: WorkflowSharingRole;
|
||||
|
||||
@ManyToOne('User', 'sharedWorkflows')
|
||||
user: User;
|
||||
|
|
|
@ -6,13 +6,11 @@ import {
|
|||
Entity,
|
||||
Index,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
BeforeInsert,
|
||||
} from 'typeorm';
|
||||
import { IsEmail, IsString, Length } from 'class-validator';
|
||||
import type { IUser, IUserSettings } from 'n8n-workflow';
|
||||
import { Role } from './Role';
|
||||
import type { SharedWorkflow } from './SharedWorkflow';
|
||||
import type { SharedCredentials } from './SharedCredentials';
|
||||
import { NoXss } from '../utils/customValidators';
|
||||
|
@ -23,10 +21,13 @@ import type { AuthIdentity } from './AuthIdentity';
|
|||
import { ownerPermissions, memberPermissions, adminPermissions } from '@/permissions/roles';
|
||||
import { hasScope, type ScopeOptions, type Scope } from '@n8n/permissions';
|
||||
|
||||
const STATIC_SCOPE_MAP: Record<string, Scope[]> = {
|
||||
owner: ownerPermissions,
|
||||
member: memberPermissions,
|
||||
admin: adminPermissions,
|
||||
export type GlobalRole = 'global:owner' | 'global:admin' | 'global:member';
|
||||
export type AssignableRole = Exclude<GlobalRole, 'global:owner'>;
|
||||
|
||||
const STATIC_SCOPE_MAP: Record<GlobalRole, Scope[]> = {
|
||||
'global:owner': ownerPermissions,
|
||||
'global:member': memberPermissions,
|
||||
'global:admin': adminPermissions,
|
||||
};
|
||||
|
||||
@Entity()
|
||||
|
@ -72,11 +73,8 @@ export class User extends WithTimestamps implements IUser {
|
|||
})
|
||||
settings: IUserSettings | null;
|
||||
|
||||
@ManyToOne('Role', 'globalForUsers', { nullable: false })
|
||||
globalRole: Role;
|
||||
|
||||
@Column()
|
||||
globalRoleId: string;
|
||||
role: GlobalRole;
|
||||
|
||||
@OneToMany('AuthIdentity', 'user')
|
||||
authIdentities: AuthIdentity[];
|
||||
|
@ -127,11 +125,11 @@ export class User extends WithTimestamps implements IUser {
|
|||
|
||||
@AfterLoad()
|
||||
computeIsOwner(): void {
|
||||
this.isOwner = this.globalRole?.name === 'owner';
|
||||
this.isOwner = this.role === 'global:owner';
|
||||
}
|
||||
|
||||
get globalScopes() {
|
||||
return STATIC_SCOPE_MAP[this.globalRole?.name] ?? [];
|
||||
return STATIC_SCOPE_MAP[this.role] ?? [];
|
||||
}
|
||||
|
||||
hasGlobalScope(scope: Scope | Scope[], scopeOptions?: ScopeOptions): boolean {
|
||||
|
|
|
@ -6,7 +6,6 @@ import { EventDestinations } from './EventDestinations';
|
|||
import { ExecutionEntity } from './ExecutionEntity';
|
||||
import { InstalledNodes } from './InstalledNodes';
|
||||
import { InstalledPackages } from './InstalledPackages';
|
||||
import { Role } from './Role';
|
||||
import { Settings } from './Settings';
|
||||
import { SharedCredentials } from './SharedCredentials';
|
||||
import { SharedWorkflow } from './SharedWorkflow';
|
||||
|
@ -29,7 +28,6 @@ export const entities = {
|
|||
ExecutionEntity,
|
||||
InstalledNodes,
|
||||
InstalledPackages,
|
||||
Role,
|
||||
Settings,
|
||||
SharedCredentials,
|
||||
SharedWorkflow,
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import type { MigrationContext, ReversibleMigration } from '@db/types';
|
||||
|
||||
type Table = 'user' | 'shared_workflow' | 'shared_credentials';
|
||||
|
||||
const idColumns: Record<Table, string> = {
|
||||
user: 'id',
|
||||
shared_credentials: 'credentialsId',
|
||||
shared_workflow: 'workflowId',
|
||||
};
|
||||
|
||||
const roleScopes: Record<Table, string> = {
|
||||
user: 'global',
|
||||
shared_credentials: 'credential',
|
||||
shared_workflow: 'workflow',
|
||||
};
|
||||
|
||||
const foreignKeySuffixes: Record<Table, string> = {
|
||||
user: 'f0609be844f9200ff4365b1bb3d',
|
||||
shared_credentials: 'c68e056637562000b68f480815a',
|
||||
shared_workflow: '3540da03964527aa24ae014b780',
|
||||
};
|
||||
|
||||
export class DropRoleMapping1705429061930 implements ReversibleMigration {
|
||||
async up(context: MigrationContext) {
|
||||
await this.migrateUp('user', context);
|
||||
await this.migrateUp('shared_workflow', context);
|
||||
await this.migrateUp('shared_credentials', context);
|
||||
}
|
||||
|
||||
async down(context: MigrationContext) {
|
||||
await this.migrateDown('shared_workflow', context);
|
||||
await this.migrateDown('shared_credentials', context);
|
||||
await this.migrateDown('user', context);
|
||||
}
|
||||
|
||||
private async migrateUp(
|
||||
table: Table,
|
||||
{
|
||||
dbType,
|
||||
escape,
|
||||
runQuery,
|
||||
schemaBuilder: { addNotNull, addColumns, dropColumns, dropForeignKey, column },
|
||||
tablePrefix,
|
||||
}: MigrationContext,
|
||||
) {
|
||||
await addColumns(table, [column('role').text]);
|
||||
|
||||
const roleTable = escape.tableName('role');
|
||||
const tableName = escape.tableName(table);
|
||||
const idColumn = escape.columnName(idColumns[table]);
|
||||
const roleColumnName = table === 'user' ? 'globalRoleId' : 'roleId';
|
||||
const roleColumn = escape.columnName(roleColumnName);
|
||||
const scope = roleScopes[table];
|
||||
const isMySQL = ['mariadb', 'mysqldb'].includes(dbType);
|
||||
const roleField = isMySQL ? `CONCAT('${scope}:', R.name)` : `'${scope}:' || R.name`;
|
||||
const subQuery = `
|
||||
SELECT ${roleField} as role, T.${idColumn} as id
|
||||
FROM ${tableName} T
|
||||
LEFT JOIN ${roleTable} R
|
||||
ON T.${roleColumn} = R.id and R.scope = '${scope}'`;
|
||||
const swQuery = isMySQL
|
||||
? `UPDATE ${tableName}, (${subQuery}) as mapping
|
||||
SET ${tableName}.role = mapping.role
|
||||
WHERE ${tableName}.${idColumn} = mapping.id`
|
||||
: `UPDATE ${tableName}
|
||||
SET role = mapping.role
|
||||
FROM (${subQuery}) as mapping
|
||||
WHERE ${tableName}.${idColumn} = mapping.id`;
|
||||
await runQuery(swQuery);
|
||||
|
||||
await addNotNull(table, 'role');
|
||||
|
||||
await dropForeignKey(
|
||||
table,
|
||||
roleColumnName,
|
||||
['role', 'id'],
|
||||
`FK_${tablePrefix}${foreignKeySuffixes[table]}`,
|
||||
);
|
||||
await dropColumns(table, [roleColumnName]);
|
||||
}
|
||||
|
||||
private async migrateDown(
|
||||
table: Table,
|
||||
{
|
||||
dbType,
|
||||
escape,
|
||||
runQuery,
|
||||
schemaBuilder: { addNotNull, addColumns, dropColumns, addForeignKey, column },
|
||||
tablePrefix,
|
||||
}: MigrationContext,
|
||||
) {
|
||||
const roleColumnName = table === 'user' ? 'globalRoleId' : 'roleId';
|
||||
await addColumns(table, [column(roleColumnName).int]);
|
||||
|
||||
const roleTable = escape.tableName('role');
|
||||
const tableName = escape.tableName(table);
|
||||
const idColumn = escape.columnName(idColumns[table]);
|
||||
const roleColumn = escape.columnName(roleColumnName);
|
||||
const scope = roleScopes[table];
|
||||
const isMySQL = ['mariadb', 'mysqldb'].includes(dbType);
|
||||
const roleField = isMySQL ? `CONCAT('${scope}:', R.name)` : `'${scope}:' || R.name`;
|
||||
const subQuery = `
|
||||
SELECT R.id as role_id, T.${idColumn} as id
|
||||
FROM ${tableName} T
|
||||
LEFT JOIN ${roleTable} R
|
||||
ON T.role = ${roleField} and R.scope = '${scope}'`;
|
||||
const query = isMySQL
|
||||
? `UPDATE ${tableName}, (${subQuery}) as mapping
|
||||
SET ${tableName}.${roleColumn} = mapping.role_id
|
||||
WHERE ${tableName}.${idColumn} = mapping.id`
|
||||
: `UPDATE ${tableName}
|
||||
SET ${roleColumn} = mapping.role_id
|
||||
FROM (${subQuery}) as mapping
|
||||
WHERE ${tableName}.${idColumn} = mapping.id`;
|
||||
await runQuery(query);
|
||||
|
||||
await addNotNull(table, roleColumnName);
|
||||
await addForeignKey(
|
||||
table,
|
||||
roleColumnName,
|
||||
['role', 'id'],
|
||||
`FK_${tablePrefix}${foreignKeySuffixes[table]}`,
|
||||
);
|
||||
|
||||
await dropColumns(table, ['role']);
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ import { ExecutionSoftDelete1693491613982 } from '../common/1693491613982-Execut
|
|||
import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata';
|
||||
import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections';
|
||||
import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole';
|
||||
import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping';
|
||||
|
||||
export const mysqlMigrations: Migration[] = [
|
||||
InitialMigration1588157391238,
|
||||
|
@ -105,4 +106,5 @@ export const mysqlMigrations: Migration[] = [
|
|||
AddWorkflowMetadata1695128658538,
|
||||
ModifyWorkflowHistoryNodesAndConnections1695829275184,
|
||||
AddGlobalAdminRole1700571993961,
|
||||
DropRoleMapping1705429061930,
|
||||
];
|
||||
|
|
|
@ -50,6 +50,7 @@ import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWor
|
|||
import { MigrateToTimestampTz1694091729095 } from './1694091729095-MigrateToTimestampTz';
|
||||
import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections';
|
||||
import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole';
|
||||
import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping';
|
||||
|
||||
export const postgresMigrations: Migration[] = [
|
||||
InitialMigration1587669153312,
|
||||
|
@ -103,4 +104,5 @@ export const postgresMigrations: Migration[] = [
|
|||
MigrateToTimestampTz1694091729095,
|
||||
ModifyWorkflowHistoryNodesAndConnections1695829275184,
|
||||
AddGlobalAdminRole1700571993961,
|
||||
DropRoleMapping1705429061930,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { DropRoleMapping1705429061930 as BaseMigration } from '../common/1705429061930-DropRoleMapping';
|
||||
|
||||
export class DropRoleMapping1705429061930 extends BaseMigration {
|
||||
transaction = false as const;
|
||||
}
|
|
@ -48,6 +48,7 @@ import { ExecutionSoftDelete1693491613982 } from './1693491613982-ExecutionSoftD
|
|||
import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata';
|
||||
import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections';
|
||||
import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole';
|
||||
import { DropRoleMapping1705429061930 } from './1705429061930-DropRoleMapping';
|
||||
|
||||
const sqliteMigrations: Migration[] = [
|
||||
InitialMigration1588102412422,
|
||||
|
@ -99,6 +100,7 @@ const sqliteMigrations: Migration[] = [
|
|||
AddWorkflowMetadata1695128658538,
|
||||
ModifyWorkflowHistoryNodesAndConnections1695829275184,
|
||||
AddGlobalAdminRole1700571993961,
|
||||
DropRoleMapping1705429061930,
|
||||
];
|
||||
|
||||
export { sqliteMigrations };
|
||||
|
|
|
@ -45,7 +45,7 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
|
|||
|
||||
type Select = Array<keyof CredentialsEntity>;
|
||||
|
||||
const defaultRelations = ['shared', 'shared.role', 'shared.user'];
|
||||
const defaultRelations = ['shared', 'shared.user'];
|
||||
const defaultSelect: Select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'];
|
||||
|
||||
if (!listQueryOptions) return { select: defaultSelect, relations: defaultRelations };
|
||||
|
@ -81,7 +81,7 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
|
|||
const findManyOptions: FindManyOptions<CredentialsEntity> = { where: { id: In(ids) } };
|
||||
|
||||
if (withSharings) {
|
||||
findManyOptions.relations = ['shared', 'shared.user', 'shared.role'];
|
||||
findManyOptions.relations = ['shared', 'shared.user'];
|
||||
}
|
||||
|
||||
return await this.find(findManyOptions);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { Service } from 'typedi';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
import type { RoleNames, RoleScopes } from '../entities/Role';
|
||||
import { Role } from '../entities/Role';
|
||||
import { User } from '../entities/User';
|
||||
|
||||
@Service()
|
||||
export class RoleRepository extends Repository<Role> {
|
||||
constructor(dataSource: DataSource) {
|
||||
super(Role, dataSource.manager);
|
||||
}
|
||||
|
||||
async findRole(scope: RoleScopes, name: RoleNames) {
|
||||
return await this.findOne({ where: { scope, name } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of users in each role, e.g. `{ admin: 2, member: 6, owner: 1 }`
|
||||
*/
|
||||
async countUsersByRole() {
|
||||
type Row = { role_name: string; count: number | string };
|
||||
|
||||
const rows: Row[] = await this.createQueryBuilder('role')
|
||||
.select('role.name')
|
||||
.addSelect('COUNT(user.id)', 'count')
|
||||
.innerJoin(User, 'user', 'role.id = user.globalRoleId')
|
||||
.groupBy('role.name')
|
||||
.getRawMany();
|
||||
|
||||
return rows.reduce<Record<string, number>>((acc, item) => {
|
||||
acc[item.role_name] = typeof item.count === 'number' ? item.count : parseInt(item.count, 10);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async getIdsInScopeWorkflowByNames(roleNames: RoleNames[]) {
|
||||
return await this.find({
|
||||
select: ['id'],
|
||||
where: { name: In(roleNames), scope: 'workflow' },
|
||||
}).then((role) => role.map(({ id }) => id));
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import { Service } from 'typedi';
|
||||
import type { EntityManager, FindOptionsWhere } from 'typeorm';
|
||||
import type { EntityManager } from 'typeorm';
|
||||
import { DataSource, In, Not, Repository } from 'typeorm';
|
||||
import { SharedCredentials } from '../entities/SharedCredentials';
|
||||
import type { User } from '../entities/User';
|
||||
import type { Role } from '../entities/Role';
|
||||
|
||||
@Service()
|
||||
export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
||||
|
@ -26,15 +25,15 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
|||
|
||||
async findByCredentialIds(credentialIds: string[]) {
|
||||
return await this.find({
|
||||
relations: ['credentials', 'role', 'user'],
|
||||
relations: ['credentials', 'user'],
|
||||
where: {
|
||||
credentialsId: In(credentialIds),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async makeOwnerOfAllCredentials(user: User, role: Role) {
|
||||
return await this.update({ userId: Not(user.id), roleId: role.id }, { user });
|
||||
async makeOwnerOfAllCredentials(user: User) {
|
||||
return await this.update({ userId: Not(user.id), role: 'credential:owner' }, { user });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,23 +41,22 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
|||
*/
|
||||
async getAccessibleCredentials(userId: string) {
|
||||
const sharings = await this.find({
|
||||
relations: ['role'],
|
||||
where: {
|
||||
userId,
|
||||
role: { name: In(['owner', 'user']), scope: 'credential' },
|
||||
role: In(['credential:owner', 'credential:user']),
|
||||
},
|
||||
});
|
||||
|
||||
return sharings.map((s) => s.credentialsId);
|
||||
}
|
||||
|
||||
async findSharings(userIds: string[], roleId?: string) {
|
||||
const where: FindOptionsWhere<SharedCredentials> = { userId: In(userIds) };
|
||||
|
||||
// If credential sharing is not enabled, get only credentials owned by this user
|
||||
if (roleId) where.roleId = roleId;
|
||||
|
||||
return await this.find({ where });
|
||||
async findOwnedSharings(userIds: string[]) {
|
||||
return await this.find({
|
||||
where: {
|
||||
userId: In(userIds),
|
||||
role: 'credential:owner',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteByIds(transaction: EntityManager, sharedCredentialsIds: string[], user?: User) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Service } from 'typedi';
|
||||
import { DataSource, Repository, In, Not } from 'typeorm';
|
||||
import type { EntityManager, FindOptionsSelect, FindOptionsWhere } from 'typeorm';
|
||||
import { SharedWorkflow } from '../entities/SharedWorkflow';
|
||||
import type { EntityManager, FindManyOptions, FindOptionsWhere } from 'typeorm';
|
||||
import { SharedWorkflow, type WorkflowSharingRole } from '../entities/SharedWorkflow';
|
||||
import { type User } from '../entities/User';
|
||||
import type { Scope } from '@n8n/permissions';
|
||||
import type { Role } from '../entities/Role';
|
||||
import type { WorkflowEntity } from '../entities/WorkflowEntity';
|
||||
|
||||
@Service()
|
||||
|
@ -35,22 +34,29 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||
|
||||
async findByWorkflowIds(workflowIds: string[]) {
|
||||
return await this.find({
|
||||
relations: ['role', 'user'],
|
||||
relations: ['user'],
|
||||
where: {
|
||||
role: {
|
||||
name: 'owner',
|
||||
scope: 'workflow',
|
||||
},
|
||||
role: 'workflow:owner',
|
||||
workflowId: In(workflowIds),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findSharingRole(
|
||||
userId: string,
|
||||
workflowId: string,
|
||||
): Promise<WorkflowSharingRole | undefined> {
|
||||
return await this.findOne({
|
||||
select: ['role'],
|
||||
where: { workflowId, userId },
|
||||
}).then((shared) => shared?.role);
|
||||
}
|
||||
|
||||
async findSharing(
|
||||
workflowId: string,
|
||||
user: User,
|
||||
scope: Scope,
|
||||
{ roles, extraRelations }: { roles?: string[]; extraRelations?: string[] } = {},
|
||||
{ roles, extraRelations }: { roles?: WorkflowSharingRole[]; extraRelations?: string[] } = {},
|
||||
) {
|
||||
const where: FindOptionsWhere<SharedWorkflow> = {
|
||||
workflow: { id: workflowId },
|
||||
|
@ -61,18 +67,18 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||
}
|
||||
|
||||
if (roles) {
|
||||
where.role = { name: In(roles) };
|
||||
where.role = In(roles);
|
||||
}
|
||||
|
||||
const relations = ['workflow', 'role'];
|
||||
const relations = ['workflow'];
|
||||
|
||||
if (extraRelations) relations.push(...extraRelations);
|
||||
|
||||
return await this.findOne({ relations, where });
|
||||
}
|
||||
|
||||
async makeOwnerOfAllWorkflows(user: User, role: Role) {
|
||||
return await this.update({ userId: Not(user.id), roleId: role.id }, { user });
|
||||
async makeOwnerOfAllWorkflows(user: User) {
|
||||
return await this.update({ userId: Not(user.id), role: 'workflow:owner' }, { user });
|
||||
}
|
||||
|
||||
async getSharing(
|
||||
|
@ -102,14 +108,14 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||
): Promise<SharedWorkflow[]> {
|
||||
return await this.find({
|
||||
where: {
|
||||
...(!['owner', 'admin'].includes(user.globalRole.name) && { userId: user.id }),
|
||||
...(!['global:owner', 'global:admin'].includes(user.role) && { userId: user.id }),
|
||||
...(options.workflowIds && { workflowId: In(options.workflowIds) }),
|
||||
},
|
||||
...(options.relations && { relations: options.relations }),
|
||||
});
|
||||
}
|
||||
|
||||
async share(transaction: EntityManager, workflow: WorkflowEntity, users: User[], roleId: string) {
|
||||
async share(transaction: EntityManager, workflow: WorkflowEntity, users: User[]) {
|
||||
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
|
||||
if (user.isPending) {
|
||||
return acc;
|
||||
|
@ -117,7 +123,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||
const entity: Partial<SharedWorkflow> = {
|
||||
workflowId: workflow.id,
|
||||
userId: user.id,
|
||||
roleId,
|
||||
role: 'workflow:editor',
|
||||
};
|
||||
acc.push(this.create(entity));
|
||||
return acc;
|
||||
|
@ -126,12 +132,15 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||
return await transaction.save(newSharedWorkflows);
|
||||
}
|
||||
|
||||
async findWithFields(workflowIds: string[], { fields }: { fields: string[] }) {
|
||||
async findWithFields(
|
||||
workflowIds: string[],
|
||||
{ select }: Pick<FindManyOptions<SharedWorkflow>, 'select'>,
|
||||
) {
|
||||
return await this.find({
|
||||
where: {
|
||||
workflowId: In(workflowIds),
|
||||
},
|
||||
select: fields as FindOptionsSelect<SharedWorkflow>,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Service } from 'typedi';
|
||||
import type { EntityManager, FindManyOptions } from 'typeorm';
|
||||
import { DataSource, In, IsNull, Not, Repository } from 'typeorm';
|
||||
import { User } from '../entities/User';
|
||||
import type { ListQuery } from '@/requests';
|
||||
|
||||
import { type GlobalRole, User } from '../entities/User';
|
||||
@Service()
|
||||
export class UserRepository extends Repository<User> {
|
||||
constructor(dataSource: DataSource) {
|
||||
|
@ -13,7 +13,6 @@ export class UserRepository extends Repository<User> {
|
|||
async findManyByIds(userIds: string[]) {
|
||||
return await this.find({
|
||||
where: { id: In(userIds) },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -28,7 +27,6 @@ export class UserRepository extends Repository<User> {
|
|||
async findManyByEmail(emails: string[]) {
|
||||
return await this.find({
|
||||
where: { email: In(emails) },
|
||||
relations: ['globalRole'],
|
||||
select: ['email', 'password', 'id'],
|
||||
});
|
||||
}
|
||||
|
@ -43,15 +41,30 @@ export class UserRepository extends Repository<User> {
|
|||
email,
|
||||
password: Not(IsNull()),
|
||||
},
|
||||
relations: ['authIdentities', 'globalRole'],
|
||||
relations: ['authIdentities'],
|
||||
});
|
||||
}
|
||||
|
||||
async toFindManyOptions(listQueryOptions?: ListQuery.Options, globalOwnerRoleId?: string) {
|
||||
/** Counts the number of users in each role, e.g. `{ admin: 2, member: 6, owner: 1 }` */
|
||||
async countUsersByRole() {
|
||||
const rows = (await this.createQueryBuilder()
|
||||
.select(['role', 'COUNT(role) as count'])
|
||||
.groupBy('role')
|
||||
.execute()) as Array<{ role: GlobalRole; count: string }>;
|
||||
return rows.reduce(
|
||||
(acc, row) => {
|
||||
acc[row.role] = parseInt(row.count, 10);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<GlobalRole, number>,
|
||||
);
|
||||
}
|
||||
|
||||
async toFindManyOptions(listQueryOptions?: ListQuery.Options) {
|
||||
const findManyOptions: FindManyOptions<User> = {};
|
||||
|
||||
if (!listQueryOptions) {
|
||||
findManyOptions.relations = ['globalRole', 'authIdentities'];
|
||||
findManyOptions.relations = ['authIdentities'];
|
||||
return findManyOptions;
|
||||
}
|
||||
|
||||
|
@ -62,7 +75,7 @@ export class UserRepository extends Repository<User> {
|
|||
if (skip) findManyOptions.skip = skip;
|
||||
|
||||
if (take && !select) {
|
||||
findManyOptions.relations = ['globalRole', 'authIdentities'];
|
||||
findManyOptions.relations = ['authIdentities'];
|
||||
}
|
||||
|
||||
if (take && select && !select?.id) {
|
||||
|
@ -74,11 +87,8 @@ export class UserRepository extends Repository<User> {
|
|||
|
||||
findManyOptions.where = otherFilters;
|
||||
|
||||
if (isOwner !== undefined && globalOwnerRoleId) {
|
||||
findManyOptions.relations = ['globalRole'];
|
||||
findManyOptions.where.globalRole = {
|
||||
id: isOwner ? globalOwnerRoleId : Not(globalOwnerRoleId),
|
||||
};
|
||||
if (isOwner !== undefined) {
|
||||
findManyOptions.where.role = isOwner ? 'global:owner' : Not('global:owner');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
async getAllActive() {
|
||||
return await this.find({
|
||||
where: { active: true },
|
||||
relations: ['shared', 'shared.user', 'shared.user.globalRole', 'shared.role'],
|
||||
relations: ['shared', 'shared.user'],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
async findById(workflowId: string) {
|
||||
return await this.findOne({
|
||||
where: { id: workflowId },
|
||||
relations: ['shared', 'shared.user', 'shared.user.globalRole', 'shared.role'],
|
||||
relations: ['shared', 'shared.user'],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
createdAt: true,
|
||||
updatedAt: true,
|
||||
versionId: true,
|
||||
shared: { userId: true, roleId: true },
|
||||
shared: { userId: true, role: true },
|
||||
};
|
||||
|
||||
delete select?.ownedBy; // remove non-entity field, handled after query
|
||||
|
@ -152,7 +152,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
select.tags = { id: true, name: true };
|
||||
}
|
||||
|
||||
if (isOwnedByIncluded) relations.push('shared', 'shared.role', 'shared.user');
|
||||
if (isOwnedByIncluded) relations.push('shared', 'shared.user');
|
||||
|
||||
if (typeof where.name === 'string' && where.name !== '') {
|
||||
where.name = Like(`%${where.name}%`);
|
||||
|
|
|
@ -5,7 +5,6 @@ import { StatisticsNames, WorkflowStatistics } from '../entities/WorkflowStatist
|
|||
import type { User } from '@/databases/entities/User';
|
||||
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
||||
import { Role } from '@/databases/entities/Role';
|
||||
|
||||
type StatisticsInsertResult = 'insert' | 'failed' | 'alreadyExists';
|
||||
type StatisticsUpsertResult = StatisticsInsertResult | 'update';
|
||||
|
@ -110,12 +109,11 @@ export class WorkflowStatisticsRepository extends Repository<WorkflowStatistics>
|
|||
'shared_workflow',
|
||||
'shared_workflow.workflowId = workflow_statistics.workflowId',
|
||||
)
|
||||
.innerJoin(Role, 'role', 'role.id = shared_workflow.roleId')
|
||||
.where('shared_workflow.userId = :userId', { userId })
|
||||
.andWhere('workflow.active = :isActive', { isActive: true })
|
||||
.andWhere('workflow_statistics.name = :name', { name: StatisticsNames.productionSuccess })
|
||||
.andWhere('workflow_statistics.count >= 5')
|
||||
.andWhere('role.name = :roleName', { roleName: 'owner' })
|
||||
.andWhere('role = :roleName', { roleName: 'workflow:owner' })
|
||||
.getCount();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,9 +36,7 @@ export const createAuthMiddleware =
|
|||
|
||||
if (!user) return res.status(401).json({ status: 'error', message: 'Unauthorized' });
|
||||
|
||||
const { globalRole } = user;
|
||||
if (authRole === 'any' || (globalRole.scope === authRole[0] && globalRole.name === authRole[1]))
|
||||
return next();
|
||||
if (authRole === 'any' || authRole === user.role) return next();
|
||||
|
||||
res.status(403).json({ status: 'error', message: 'Unauthorized' });
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import type { Request, Response, RequestHandler } from 'express';
|
||||
import type { RoleNames, RoleScopes } from '@db/entities/Role';
|
||||
import type { GlobalRole } from '@db/entities/User';
|
||||
import type { BooleanLicenseFeature } from '@/Interfaces';
|
||||
import type { Scope } from '@n8n/permissions';
|
||||
|
||||
export type Method = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
||||
|
||||
export type AuthRole = [RoleScopes, RoleNames] | 'any' | 'none';
|
||||
export type AuthRole = GlobalRole | 'any' | 'none';
|
||||
export type AuthRoleMetadata = Record<string, AuthRole>;
|
||||
|
||||
export type LicenseMetadata = Record<string, BooleanLicenseFeature[]>;
|
||||
|
|
|
@ -23,12 +23,10 @@ import { isUniqueConstraintError } from '@/ResponseHelper';
|
|||
import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
|
||||
import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlHelper.ee';
|
||||
import type { SourceControlledFile } from './types/sourceControlledFile';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { VariablesService } from '../variables/variables.service.ee';
|
||||
import { TagRepository } from '@db/repositories/tag.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { UM_FIX_INSTRUCTION } from '@/constants';
|
||||
import { Logger } from '@/Logger';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
|
@ -59,36 +57,6 @@ export class SourceControlImportService {
|
|||
);
|
||||
}
|
||||
|
||||
private async getOwnerGlobalRole() {
|
||||
const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||
|
||||
if (!globalOwnerRole) {
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return globalOwnerRole;
|
||||
}
|
||||
|
||||
private async getCredentialOwnerRole() {
|
||||
const credentialOwnerRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
if (!credentialOwnerRole) {
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return credentialOwnerRole;
|
||||
}
|
||||
|
||||
private async getWorkflowOwnerRole() {
|
||||
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) {
|
||||
throw new ApplicationError(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return workflowOwnerRole;
|
||||
}
|
||||
|
||||
public async getRemoteVersionIdsFromFiles(): Promise<SourceControlWorkflowVersionId[]> {
|
||||
const remoteWorkflowFiles = await glob('*.json', {
|
||||
cwd: this.workflowExportFolder,
|
||||
|
@ -222,7 +190,6 @@ export class SourceControlImportService {
|
|||
}
|
||||
|
||||
public async importWorkflowFromWorkFolder(candidates: SourceControlledFile[], userId: string) {
|
||||
const ownerWorkflowRole = await this.getWorkflowOwnerRole();
|
||||
const workflowRunner = this.activeWorkflowRunner;
|
||||
const candidateIds = candidates.map((c) => c.id);
|
||||
const existingWorkflows = await Container.get(WorkflowRepository).findByIds(candidateIds, {
|
||||
|
@ -230,7 +197,7 @@ export class SourceControlImportService {
|
|||
});
|
||||
const allSharedWorkflows = await Container.get(SharedWorkflowRepository).findWithFields(
|
||||
candidateIds,
|
||||
{ fields: ['workflowId', 'roleId', 'userId'] },
|
||||
{ select: ['workflowId', 'role', 'userId'] },
|
||||
);
|
||||
const cachedOwnerIds = new Map<string, string>();
|
||||
const importWorkflowsResult = await Promise.all(
|
||||
|
@ -273,35 +240,29 @@ export class SourceControlImportService {
|
|||
}
|
||||
|
||||
const existingSharedWorkflowOwnerByRoleId = allSharedWorkflows.find(
|
||||
(e) =>
|
||||
e.workflowId === importedWorkflow.id &&
|
||||
e.roleId.toString() === ownerWorkflowRole.id.toString(),
|
||||
(e) => e.workflowId === importedWorkflow.id && e.role === 'workflow:owner',
|
||||
);
|
||||
const existingSharedWorkflowOwnerByUserId = allSharedWorkflows.find(
|
||||
(e) =>
|
||||
e.workflowId === importedWorkflow.id &&
|
||||
e.roleId.toString() === workflowOwnerId.toString(),
|
||||
(e) => e.workflowId === importedWorkflow.id && e.role === 'workflow:owner',
|
||||
);
|
||||
if (!existingSharedWorkflowOwnerByUserId && !existingSharedWorkflowOwnerByRoleId) {
|
||||
// no owner exists yet, so create one
|
||||
await Container.get(SharedWorkflowRepository).insert({
|
||||
workflowId: importedWorkflow.id,
|
||||
userId: workflowOwnerId,
|
||||
roleId: ownerWorkflowRole.id,
|
||||
role: 'workflow:owner',
|
||||
});
|
||||
} else if (existingSharedWorkflowOwnerByRoleId) {
|
||||
// skip, because the workflow already has a global owner
|
||||
} else if (existingSharedWorkflowOwnerByUserId && !existingSharedWorkflowOwnerByRoleId) {
|
||||
// if the worklflow has a non-global owner that is referenced by the owner file,
|
||||
// if the workflow has a non-global owner that is referenced by the owner file,
|
||||
// and no existing global owner, update the owner to the user referenced in the owner file
|
||||
await Container.get(SharedWorkflowRepository).update(
|
||||
{
|
||||
workflowId: importedWorkflow.id,
|
||||
userId: workflowOwnerId,
|
||||
},
|
||||
{
|
||||
roleId: ownerWorkflowRole.id,
|
||||
},
|
||||
{ role: 'workflow:owner' },
|
||||
);
|
||||
}
|
||||
if (existingWorkflow?.active) {
|
||||
|
@ -343,13 +304,11 @@ export class SourceControlImportService {
|
|||
},
|
||||
select: ['id', 'name', 'type', 'data'],
|
||||
});
|
||||
const ownerCredentialRole = await this.getCredentialOwnerRole();
|
||||
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
||||
const existingSharedCredentials = await Container.get(SharedCredentialsRepository).find({
|
||||
select: ['userId', 'credentialsId', 'roleId'],
|
||||
select: ['userId', 'credentialsId', 'role'],
|
||||
where: {
|
||||
credentialsId: In(candidateIds),
|
||||
roleId: In([ownerCredentialRole.id, ownerGlobalRole.id]),
|
||||
role: 'credential:owner',
|
||||
},
|
||||
});
|
||||
let importCredentialsResult: Array<{ id: string; name: string; type: string }> = [];
|
||||
|
@ -382,7 +341,7 @@ export class SourceControlImportService {
|
|||
const newSharedCredential = new SharedCredentials();
|
||||
newSharedCredential.credentialsId = newCredentialObject.id as string;
|
||||
newSharedCredential.userId = userId;
|
||||
newSharedCredential.roleId = ownerCredentialRole.id;
|
||||
newSharedCredential.role = 'credential:owner';
|
||||
|
||||
await Container.get(SharedCredentialsRepository).upsert({ ...newSharedCredential }, [
|
||||
'credentialsId',
|
||||
|
|
|
@ -22,7 +22,7 @@ export class EnterpriseExecutionsService {
|
|||
|
||||
if (!execution) return;
|
||||
|
||||
const relations = ['shared', 'shared.user', 'shared.role'];
|
||||
const relations = ['shared', 'shared.user'];
|
||||
|
||||
const workflow = (await this.workflowRepository.get(
|
||||
{ id: execution.workflowId },
|
||||
|
|
|
@ -27,7 +27,7 @@ export class ExecutionsController {
|
|||
private async getAccessibleWorkflowIds(user: User) {
|
||||
return this.license.isSharingEnabled()
|
||||
? await this.workflowSharingService.getSharedWorkflowIds(user)
|
||||
: await this.workflowSharingService.getSharedWorkflowIds(user, ['owner']);
|
||||
: await this.workflowSharingService.getSharedWorkflowIds(user, ['workflow:owner']);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
|
|
|
@ -14,8 +14,7 @@ import type {
|
|||
import { IsBoolean, IsEmail, IsIn, IsOptional, IsString, Length } from 'class-validator';
|
||||
import { NoXss } from '@db/utils/customValidators';
|
||||
import type { PublicUser, SecretsProvider, SecretsProviderState } from '@/Interfaces';
|
||||
import type { Role, RoleNames } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { AssignableRole, type User } from '@db/entities/User';
|
||||
import type { UserManagementMailer } from '@/UserManagement/email';
|
||||
import type { Variables } from '@db/entities/Variables';
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
|
@ -48,8 +47,8 @@ export class UserSettingsUpdatePayload {
|
|||
}
|
||||
|
||||
export class UserRoleChangePayload {
|
||||
@IsIn(['member', 'admin'])
|
||||
newRoleName: Exclude<RoleNames, 'user' | 'editor' | 'owner'>;
|
||||
@IsIn(['global:admin', 'global:member'])
|
||||
newRoleName: AssignableRole;
|
||||
}
|
||||
|
||||
export type AuthlessRequest<
|
||||
|
@ -67,7 +66,6 @@ export type AuthenticatedRequest<
|
|||
> = Omit<express.Request<RouteParams, ResponseBody, RequestBody, RequestQuery>, 'user'> & {
|
||||
user: User;
|
||||
mailer?: UserManagementMailer;
|
||||
globalMemberRole?: Role;
|
||||
};
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -225,7 +223,7 @@ export declare namespace UserRequest {
|
|||
export type Invite = AuthenticatedRequest<
|
||||
{},
|
||||
{},
|
||||
Array<{ email: string; role?: 'member' | 'admin' }>
|
||||
Array<{ email: string; role?: AssignableRole }>
|
||||
>;
|
||||
|
||||
export type InviteResponse = {
|
||||
|
|
|
@ -4,16 +4,13 @@ import { type INode, type INodeCredentialsDetails } from 'n8n-workflow';
|
|||
|
||||
import { Logger } from '@/Logger';
|
||||
import * as Db from '@/Db';
|
||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||
import { TagRepository } from '@/databases/repositories/tag.repository';
|
||||
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { TagRepository } from '@db/repositories/tag.repository';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
||||
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||
import { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
|
||||
|
||||
import type { TagEntity } from '@/databases/entities/TagEntity';
|
||||
import type { Role } from '@/databases/entities/Role';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping';
|
||||
import type { TagEntity } from '@db/entities/TagEntity';
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
|
||||
@Service()
|
||||
|
@ -22,19 +19,15 @@ export class ImportService {
|
|||
|
||||
private dbTags: TagEntity[] = [];
|
||||
|
||||
private workflowOwnerRole: Role;
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly credentialsRepository: CredentialsRepository,
|
||||
private readonly tagRepository: TagRepository,
|
||||
private readonly roleService: RoleService,
|
||||
) {}
|
||||
|
||||
async initRecords() {
|
||||
this.dbCredentials = await this.credentialsRepository.find();
|
||||
this.dbTags = await this.tagRepository.find();
|
||||
this.workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
}
|
||||
|
||||
async importWorkflows(workflows: WorkflowEntity[], userId: string) {
|
||||
|
@ -64,7 +57,7 @@ export class ImportService {
|
|||
|
||||
const workflowId = upsertResult.identifiers.at(0)?.id as string;
|
||||
|
||||
await tx.upsert(SharedWorkflow, { workflowId, userId, roleId: this.workflowOwnerRole.id }, [
|
||||
await tx.upsert(SharedWorkflow, { workflowId, userId, role: 'workflow:owner' }, [
|
||||
'workflowId',
|
||||
'userId',
|
||||
]);
|
||||
|
|
|
@ -2,17 +2,14 @@ import { Service } from 'typedi';
|
|||
import { CacheService } from '@/services/cache/cache.service';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { RoleService } from './role.service';
|
||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class OwnershipService {
|
||||
constructor(
|
||||
private cacheService: CacheService,
|
||||
private userRepository: UserRepository,
|
||||
private roleService: RoleService,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
) {}
|
||||
|
||||
|
@ -27,13 +24,9 @@ export class OwnershipService {
|
|||
|
||||
if (cachedValue) return this.userRepository.create(cachedValue);
|
||||
|
||||
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) throw new ApplicationError('Failed to find workflow owner role');
|
||||
|
||||
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
|
||||
where: { workflowId, roleId: workflowOwnerRole.id },
|
||||
relations: ['user', 'user.globalRole'],
|
||||
where: { workflowId, role: 'workflow:owner' },
|
||||
relations: ['user'],
|
||||
});
|
||||
|
||||
void this.cacheService.setHash('workflow-ownership', { [workflowId]: sharedWorkflow.user });
|
||||
|
@ -61,7 +54,7 @@ export class OwnershipService {
|
|||
shared?.forEach(({ user, role }) => {
|
||||
const { id, email, firstName, lastName } = user;
|
||||
|
||||
if (role.name === 'owner') {
|
||||
if (role === 'credential:owner' || role === 'workflow:owner') {
|
||||
entity.ownedBy = { id, email, firstName, lastName };
|
||||
} else {
|
||||
entity.sharedWith.push({ id, email, firstName, lastName });
|
||||
|
@ -72,11 +65,8 @@ export class OwnershipService {
|
|||
}
|
||||
|
||||
async getInstanceOwner() {
|
||||
const globalOwnerRole = await this.roleService.findGlobalOwnerRole();
|
||||
|
||||
return await this.userRepository.findOneOrFail({
|
||||
where: { globalRoleId: globalOwnerRole.id },
|
||||
relations: ['globalRole'],
|
||||
where: { role: 'global:owner' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
import { Service } from 'typedi';
|
||||
import { RoleRepository } from '@db/repositories/role.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { CacheService } from '@/services/cache/cache.service';
|
||||
import type { RoleNames, RoleScopes } from '@db/entities/Role';
|
||||
import { InvalidRoleError } from '@/errors/invalid-role.error';
|
||||
import { License } from '@/License';
|
||||
|
||||
@Service()
|
||||
export class RoleService {
|
||||
constructor(
|
||||
private roleRepository: RoleRepository,
|
||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private cacheService: CacheService,
|
||||
private readonly license: License,
|
||||
) {
|
||||
void this.populateCache();
|
||||
}
|
||||
|
||||
async populateCache() {
|
||||
const allRoles = await this.roleRepository.find({});
|
||||
|
||||
if (!allRoles) return;
|
||||
|
||||
void this.cacheService.setMany(allRoles.map((r) => [r.cacheKey, r]));
|
||||
}
|
||||
|
||||
async findCached(scope: RoleScopes, name: RoleNames) {
|
||||
const cacheKey = `role:${scope}:${name}`;
|
||||
|
||||
const cachedRole = await this.cacheService.get(cacheKey);
|
||||
|
||||
if (cachedRole) return this.roleRepository.create(cachedRole);
|
||||
|
||||
let dbRole = await this.roleRepository.findRole(scope, name);
|
||||
|
||||
if (dbRole === null) {
|
||||
if (!this.isValid(scope, name)) {
|
||||
throw new InvalidRoleError(`${scope}:${name} is not a valid role`);
|
||||
}
|
||||
|
||||
const toSave = this.roleRepository.create({ scope, name });
|
||||
dbRole = await this.roleRepository.save(toSave);
|
||||
}
|
||||
|
||||
void this.cacheService.set(cacheKey, dbRole);
|
||||
|
||||
return dbRole;
|
||||
}
|
||||
|
||||
private roles: Array<{ name: RoleNames; scope: RoleScopes }> = [
|
||||
{ scope: 'global', name: 'owner' },
|
||||
{ scope: 'global', name: 'member' },
|
||||
{ scope: 'global', name: 'admin' },
|
||||
{ scope: 'workflow', name: 'owner' },
|
||||
{ scope: 'credential', name: 'owner' },
|
||||
{ scope: 'credential', name: 'user' },
|
||||
{ scope: 'workflow', name: 'editor' },
|
||||
];
|
||||
|
||||
listRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
private isValid(scope: RoleScopes, name: RoleNames) {
|
||||
return this.roles.some((r) => r.scope === scope && r.name === name);
|
||||
}
|
||||
|
||||
async findGlobalOwnerRole() {
|
||||
return await this.findCached('global', 'owner');
|
||||
}
|
||||
|
||||
async findGlobalMemberRole() {
|
||||
return await this.findCached('global', 'member');
|
||||
}
|
||||
|
||||
async findGlobalAdminRole() {
|
||||
return await this.findCached('global', 'admin');
|
||||
}
|
||||
|
||||
async findWorkflowOwnerRole() {
|
||||
return await this.findCached('workflow', 'owner');
|
||||
}
|
||||
|
||||
async findWorkflowEditorRole() {
|
||||
return await this.findCached('workflow', 'editor');
|
||||
}
|
||||
|
||||
async findCredentialOwnerRole() {
|
||||
return await this.findCached('credential', 'owner');
|
||||
}
|
||||
|
||||
async findCredentialUserRole() {
|
||||
return await this.findCached('credential', 'user');
|
||||
}
|
||||
|
||||
async findRoleByUserAndWorkflow(userId: string, workflowId: string) {
|
||||
return await this.sharedWorkflowRepository
|
||||
.findOne({
|
||||
where: { workflowId, userId },
|
||||
relations: ['role'],
|
||||
})
|
||||
.then((shared) => shared?.role);
|
||||
}
|
||||
|
||||
async findCredentialOwnerRoleId() {
|
||||
return this.license.isSharingEnabled() ? undefined : (await this.findCredentialOwnerRole()).id;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Container, Service } from 'typedi';
|
||||
import { User } from '@db/entities/User';
|
||||
import { type AssignableRole, User } from '@db/entities/User';
|
||||
import type { IUserSettings } from 'n8n-workflow';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import type { PublicUser } from '@/Interfaces';
|
||||
|
@ -10,7 +10,6 @@ import { Logger } from '@/Logger';
|
|||
import { createPasswordSha } from '@/auth/jwt';
|
||||
import { UserManagementMailer } from '@/UserManagement/email';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UrlService } from '@/services/url.service';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
import type { UserRequest } from '@/requests';
|
||||
|
@ -23,7 +22,6 @@ export class UserService {
|
|||
private readonly userRepository: UserRepository,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly mailer: UserManagementMailer,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly urlService: UrlService,
|
||||
) {}
|
||||
|
||||
|
@ -73,7 +71,7 @@ export class UserService {
|
|||
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: decodedToken.sub },
|
||||
relations: ['authIdentities', 'globalRole'],
|
||||
relations: ['authIdentities'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
|
@ -162,7 +160,7 @@ export class UserService {
|
|||
private async sendEmails(
|
||||
owner: User,
|
||||
toInviteUsers: { [key: string]: string },
|
||||
role: 'member' | 'admin',
|
||||
role: AssignableRole,
|
||||
) {
|
||||
const domain = this.urlService.getInstanceBaseUrl();
|
||||
|
||||
|
@ -224,9 +222,7 @@ export class UserService {
|
|||
);
|
||||
}
|
||||
|
||||
async inviteUsers(owner: User, attributes: Array<{ email: string; role: 'member' | 'admin' }>) {
|
||||
const memberRole = await this.roleService.findGlobalMemberRole();
|
||||
const adminRole = await this.roleService.findGlobalAdminRole();
|
||||
async inviteUsers(owner: User, attributes: Array<{ email: string; role: AssignableRole }>) {
|
||||
const emails = attributes.map(({ email }) => email);
|
||||
|
||||
const existingUsers = await this.userRepository.findManyByEmail(emails);
|
||||
|
@ -250,10 +246,7 @@ export class UserService {
|
|||
async (transactionManager) =>
|
||||
await Promise.all(
|
||||
toCreateUsers.map(async ({ email, role }) => {
|
||||
const newUser = Object.assign(new User(), {
|
||||
email,
|
||||
globalRole: role === 'member' ? memberRole : adminRole,
|
||||
});
|
||||
const newUser = transactionManager.create(User, { email, role });
|
||||
const savedUser = await transactionManager.save<User>(newUser);
|
||||
createdUsers.set(email, savedUser.id);
|
||||
return savedUser;
|
||||
|
|
|
@ -4,7 +4,6 @@ import { In } from 'typeorm';
|
|||
import type { User } from '@db/entities/User';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserService } from '@/services/user.service';
|
||||
|
||||
@Service()
|
||||
|
@ -12,7 +11,6 @@ export class UserOnboardingService {
|
|||
constructor(
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
|
@ -24,12 +22,11 @@ export class UserOnboardingService {
|
|||
let belowThreshold = true;
|
||||
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
|
||||
|
||||
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
const ownedWorkflowsIds = await this.sharedWorkflowRepository
|
||||
.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
roleId: workflowOwnerRole?.id,
|
||||
role: 'workflow:owner',
|
||||
},
|
||||
select: ['workflowId'],
|
||||
})
|
||||
|
|
|
@ -174,7 +174,7 @@ export class SamlService {
|
|||
const lowerCasedEmail = attributes.email.toLowerCase();
|
||||
const user = await Container.get(UserRepository).findOne({
|
||||
where: { email: lowerCasedEmail },
|
||||
relations: ['globalRole', 'authIdentities'],
|
||||
relations: ['authIdentities'],
|
||||
});
|
||||
if (user) {
|
||||
// Login path for existing users that are fully set up and that have a SAML authIdentity set up
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
} from '../ssoHelpers';
|
||||
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
||||
import type { SamlConfiguration } from './types/requests';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
|
@ -104,7 +103,7 @@ export async function createUserFromSamlAttributes(attributes: SamlUserAttribute
|
|||
user.email = lowerCasedEmail;
|
||||
user.firstName = attributes.firstName;
|
||||
user.lastName = attributes.lastName;
|
||||
user.globalRole = await Container.get(RoleService).findGlobalMemberRole();
|
||||
user.role = 'global:member';
|
||||
// generates a password that is not used or known to the user
|
||||
user.password = await Container.get(PasswordUtility).hash(generatePassword());
|
||||
authIdentity.providerId = attributes.userPrincipalName;
|
||||
|
|
|
@ -11,7 +11,7 @@ import { License } from '@/License';
|
|||
import { N8N_VERSION } from '@/constants';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { SourceControlPreferencesService } from '../environments/sourceControl/sourceControlPreferences.service.ee';
|
||||
import { RoleRepository } from '@/databases/repositories/role.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
|
||||
type ExecutionTrackDataKey = 'manual_error' | 'manual_success' | 'prod_error' | 'prod_success';
|
||||
|
||||
|
@ -111,7 +111,7 @@ export class Telemetry {
|
|||
plan_name_current: this.license.getPlanName(),
|
||||
quota: this.license.getTriggerLimit(),
|
||||
usage: await this.workflowRepository.getActiveTriggerCount(),
|
||||
role_count: await Container.get(RoleRepository).countUsersByRole(),
|
||||
role_count: await Container.get(UserRepository).countUsersByRole(),
|
||||
source_control_set_up: Container.get(SourceControlPreferencesService).isSourceControlSetup(),
|
||||
branchName: sourceControlPreferences.branchName,
|
||||
read_only_instance: sourceControlPreferences.branchReadOnly,
|
||||
|
|
|
@ -34,10 +34,10 @@ export class EnterpriseWorkflowService {
|
|||
user,
|
||||
workflowId,
|
||||
{ allowGlobalScope: false },
|
||||
['workflow', 'role'],
|
||||
['workflow'],
|
||||
);
|
||||
|
||||
if (!sharing || sharing.role.name !== 'owner') return { ownsWorkflow: false };
|
||||
if (!sharing || sharing.role !== 'workflow:owner') return { ownsWorkflow: false };
|
||||
|
||||
const { workflow } = sharing;
|
||||
|
||||
|
@ -54,7 +54,7 @@ export class EnterpriseWorkflowService {
|
|||
workflow.shared?.forEach(({ user, role }) => {
|
||||
const { id, email, firstName, lastName } = user;
|
||||
|
||||
if (role.name === 'owner') {
|
||||
if (role === 'workflow:owner') {
|
||||
workflow.ownedBy = { id, email, firstName, lastName };
|
||||
return;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export class EnterpriseWorkflowService {
|
|||
};
|
||||
credential.shared?.forEach(({ user, role }) => {
|
||||
const { id, email, firstName, lastName } = user;
|
||||
if (role.name === 'owner') {
|
||||
if (role === 'credential:owner') {
|
||||
workflowCredential.ownedBy = { id, email, firstName, lastName };
|
||||
} else {
|
||||
workflowCredential.sharedWith?.push({ id, email, firstName, lastName });
|
||||
|
|
|
@ -8,6 +8,7 @@ import { BinaryDataService } from 'n8n-core';
|
|||
import config from '@/config';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import type { WorkflowSharingRole } from '@db/entities/SharedWorkflow';
|
||||
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMapping.repository';
|
||||
|
@ -60,7 +61,7 @@ export class WorkflowService {
|
|||
workflowId: string,
|
||||
tagIds?: string[],
|
||||
forceSave?: boolean,
|
||||
roles?: string[],
|
||||
roles?: WorkflowSharingRole[],
|
||||
): Promise<WorkflowEntity> {
|
||||
const shared = await this.sharedWorkflowRepository.findSharing(
|
||||
workflowId,
|
||||
|
@ -250,7 +251,7 @@ export class WorkflowService {
|
|||
workflowId,
|
||||
user,
|
||||
'workflow:delete',
|
||||
{ roles: ['owner'] },
|
||||
{ roles: ['workflow:owner'] },
|
||||
);
|
||||
|
||||
if (!sharedWorkflow) {
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
import { Service } from 'typedi';
|
||||
import { In, type FindOptionsWhere } from 'typeorm';
|
||||
|
||||
import type { RoleNames } from '@db/entities/Role';
|
||||
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import type { SharedWorkflow, WorkflowSharingRole } from '@db/entities/SharedWorkflow';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { RoleRepository } from '@db/repositories/role.repository';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
|
||||
@Service()
|
||||
export class WorkflowSharingService {
|
||||
constructor(
|
||||
private readonly roleRepository: RoleRepository,
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
) {}
|
||||
constructor(private readonly sharedWorkflowRepository: SharedWorkflowRepository) {}
|
||||
|
||||
/**
|
||||
* Get the IDs of the workflows that have been shared with the user.
|
||||
* Returns all IDs if user has the 'workflow:read' scope.
|
||||
*/
|
||||
async getSharedWorkflowIds(user: User, roleNames?: RoleNames[]): Promise<string[]> {
|
||||
async getSharedWorkflowIds(user: User, roles?: WorkflowSharingRole[]): Promise<string[]> {
|
||||
const where: FindOptionsWhere<SharedWorkflow> = {};
|
||||
if (!user.hasGlobalScope('workflow:read')) {
|
||||
where.userId = user.id;
|
||||
}
|
||||
if (roleNames?.length) {
|
||||
const roleIds = await this.roleRepository.getIdsInScopeWorkflowByNames(roleNames);
|
||||
where.roleId = In(roleIds);
|
||||
if (roles?.length) {
|
||||
where.role = In(roles);
|
||||
}
|
||||
const sharedWorkflows = await this.sharedWorkflowRepository.find({
|
||||
where,
|
||||
|
|
|
@ -10,8 +10,7 @@ import * as WorkflowHelpers from '@/WorkflowHelpers';
|
|||
import type { IWorkflowResponse } from '@/Interfaces';
|
||||
import config from '@/config';
|
||||
import { Authorized, Delete, Get, Patch, Post, Put, RestController } from '@/decorators';
|
||||
import type { RoleNames } from '@db/entities/Role';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { SharedWorkflow, type WorkflowSharingRole } from '@db/entities/SharedWorkflow';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { TagRepository } from '@db/repositories/tag.repository';
|
||||
|
@ -23,7 +22,6 @@ import { ListQuery } from '@/requests';
|
|||
import { WorkflowService } from './workflow.service';
|
||||
import { License } from '@/License';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import * as utils from '@/utils';
|
||||
import { listQueryMiddleware } from '@/middlewares';
|
||||
import { TagService } from '@/services/tag.service';
|
||||
|
@ -53,7 +51,6 @@ export class WorkflowsController {
|
|||
private readonly externalHooks: ExternalHooks,
|
||||
private readonly tagRepository: TagRepository,
|
||||
private readonly enterpriseWorkflowService: EnterpriseWorkflowService,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly workflowHistoryService: WorkflowHistoryService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly namingService: NamingService,
|
||||
|
@ -116,12 +113,10 @@ export class WorkflowsController {
|
|||
await Db.transaction(async (transactionManager) => {
|
||||
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||
|
||||
const role = await this.roleService.findWorkflowOwnerRole();
|
||||
|
||||
const newSharedWorkflow = new SharedWorkflow();
|
||||
|
||||
Object.assign(newSharedWorkflow, {
|
||||
role,
|
||||
role: 'workflow:owner',
|
||||
user: req.user,
|
||||
workflow: savedWorkflow,
|
||||
});
|
||||
|
@ -151,7 +146,9 @@ export class WorkflowsController {
|
|||
@Get('/', { middlewares: listQueryMiddleware })
|
||||
async getAll(req: ListQuery.Request, res: express.Response) {
|
||||
try {
|
||||
const roles: RoleNames[] = this.license.isSharingEnabled() ? [] : ['owner'];
|
||||
const roles: WorkflowSharingRole[] = this.license.isSharingEnabled()
|
||||
? []
|
||||
: ['workflow:owner'];
|
||||
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(
|
||||
req.user,
|
||||
roles,
|
||||
|
@ -223,7 +220,7 @@ export class WorkflowsController {
|
|||
const { id: workflowId } = req.params;
|
||||
|
||||
if (this.license.isSharingEnabled()) {
|
||||
const relations = ['shared', 'shared.user', 'shared.role'];
|
||||
const relations = ['shared', 'shared.user'];
|
||||
if (!config.getEnv('workflowTagsDisabled')) {
|
||||
relations.push('tags');
|
||||
}
|
||||
|
@ -281,7 +278,8 @@ export class WorkflowsController {
|
|||
const { tags, ...rest } = req.body;
|
||||
Object.assign(updateData, rest);
|
||||
|
||||
if (this.license.isSharingEnabled()) {
|
||||
const isSharingEnabled = this.license.isSharingEnabled();
|
||||
if (isSharingEnabled) {
|
||||
updateData = await this.enterpriseWorkflowService.preventTampering(
|
||||
updateData,
|
||||
workflowId,
|
||||
|
@ -294,8 +292,8 @@ export class WorkflowsController {
|
|||
updateData,
|
||||
workflowId,
|
||||
tags,
|
||||
this.license.isSharingEnabled() ? forceSave : true,
|
||||
this.license.isSharingEnabled() ? undefined : ['owner'],
|
||||
isSharingEnabled ? forceSave : true,
|
||||
isSharingEnabled ? undefined : ['workflow:owner'],
|
||||
);
|
||||
|
||||
return updatedWorkflow;
|
||||
|
@ -378,10 +376,10 @@ export class WorkflowsController {
|
|||
await this.workflowRepository.getSharings(
|
||||
Db.getConnection().createEntityManager(),
|
||||
workflowId,
|
||||
['shared', 'shared.role'],
|
||||
['shared'],
|
||||
)
|
||||
)
|
||||
.filter((e) => e.role.name === 'owner')
|
||||
.filter((e) => e.role === 'workflow:owner')
|
||||
.map((e) => e.userId);
|
||||
|
||||
let newShareeIds: string[] = [];
|
||||
|
@ -399,9 +397,7 @@ export class WorkflowsController {
|
|||
|
||||
if (newShareeIds.length) {
|
||||
const users = await this.userRepository.getByIds(trx, newShareeIds);
|
||||
const role = await this.roleService.findWorkflowEditorRole();
|
||||
|
||||
await this.sharedWorkflowRepository.share(trx, workflow!, users, role.id);
|
||||
await this.sharedWorkflowRepository.share(trx, workflow!, users);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,19 +3,15 @@ import { Container } from 'typedi';
|
|||
import validator from 'validator';
|
||||
import config from '@/config';
|
||||
import { AUTH_COOKIE_NAME } from '@/constants';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||
import { randomValidPassword } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUser, createUserShell } from './shared/db/users';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { MfaService } from '@/Mfa/mfa.service';
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
const ownerPassword = randomValidPassword();
|
||||
|
@ -26,8 +22,6 @@ const license = testServer.license;
|
|||
let mfaService: MfaService;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
globalMemberRole = await getGlobalMemberRole();
|
||||
mfaService = Container.get(MfaService);
|
||||
});
|
||||
|
||||
|
@ -41,7 +35,7 @@ describe('POST /login', () => {
|
|||
beforeEach(async () => {
|
||||
owner = await createUser({
|
||||
password: ownerPassword,
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -60,7 +54,7 @@ describe('POST /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
mfaSecret,
|
||||
|
@ -74,9 +68,7 @@ describe('POST /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(mfaRecoveryCodes).toBeUndefined();
|
||||
|
@ -107,7 +99,7 @@ describe('POST /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
mfaRecoveryCodes,
|
||||
mfaSecret,
|
||||
|
@ -120,9 +112,7 @@ describe('POST /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(mfaRecoveryCodes).toBeUndefined();
|
||||
expect(mfaSecret).toBeUndefined();
|
||||
|
@ -149,7 +139,7 @@ describe('POST /login', () => {
|
|||
license.setQuota('quota:users', 0);
|
||||
const ownerUser = await createUser({
|
||||
password: randomValidPassword(),
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
});
|
||||
|
||||
const response = await testServer.authAgentFor(ownerUser).get('/login');
|
||||
|
@ -168,7 +158,7 @@ describe('GET /login', () => {
|
|||
});
|
||||
|
||||
test('should return cookie if UM is disabled and no cookie is already set', async () => {
|
||||
await createUserShell(globalOwnerRole);
|
||||
await createUserShell('global:owner');
|
||||
await utils.setInstanceOwnerSetUp(false);
|
||||
|
||||
const response = await testServer.authlessAgent.get('/login');
|
||||
|
@ -191,7 +181,7 @@ describe('GET /login', () => {
|
|||
});
|
||||
|
||||
test('should return logged-in owner shell', async () => {
|
||||
const ownerShell = await createUserShell(globalOwnerRole);
|
||||
const ownerShell = await createUserShell('global:owner');
|
||||
|
||||
const response = await testServer.authAgentFor(ownerShell).get('/login');
|
||||
|
||||
|
@ -204,7 +194,7 @@ describe('GET /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
} = response.body.data;
|
||||
|
@ -216,9 +206,7 @@ describe('GET /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).toContain('workflow:read');
|
||||
|
@ -228,7 +216,7 @@ describe('GET /login', () => {
|
|||
});
|
||||
|
||||
test('should return logged-in member shell', async () => {
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
|
||||
const response = await testServer.authAgentFor(memberShell).get('/login');
|
||||
|
||||
|
@ -241,7 +229,7 @@ describe('GET /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
} = response.body.data;
|
||||
|
@ -253,9 +241,7 @@ describe('GET /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:member');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).not.toContain('workflow:read');
|
||||
|
@ -265,7 +251,7 @@ describe('GET /login', () => {
|
|||
});
|
||||
|
||||
test('should return logged-in owner', async () => {
|
||||
const owner = await createUser({ globalRole: globalOwnerRole });
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
|
||||
const response = await testServer.authAgentFor(owner).get('/login');
|
||||
|
||||
|
@ -278,7 +264,7 @@ describe('GET /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
} = response.body.data;
|
||||
|
@ -290,9 +276,7 @@ describe('GET /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).toContain('workflow:read');
|
||||
|
@ -302,7 +286,7 @@ describe('GET /login', () => {
|
|||
});
|
||||
|
||||
test('should return logged-in member', async () => {
|
||||
const member = await createUser({ globalRole: globalMemberRole });
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
|
||||
const response = await testServer.authAgentFor(member).get('/login');
|
||||
|
||||
|
@ -315,7 +299,7 @@ describe('GET /login', () => {
|
|||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
} = response.body.data;
|
||||
|
@ -327,9 +311,7 @@ describe('GET /login', () => {
|
|||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:member');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).not.toContain('workflow:read');
|
||||
|
@ -343,13 +325,13 @@ describe('GET /resolve-signup-token', () => {
|
|||
beforeEach(async () => {
|
||||
owner = await createUser({
|
||||
password: ownerPassword,
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
});
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
});
|
||||
|
||||
test('should validate invite token', async () => {
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
|
@ -369,7 +351,7 @@ describe('GET /resolve-signup-token', () => {
|
|||
|
||||
test('should return 403 if user quota reached', async () => {
|
||||
license.setQuota('quota:users', 0);
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
|
@ -380,7 +362,7 @@ describe('GET /resolve-signup-token', () => {
|
|||
});
|
||||
|
||||
test('should fail with invalid inputs', async () => {
|
||||
const { id: inviteeId } = await createUser({ globalRole: globalMemberRole });
|
||||
const { id: inviteeId } = await createUser({ role: 'global:member' });
|
||||
|
||||
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
|
||||
|
||||
|
@ -412,7 +394,7 @@ describe('GET /resolve-signup-token', () => {
|
|||
|
||||
describe('POST /logout', () => {
|
||||
test('should log user out', async () => {
|
||||
const owner = await createUser({ globalRole: globalOwnerRole });
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
|
||||
const response = await testServer.authAgentFor(owner).post('/logout');
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalMemberRole } from './shared/db/roles';
|
||||
import { createUser } from './shared/db/users';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
|
||||
describe('Auth Middleware', () => {
|
||||
|
@ -42,8 +42,7 @@ describe('Auth Middleware', () => {
|
|||
describe('Routes requiring Authorization', () => {
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
beforeAll(async () => {
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
const member = await createUser({ globalRole: globalMemberRole });
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Reset } from '@/commands/user-management/reset';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
|
@ -8,18 +7,13 @@ import { UserRepository } from '@db/repositories/user.repository';
|
|||
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { getGlobalOwnerRole } from '../shared/db/roles';
|
||||
import { createUser } from '../shared/db/users';
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
mockInstance(InternalHooks);
|
||||
mockInstance(LoadNodesAndCredentials);
|
||||
mockInstance(NodeTypes);
|
||||
await testDb.init();
|
||||
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -32,11 +26,11 @@ afterAll(async () => {
|
|||
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
test.skip('user-management:reset should reset DB to default user state', async () => {
|
||||
await createUser({ globalRole: globalOwnerRole });
|
||||
await createUser({ role: 'global:owner' });
|
||||
|
||||
await Reset.run();
|
||||
|
||||
const user = await Container.get(UserRepository).findOneBy({ globalRoleId: globalOwnerRole.id });
|
||||
const user = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
|
||||
|
||||
if (!user) {
|
||||
fail('No owner found after DB reset to default user state');
|
||||
|
|
|
@ -18,6 +18,7 @@ import { OrchestrationHandlerWorkerService } from '@/services/orchestration/work
|
|||
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
|
||||
import { OrchestrationService } from '@/services/orchestration.service';
|
||||
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
|
||||
const oclifConfig = new Config({ root: __dirname });
|
||||
|
@ -39,6 +40,11 @@ beforeAll(async () => {
|
|||
mockInstance(RedisServicePubSubPublisher);
|
||||
mockInstance(RedisServicePubSubSubscriber);
|
||||
mockInstance(OrchestrationService);
|
||||
await testDb.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
test('worker initializes all its components', async () => {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { setupTestServer } from './shared/utils/';
|
|||
import { randomCredentialPayload as payload } from './shared/random';
|
||||
import { saveCredential } from './shared/db/credentials';
|
||||
import { createMember, createOwner } from './shared/db/users';
|
||||
import { getCredentialOwnerRole } from './shared/db/roles';
|
||||
|
||||
const { any } = expect;
|
||||
|
||||
|
@ -26,10 +25,14 @@ type GetAllResponse = { body: { data: ListQuery.Credentials.WithOwnedByAndShared
|
|||
describe('GET /credentials', () => {
|
||||
describe('should return', () => {
|
||||
test('all credentials for owner', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
const { id: id1 } = await saveCredential(payload(), { user: owner, role });
|
||||
const { id: id2 } = await saveCredential(payload(), { user: member, role });
|
||||
const { id: id1 } = await saveCredential(payload(), {
|
||||
user: owner,
|
||||
role: 'credential:owner',
|
||||
});
|
||||
const { id: id2 } = await saveCredential(payload(), {
|
||||
user: member,
|
||||
role: 'credential:owner',
|
||||
});
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -47,13 +50,11 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('only own credentials for member', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
const firstMember = member;
|
||||
const secondMember = await createMember();
|
||||
|
||||
const c1 = await saveCredential(payload(), { user: firstMember, role });
|
||||
const c2 = await saveCredential(payload(), { user: secondMember, role });
|
||||
const c1 = await saveCredential(payload(), { user: firstMember, role: 'credential:owner' });
|
||||
const c2 = await saveCredential(payload(), { user: secondMember, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(firstMember)
|
||||
|
@ -72,8 +73,7 @@ describe('GET /credentials', () => {
|
|||
|
||||
describe('filter', () => {
|
||||
test('should filter credentials by field: name - full match', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role });
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -97,8 +97,7 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should filter credentials by field: name - partial match', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role });
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const partialName = savedCred.name.slice(3);
|
||||
|
||||
|
@ -124,9 +123,7 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should filter credentials by field: type - full match', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role });
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -150,9 +147,7 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should filter credentials by field: type - partial match', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role });
|
||||
const savedCred = await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const partialType = savedCred.type.slice(3);
|
||||
|
||||
|
@ -180,10 +175,8 @@ describe('GET /credentials', () => {
|
|||
|
||||
describe('select', () => {
|
||||
test('should select credential field: id', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -197,10 +190,8 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should select credential field: name', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -214,10 +205,8 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should select credential field: type', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response: GetAllResponse = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -233,10 +222,8 @@ describe('GET /credentials', () => {
|
|||
|
||||
describe('take', () => {
|
||||
test('should return n credentials or less, without skip', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
@ -260,10 +247,8 @@ describe('GET /credentials', () => {
|
|||
});
|
||||
|
||||
test('should return n credentials or less, with skip', async () => {
|
||||
const role = await getCredentialOwnerRole();
|
||||
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
await saveCredential(payload(), { user: owner, role: 'credential:owner' });
|
||||
|
||||
const response = await testServer
|
||||
.authAgentFor(owner)
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
import { Container } from 'typedi';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { In } from 'typeorm';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { License } from '@/License';
|
||||
|
||||
import { randomCredentialPayload } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { SaveCredentialFunction } from './shared/types';
|
||||
import * as utils from './shared/utils/';
|
||||
import { affixRoleToSaveCredential, shareCredentialWithUsers } from './shared/db/credentials';
|
||||
import { getCredentialOwnerRole, getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createManyUsers, createUser, createUserShell } from './shared/db/users';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import Container from 'typedi';
|
||||
import { License } from '@/License';
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
import { UserManagementMailer } from '@/UserManagement/email';
|
||||
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
|
||||
const sharingSpy = jest.spyOn(License.prototype, 'isSharingEnabled').mockReturnValue(true);
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['credentials'] });
|
||||
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let anotherMember: User;
|
||||
|
@ -32,18 +30,14 @@ let saveCredential: SaveCredentialFunction;
|
|||
const mailer = mockInstance(UserManagementMailer);
|
||||
|
||||
beforeAll(async () => {
|
||||
const globalOwnerRole = await getGlobalOwnerRole();
|
||||
globalMemberRole = await getGlobalMemberRole();
|
||||
const credentialOwnerRole = await getCredentialOwnerRole();
|
||||
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
member = await createUser({ globalRole: globalMemberRole });
|
||||
anotherMember = await createUser({ globalRole: globalMemberRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
member = await createUser({ role: 'global:member' });
|
||||
anotherMember = await createUser({ role: 'global:member' });
|
||||
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
authAnotherMemberAgent = testServer.authAgentFor(anotherMember);
|
||||
|
||||
saveCredential = affixRoleToSaveCredential(credentialOwnerRole);
|
||||
saveCredential = affixRoleToSaveCredential('credential:owner');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -92,7 +86,7 @@ describe('router should switch based on flag', () => {
|
|||
describe('GET /credentials', () => {
|
||||
test('should return all creds for owner', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
@ -156,7 +150,7 @@ describe('GET /credentials', () => {
|
|||
|
||||
test('should return only relevant creds for member', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
await saveCredential(randomCredentialPayload(), { user: member2 });
|
||||
|
@ -232,7 +226,7 @@ describe('GET /credentials/:id', () => {
|
|||
|
||||
test('should retrieve non-owned cred for owner', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
|
@ -271,7 +265,7 @@ describe('GET /credentials/:id', () => {
|
|||
|
||||
test('should retrieve owned cred for member', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
const authMemberAgent = testServer.authAgentFor(member1);
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
|
||||
|
@ -339,7 +333,7 @@ describe('PUT /credentials/:id/share', () => {
|
|||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const [member1, member2, member3, member4, member5] = await createManyUsers(5, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
const shareWithIds = [member1.id, member2.id, member3.id];
|
||||
|
||||
|
@ -353,7 +347,6 @@ describe('PUT /credentials/:id/share', () => {
|
|||
expect(response.body.data).toBeUndefined();
|
||||
|
||||
const sharedCredentials = await Container.get(SharedCredentialsRepository).find({
|
||||
relations: ['role'],
|
||||
where: { credentialsId: savedCredential.id },
|
||||
});
|
||||
|
||||
|
@ -362,13 +355,11 @@ describe('PUT /credentials/:id/share', () => {
|
|||
|
||||
sharedCredentials.forEach((sharedCredential) => {
|
||||
if (sharedCredential.userId === owner.id) {
|
||||
expect(sharedCredential.role.name).toBe('owner');
|
||||
expect(sharedCredential.role.scope).toBe('credential');
|
||||
expect(sharedCredential.role).toBe('credential:owner');
|
||||
return;
|
||||
}
|
||||
expect(shareWithIds).toContain(sharedCredential.userId);
|
||||
expect(sharedCredential.role.name).toBe('user');
|
||||
expect(sharedCredential.role.scope).toBe('credential');
|
||||
expect(sharedCredential.role).toBe('credential:user');
|
||||
});
|
||||
|
||||
expect(mailer.notifyCredentialsShared).toHaveBeenCalledTimes(1);
|
||||
|
@ -376,7 +367,7 @@ describe('PUT /credentials/:id/share', () => {
|
|||
|
||||
test('should share the credential with the provided userIds', async () => {
|
||||
const [member1, member2, member3] = await createManyUsers(3, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
const memberIds = [member1.id, member2.id, member3.id];
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
@ -390,25 +381,21 @@ describe('PUT /credentials/:id/share', () => {
|
|||
|
||||
// check that sharings got correctly set in DB
|
||||
const sharedCredentials = await Container.get(SharedCredentialsRepository).find({
|
||||
relations: ['role'],
|
||||
where: { credentialsId: savedCredential.id, userId: In([...memberIds]) },
|
||||
});
|
||||
|
||||
expect(sharedCredentials.length).toBe(memberIds.length);
|
||||
|
||||
sharedCredentials.forEach((sharedCredential) => {
|
||||
expect(sharedCredential.role.name).toBe('user');
|
||||
expect(sharedCredential.role.scope).toBe('credential');
|
||||
expect(sharedCredential.role).toBe('credential:user');
|
||||
});
|
||||
|
||||
// check that owner still exists
|
||||
const ownerSharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
|
||||
relations: ['role'],
|
||||
where: { credentialsId: savedCredential.id, userId: owner.id },
|
||||
});
|
||||
|
||||
expect(ownerSharedCredential.role.name).toBe('owner');
|
||||
expect(ownerSharedCredential.role.scope).toBe('credential');
|
||||
expect(ownerSharedCredential.role).toBe('credential:owner');
|
||||
expect(mailer.notifyCredentialsShared).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -456,7 +443,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({ globalRole: globalMemberRole });
|
||||
const tempUser = await createUser({ role: 'global:member' });
|
||||
|
||||
const response = await authAnotherMemberAgent
|
||||
.put(`/credentials/${savedCredential.id}/share`)
|
||||
|
@ -487,7 +474,7 @@ describe('PUT /credentials/:id/share', () => {
|
|||
});
|
||||
|
||||
test('should ignore pending sharee', async () => {
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
|
@ -538,7 +525,7 @@ describe('PUT /credentials/:id/share', () => {
|
|||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
await shareCredentialWithUsers(savedCredential, [member1, member2]);
|
||||
|
|
|
@ -1,28 +1,24 @@
|
|||
import { Container } from 'typedi';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
|
||||
import config from '@/config';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { License } from '@/License';
|
||||
|
||||
import { randomCredentialPayload, randomName, randomString } from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { SaveCredentialFunction } from './shared/types';
|
||||
import * as utils from './shared/utils/';
|
||||
import { affixRoleToSaveCredential, shareCredentialWithUsers } from './shared/db/credentials';
|
||||
import { getCredentialOwnerRole, getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createManyUsers, createUser } from './shared/db/users';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import Container from 'typedi';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
import { License } from '@/License';
|
||||
|
||||
// mock that credentialsSharing is not enabled
|
||||
jest.spyOn(License.prototype, 'isSharingEnabled').mockReturnValue(false);
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['credentials'] });
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let secondMember: User;
|
||||
|
@ -31,15 +27,11 @@ let authMemberAgent: SuperAgentTest;
|
|||
let saveCredential: SaveCredentialFunction;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
globalMemberRole = await getGlobalMemberRole();
|
||||
const credentialOwnerRole = await getCredentialOwnerRole();
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
member = await createUser({ role: 'global:member' });
|
||||
secondMember = await createUser({ role: 'global:member' });
|
||||
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
member = await createUser({ globalRole: globalMemberRole });
|
||||
secondMember = await createUser({ globalRole: globalMemberRole });
|
||||
|
||||
saveCredential = affixRoleToSaveCredential(credentialOwnerRole);
|
||||
saveCredential = affixRoleToSaveCredential('credential:owner');
|
||||
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
|
@ -74,7 +66,7 @@ describe('GET /credentials', () => {
|
|||
|
||||
test('should return only own creds for member', async () => {
|
||||
const [member1, member2] = await createManyUsers(2, {
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
const [savedCredential1] = await Promise.all([
|
||||
|
|
|
@ -8,7 +8,6 @@ import { SourceControlService } from '@/environments/sourceControl/sourceControl
|
|||
import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile';
|
||||
|
||||
import * as utils from '../shared/utils/';
|
||||
import { getGlobalOwnerRole } from '../shared/db/roles';
|
||||
import { createUser } from '../shared/db/users';
|
||||
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
@ -20,8 +19,7 @@ const testServer = utils.setupTestServer({
|
|||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
const globalOwnerRole = await getGlobalOwnerRole();
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
Container.get(SourceControlPreferencesService).isSourceControlConnected = () => true;
|
||||
|
|
|
@ -3,7 +3,6 @@ import axios from 'axios';
|
|||
import syslog from 'syslog-client';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type {
|
||||
MessageEventBusDestinationSentryOptions,
|
||||
|
@ -26,7 +25,6 @@ import { EventMessageWorkflow } from '@/eventbus/EventMessageClasses/EventMessag
|
|||
import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNode';
|
||||
|
||||
import * as utils from './shared/utils';
|
||||
import { getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUser } from './shared/db/users';
|
||||
|
||||
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
||||
|
@ -35,7 +33,6 @@ const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|||
jest.mock('syslog-client');
|
||||
const mockedSyslog = syslog as jest.Mocked<typeof syslog>;
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
|
@ -85,8 +82,7 @@ const testServer = utils.setupTestServer({
|
|||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import type { SuperAgentTest } from 'supertest';
|
||||
import * as utils from './shared/utils/';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUser } from './shared/db/users';
|
||||
|
||||
/**
|
||||
|
@ -11,7 +9,6 @@ import { createUser } from './shared/db/users';
|
|||
* The tests in this file are only checking endpoint permissions.
|
||||
*/
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
|
@ -21,8 +18,7 @@ const testServer = utils.setupTestServer({
|
|||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import type { INode } from 'n8n-workflow';
|
|||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||
import { TagRepository } from '@/databases/repositories/tag.repository';
|
||||
import { ImportService } from '@/services/import.service';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { TagEntity } from '@/databases/entities/TagEntity';
|
||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository';
|
||||
|
@ -34,12 +33,7 @@ describe('ImportService', () => {
|
|||
|
||||
credentialsRepository.find.mockResolvedValue([]);
|
||||
|
||||
importService = new ImportService(
|
||||
mock(),
|
||||
credentialsRepository,
|
||||
tagRepository,
|
||||
Container.get(RoleService),
|
||||
);
|
||||
importService = new ImportService(mock(), credentialsRepository, tagRepository);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -67,10 +61,8 @@ describe('ImportService', () => {
|
|||
|
||||
await importService.importWorkflows([workflowToImport], owner.id);
|
||||
|
||||
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
const dbSharing = await Container.get(SharedWorkflowRepository).findOneOrFail({
|
||||
where: { workflowId: workflowToImport.id, userId: owner.id, roleId: workflowOwnerRole.id },
|
||||
where: { workflowId: workflowToImport.id, userId: owner.id, role: 'workflow:owner' },
|
||||
});
|
||||
|
||||
expect(dbSharing.userId).toBe(owner.id);
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalAdminRole, getGlobalMemberRole } from './shared/db/roles';
|
||||
import { createMember, createOwner, createUser, createUserShell } from './shared/db/users';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
|
@ -56,8 +55,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
});
|
||||
|
||||
test('should fill out a member shell', async () => {
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
|
||||
const memberData = {
|
||||
inviterId: owner.id,
|
||||
|
@ -78,7 +76,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
lastName,
|
||||
personalizationAnswers,
|
||||
password,
|
||||
globalRole,
|
||||
role,
|
||||
isPending,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
|
@ -91,8 +89,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(role).toBe('global:member');
|
||||
expect(apiKey).not.toBeDefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).not.toHaveLength(0);
|
||||
|
@ -110,8 +107,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
});
|
||||
|
||||
test('should fill out an admin shell', async () => {
|
||||
const globalAdminRole = await getGlobalAdminRole();
|
||||
const adminShell = await createUserShell(globalAdminRole);
|
||||
const adminShell = await createUserShell('global:admin');
|
||||
|
||||
const memberData = {
|
||||
inviterId: owner.id,
|
||||
|
@ -132,7 +128,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
lastName,
|
||||
personalizationAnswers,
|
||||
password,
|
||||
globalRole,
|
||||
role,
|
||||
isPending,
|
||||
apiKey,
|
||||
globalScopes,
|
||||
|
@ -145,8 +141,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(globalRole.name).toBe('admin');
|
||||
expect(role).toBe('global:admin');
|
||||
expect(apiKey).not.toBeDefined();
|
||||
expect(globalScopes).toBeDefined();
|
||||
expect(globalScopes).not.toHaveLength(0);
|
||||
|
@ -166,11 +161,9 @@ describe('POST /invitations/:id/accept', () => {
|
|||
test('should fail with invalid payloads', async () => {
|
||||
const memberShellEmail = randomEmail();
|
||||
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
|
||||
const memberShell = await Container.get(UserRepository).save({
|
||||
email: memberShellEmail,
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
const invalidPayloads = [
|
||||
|
@ -219,8 +212,7 @@ describe('POST /invitations/:id/accept', () => {
|
|||
});
|
||||
|
||||
test('should fail with already accepted invite', async () => {
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
const member = await createUser({ globalRole: globalMemberRole });
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
|
||||
const memberData = {
|
||||
inviterId: owner.id,
|
||||
|
@ -334,7 +326,7 @@ describe('POST /invitations', () => {
|
|||
|
||||
const response = await ownerAgent
|
||||
.post('/invitations')
|
||||
.send([{ email: randomEmail(), role: 'admin' }])
|
||||
.send([{ email: randomEmail(), role: 'global:admin' }])
|
||||
.expect(200);
|
||||
|
||||
const [result] = response.body.data as UserInvitationResponse[];
|
||||
|
@ -349,11 +341,11 @@ describe('POST /invitations', () => {
|
|||
test('should reinvite member', async () => {
|
||||
mailer.invite.mockResolvedValue({ emailSent: false });
|
||||
|
||||
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'member' }]);
|
||||
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'global:member' }]);
|
||||
|
||||
await ownerAgent
|
||||
.post('/invitations')
|
||||
.send([{ email: randomEmail(), role: 'member' }])
|
||||
.send([{ email: randomEmail(), role: 'global:member' }])
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
|
@ -361,11 +353,11 @@ describe('POST /invitations', () => {
|
|||
license.isAdvancedPermissionsLicensed.mockReturnValue(true);
|
||||
mailer.invite.mockResolvedValue({ emailSent: false });
|
||||
|
||||
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'admin' }]);
|
||||
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'global:admin' }]);
|
||||
|
||||
await ownerAgent
|
||||
.post('/invitations')
|
||||
.send([{ email: randomEmail(), role: 'admin' }])
|
||||
.send([{ email: randomEmail(), role: 'global:admin' }])
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
|
@ -375,7 +367,7 @@ describe('POST /invitations', () => {
|
|||
|
||||
await ownerAgent
|
||||
.post('/invitations')
|
||||
.send([{ email: randomEmail(), role: 'admin' }])
|
||||
.send([{ email: randomEmail(), role: 'global:admin' }])
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
|
@ -384,8 +376,7 @@ describe('POST /invitations', () => {
|
|||
|
||||
mailer.invite.mockResolvedValue({ emailSent: true });
|
||||
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
const memberShell = await createUserShell(globalMemberRole);
|
||||
const memberShell = await createUserShell('global:member');
|
||||
|
||||
const newUser = randomEmail();
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import { jsonParse } from 'n8n-workflow';
|
|||
import { Cipher } from 'n8n-core';
|
||||
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { LDAP_DEFAULT_CONFIGURATION, LDAP_FEATURE_NAME } from '@/Ldap/constants';
|
||||
import { LdapService } from '@/Ldap/ldap.service';
|
||||
|
@ -18,7 +17,6 @@ import { randomEmail, randomName, uniqueId } from './../shared/random';
|
|||
import * as testDb from './../shared/testDb';
|
||||
import * as utils from '../shared/utils/';
|
||||
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from '../shared/db/roles';
|
||||
import { createLdapUser, createUser, getAllUsers, getLdapIdentities } from '../shared/db/users';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
|
@ -26,7 +24,6 @@ import { AuthProviderSyncHistoryRepository } from '@db/repositories/authProvider
|
|||
|
||||
jest.mock('@/telemetry');
|
||||
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
||||
|
@ -50,14 +47,7 @@ const testServer = utils.setupTestServer({
|
|||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
const [globalOwnerRole, fetchedGlobalMemberRole] = await Promise.all([
|
||||
getGlobalOwnerRole(),
|
||||
getGlobalMemberRole(),
|
||||
]);
|
||||
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
|
||||
owner = await createUser({ globalRole: globalOwnerRole, password: 'password' });
|
||||
owner = await createUser({ role: 'global:owner', password: 'password' });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
defaultLdapConfig.bindingAdminPassword = Container.get(Cipher).encrypt(
|
||||
|
@ -97,7 +87,7 @@ const createLdapConfig = async (attributes: Partial<LdapConfig> = {}): Promise<L
|
|||
};
|
||||
|
||||
test('Member role should not be able to access ldap routes', async () => {
|
||||
const member = await createUser({ globalRole: globalMemberRole });
|
||||
const member = await createUser({ role: 'global:member' });
|
||||
const authAgent = testServer.authAgentFor(member);
|
||||
await authAgent.get('/ldap/config').expect(403);
|
||||
await authAgent.put('/ldap/config').expect(403);
|
||||
|
@ -169,7 +159,7 @@ describe('PUT /ldap/config', () => {
|
|||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ globalRole: globalMemberRole }, uniqueId());
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
|
||||
const configuration = ldapConfig;
|
||||
|
||||
|
@ -282,7 +272,7 @@ describe('POST /ldap/sync', () => {
|
|||
const ldapUserId = uniqueId();
|
||||
|
||||
const member = await createLdapUser(
|
||||
{ globalRole: globalMemberRole, email: ldapUserEmail },
|
||||
{ role: 'global:member', email: ldapUserEmail },
|
||||
ldapUserId,
|
||||
);
|
||||
|
||||
|
@ -311,7 +301,7 @@ describe('POST /ldap/sync', () => {
|
|||
const ldapUserId = uniqueId();
|
||||
|
||||
const member = await createLdapUser(
|
||||
{ globalRole: globalMemberRole, email: ldapUserEmail },
|
||||
{ role: 'global:member', email: ldapUserEmail },
|
||||
ldapUserId,
|
||||
);
|
||||
|
||||
|
@ -394,7 +384,7 @@ describe('POST /ldap/sync', () => {
|
|||
|
||||
await createLdapUser(
|
||||
{
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: randomName(),
|
||||
|
@ -427,7 +417,7 @@ describe('POST /ldap/sync', () => {
|
|||
|
||||
await createLdapUser(
|
||||
{
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: ldapUser.sn,
|
||||
|
@ -456,7 +446,7 @@ describe('POST /ldap/sync', () => {
|
|||
});
|
||||
|
||||
test('should remove user instance access once the user is disabled during synchronization', async () => {
|
||||
const member = await createLdapUser({ globalRole: globalMemberRole }, uniqueId());
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
|
||||
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue([]);
|
||||
|
||||
|
@ -543,7 +533,7 @@ describe('POST /login', () => {
|
|||
|
||||
await createLdapUser(
|
||||
{
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
email: ldapUser.mail,
|
||||
firstName: 'firstname',
|
||||
lastName: 'lastname',
|
||||
|
@ -577,7 +567,7 @@ describe('POST /login', () => {
|
|||
};
|
||||
|
||||
await createUser({
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
email: ldapUser.mail,
|
||||
firstName: ldapUser.givenName,
|
||||
lastName: 'lastname',
|
||||
|
@ -592,7 +582,7 @@ describe('Instance owner should able to delete LDAP users', () => {
|
|||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ globalRole: globalMemberRole }, uniqueId());
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
|
||||
await authOwnerAgent.post(`/users/${member.id}`);
|
||||
});
|
||||
|
@ -601,7 +591,7 @@ describe('Instance owner should able to delete LDAP users', () => {
|
|||
const ldapConfig = await createLdapConfig();
|
||||
Container.get(LdapService).setConfig(ldapConfig);
|
||||
|
||||
const member = await createLdapUser({ globalRole: globalMemberRole }, uniqueId());
|
||||
const member = await createLdapUser({ role: 'global:member' }, uniqueId());
|
||||
|
||||
// delete the LDAP member and transfer its workflows/credentials to instance owner
|
||||
await authOwnerAgent.post(`/users/${member.id}?transferId=${owner.id}`);
|
||||
|
|
|
@ -5,7 +5,6 @@ import type { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
|
|||
import { License } from '@/License';
|
||||
import * as testDb from './shared/testDb';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUserShell } from './shared/db/users';
|
||||
|
||||
const MOCK_SERVER_URL = 'https://server.com/v1';
|
||||
|
@ -19,10 +18,8 @@ let authMemberAgent: SuperAgentTest;
|
|||
const testServer = utils.setupTestServer({ endpointGroups: ['license'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
const globalOwnerRole = await getGlobalOwnerRole();
|
||||
const globalMemberRole = await getGlobalMemberRole();
|
||||
owner = await createUserShell(globalOwnerRole);
|
||||
member = await createUserShell(globalMemberRole);
|
||||
owner = await createUserShell('global:owner');
|
||||
member = await createUserShell('global:member');
|
||||
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { SuperAgentTest } from 'supertest';
|
||||
import { IsNull } from 'typeorm';
|
||||
import validator from 'validator';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import {
|
||||
|
@ -13,21 +12,12 @@ import {
|
|||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { addApiKey, createUser, createUserShell } from './shared/db/users';
|
||||
import Container from 'typedi';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['me'] });
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
globalMemberRole = await getGlobalMemberRole();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
});
|
||||
|
@ -37,7 +27,7 @@ describe('Owner shell', () => {
|
|||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
beforeEach(async () => {
|
||||
ownerShell = await createUserShell(globalOwnerRole);
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
await addApiKey(ownerShell);
|
||||
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
|
||||
});
|
||||
|
@ -54,7 +44,7 @@ describe('Owner shell', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
apiKey,
|
||||
|
@ -67,8 +57,7 @@ describe('Owner shell', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
|
||||
const storedOwnerShell = await Container.get(UserRepository).findOneByOrFail({ id });
|
||||
|
@ -177,7 +166,7 @@ describe('Member', () => {
|
|||
beforeEach(async () => {
|
||||
member = await createUser({
|
||||
password: memberPassword,
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
|
@ -197,7 +186,7 @@ describe('Member', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
apiKey,
|
||||
|
@ -210,8 +199,7 @@ describe('Member', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:member');
|
||||
expect(apiKey).toBeUndefined();
|
||||
|
||||
const storedMember = await Container.get(UserRepository).findOneByOrFail({ id });
|
||||
|
@ -317,7 +305,7 @@ describe('Owner', () => {
|
|||
});
|
||||
|
||||
test('PATCH /me should succeed with valid inputs', async () => {
|
||||
const owner = await createUser({ globalRole: globalOwnerRole });
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
|
||||
|
@ -331,7 +319,7 @@ describe('Owner', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
apiKey,
|
||||
|
@ -344,8 +332,7 @@ describe('Owner', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
|
||||
const storedOwner = await Container.get(UserRepository).findOneByOrFail({ id });
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Container from 'typedi';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { randomPassword } from '@/Ldap/helpers';
|
||||
import { TOTPService } from '@/Mfa/totp.service';
|
||||
|
@ -13,7 +12,6 @@ import { UserRepository } from '@db/repositories/user.repository';
|
|||
|
||||
jest.mock('@/telemetry');
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let owner: User;
|
||||
|
||||
const testServer = utils.setupTestServer({
|
||||
|
@ -23,7 +21,7 @@ const testServer = utils.setupTestServer({
|
|||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
|
||||
config.set('userManagement.disabled', false);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@ import validator from 'validator';
|
|||
import type { SuperAgentTest } from 'supertest';
|
||||
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import {
|
||||
randomEmail,
|
||||
|
@ -12,23 +11,17 @@ import {
|
|||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import * as utils from './shared/utils/';
|
||||
import { getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUserShell } from './shared/db/users';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import Container from 'typedi';
|
||||
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let ownerShell: User;
|
||||
let authOwnerShellAgent: SuperAgentTest;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
ownerShell = await createUserShell(globalOwnerRole);
|
||||
ownerShell = await createUserShell('global:owner');
|
||||
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
});
|
||||
|
@ -56,7 +49,7 @@ describe('POST /owner/setup', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
apiKey,
|
||||
|
@ -70,8 +63,7 @@ describe('POST /owner/setup', () => {
|
|||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(role).toBe('global:owner');
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(globalScopes).not.toHaveLength(0);
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { mock } from 'jest-mock-extended';
|
|||
|
||||
import { License } from '@/License';
|
||||
import config from '@/config';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
|
@ -24,14 +23,11 @@ import {
|
|||
randomValidPassword,
|
||||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from './shared/db/roles';
|
||||
import { createUser } from './shared/db/users';
|
||||
import { PasswordUtility } from '@/services/password.utility';
|
||||
|
||||
config.set('userManagement.jwtSecret', randomString(5, 10));
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
|
||||
|
@ -41,15 +37,10 @@ const testServer = setupTestServer({ endpointGroups: ['passwordReset'] });
|
|||
const jwtService = Container.get(JwtService);
|
||||
let userService: UserService;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
globalMemberRole = await getGlobalMemberRole();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['User']);
|
||||
owner = await createUser({ globalRole: globalOwnerRole });
|
||||
member = await createUser({ globalRole: globalMemberRole });
|
||||
owner = await createUser({ role: 'global:owner' });
|
||||
member = await createUser({ role: 'global:member' });
|
||||
externalHooks.run.mockReset();
|
||||
jest.replaceProperty(mailer, 'isEmailSetUp', true);
|
||||
userService = Container.get(UserService);
|
||||
|
@ -59,7 +50,7 @@ describe('POST /forgot-password', () => {
|
|||
test('should send password reset email', async () => {
|
||||
const member = await createUser({
|
||||
email: 'test@test.com',
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
|
@ -85,7 +76,7 @@ describe('POST /forgot-password', () => {
|
|||
await setCurrentAuthenticationMethod('saml');
|
||||
const member = await createUser({
|
||||
email: 'test@test.com',
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
});
|
||||
|
||||
await testServer.authlessAgent
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { SuperAgentTest } from 'supertest';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
|
||||
import { randomApiKey, randomName, randomString } from '../shared/random';
|
||||
|
@ -7,14 +6,11 @@ import * as utils from '../shared/utils/';
|
|||
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { affixRoleToSaveCredential } from '../shared/db/credentials';
|
||||
import { getAllRoles } from '../shared/db/roles';
|
||||
import { addApiKey, createUser, createUserShell } from '../shared/db/users';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import Container from 'typedi';
|
||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||
|
||||
let globalMemberRole: Role;
|
||||
let credentialOwnerRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
@ -25,19 +21,13 @@ let saveCredential: SaveCredentialFunction;
|
|||
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
||||
await getAllRoles();
|
||||
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
credentialOwnerRole = fetchedCredentialOwnerRole;
|
||||
|
||||
owner = await addApiKey(await createUserShell(globalOwnerRole));
|
||||
member = await createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
|
||||
owner = await addApiKey(await createUserShell('global:owner'));
|
||||
member = await createUser({ role: 'global:member', apiKey: randomApiKey() });
|
||||
|
||||
authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
authMemberAgent = testServer.publicApiAgentFor(member);
|
||||
|
||||
saveCredential = affixRoleToSaveCredential(credentialOwnerRole);
|
||||
saveCredential = affixRoleToSaveCredential('credential:owner');
|
||||
|
||||
await utils.initCredentialsTypes();
|
||||
});
|
||||
|
@ -73,11 +63,11 @@ describe('POST /credentials', () => {
|
|||
expect(credential.data).not.toBe(payload.data);
|
||||
|
||||
const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
|
||||
relations: ['user', 'credentials', 'role'],
|
||||
relations: ['user', 'credentials'],
|
||||
where: { credentialsId: credential.id, userId: owner.id },
|
||||
});
|
||||
|
||||
expect(sharedCredential.role).toEqual(credentialOwnerRole);
|
||||
expect(sharedCredential.role).toEqual('credential:owner');
|
||||
expect(sharedCredential.credentials.name).toBe(payload.name);
|
||||
});
|
||||
|
||||
|
@ -156,7 +146,7 @@ describe('DELETE /credentials/:id', () => {
|
|||
|
||||
test('should delete owned cred for member but leave others untouched', async () => {
|
||||
const anotherMember = await createUser({
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
|||
import { randomApiKey } from '../shared/random';
|
||||
import * as utils from '../shared/utils/';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from '../shared/db/roles';
|
||||
import { createUser } from '../shared/db/users';
|
||||
import {
|
||||
createManyWorkflows,
|
||||
|
@ -30,11 +29,9 @@ let workflowRunner: ActiveWorkflowRunner;
|
|||
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
const globalOwnerRole = await getGlobalOwnerRole();
|
||||
const globalUserRole = await getGlobalMemberRole();
|
||||
owner = await createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
user1 = await createUser({ globalRole: globalUserRole, apiKey: randomApiKey() });
|
||||
user2 = await createUser({ globalRole: globalUserRole, apiKey: randomApiKey() });
|
||||
owner = await createUser({ role: 'global:owner', apiKey: randomApiKey() });
|
||||
user1 = await createUser({ role: 'global:member', apiKey: randomApiKey() });
|
||||
user2 = await createUser({ role: 'global:member', apiKey: randomApiKey() });
|
||||
|
||||
// TODO: mock BinaryDataService instead
|
||||
await utils.initBinaryDataService();
|
||||
|
|
|
@ -2,14 +2,12 @@ import type { SuperAgentTest } from 'supertest';
|
|||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import { License } from '@/License';
|
||||
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
import { randomApiKey } from '../shared/random';
|
||||
import * as utils from '../shared/utils/';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { getGlobalMemberRole, getGlobalOwnerRole } from '../shared/db/roles';
|
||||
import { createUser, createUserShell } from '../shared/db/users';
|
||||
|
||||
mockInstance(License, {
|
||||
|
@ -18,16 +16,6 @@ mockInstance(License, {
|
|||
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
[globalOwnerRole, globalMemberRole] = await Promise.all([
|
||||
getGlobalOwnerRole(),
|
||||
getGlobalMemberRole(),
|
||||
]);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.truncate(['SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials', 'User']);
|
||||
});
|
||||
|
@ -35,14 +23,14 @@ beforeEach(async () => {
|
|||
describe('With license unlimited quota:users', () => {
|
||||
describe('GET /users', () => {
|
||||
test('should fail due to missing API Key', async () => {
|
||||
const owner = await createUser({ globalRole: globalOwnerRole });
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
await authOwnerAgent.get('/users').expect(401);
|
||||
});
|
||||
|
||||
test('should fail due to invalid API Key', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
owner.apiKey = 'invalid-key';
|
||||
|
@ -58,7 +46,7 @@ describe('With license unlimited quota:users', () => {
|
|||
|
||||
test('should return all users', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
|
@ -77,7 +65,7 @@ describe('With license unlimited quota:users', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
createdAt,
|
||||
|
@ -91,7 +79,7 @@ describe('With license unlimited quota:users', () => {
|
|||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(role).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(updatedAt).toBeDefined();
|
||||
}
|
||||
|
@ -100,14 +88,14 @@ describe('With license unlimited quota:users', () => {
|
|||
|
||||
describe('GET /users/:id', () => {
|
||||
test('should fail due to missing API Key', async () => {
|
||||
const owner = await createUser({ globalRole: globalOwnerRole });
|
||||
const owner = await createUser({ role: 'global:owner' });
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
await authOwnerAgent.get(`/users/${owner.id}`).expect(401);
|
||||
});
|
||||
|
||||
test('should fail due to invalid API Key', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
owner.apiKey = 'invalid-key';
|
||||
|
@ -122,7 +110,7 @@ describe('With license unlimited quota:users', () => {
|
|||
});
|
||||
test('should return 404 for non-existing id ', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
|
@ -131,11 +119,11 @@ describe('With license unlimited quota:users', () => {
|
|||
|
||||
test('should return a pending user', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
const { id: memberId } = await createUserShell(globalMemberRole);
|
||||
const { id: memberId } = await createUserShell('global:member');
|
||||
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
const response = await authOwnerAgent.get(`/users/${memberId}`).expect(200);
|
||||
|
@ -146,7 +134,7 @@ describe('With license unlimited quota:users', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
createdAt,
|
||||
|
@ -159,7 +147,7 @@ describe('With license unlimited quota:users', () => {
|
|||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(role).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(isPending).toBeDefined();
|
||||
expect(isPending).toBeTruthy();
|
||||
|
@ -170,7 +158,7 @@ describe('With license unlimited quota:users', () => {
|
|||
describe('GET /users/:email', () => {
|
||||
test('with non-existing email should return 404', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
const authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
|
@ -179,7 +167,7 @@ describe('With license unlimited quota:users', () => {
|
|||
|
||||
test('should return a user', async () => {
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
|
@ -192,7 +180,7 @@ describe('With license unlimited quota:users', () => {
|
|||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
role,
|
||||
password,
|
||||
isPending,
|
||||
createdAt,
|
||||
|
@ -206,7 +194,7 @@ describe('With license unlimited quota:users', () => {
|
|||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(role).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(updatedAt).toBeDefined();
|
||||
});
|
||||
|
@ -220,7 +208,7 @@ describe('With license without quota:users', () => {
|
|||
mockInstance(License, { getUsersLimit: jest.fn().mockReturnValue(null) });
|
||||
|
||||
const owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
authOwnerAgent = testServer.publicApiAgentFor(owner);
|
||||
|
|
|
@ -2,25 +2,22 @@ import type { SuperAgentTest } from 'supertest';
|
|||
import Container from 'typedi';
|
||||
import type { INode } from 'n8n-workflow';
|
||||
import { STARTING_NODES } from '@/constants';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { TagEntity } from '@db/entities/TagEntity';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repository';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import { Push } from '@/push';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
|
||||
import { randomApiKey } from '../shared/random';
|
||||
import * as utils from '../shared/utils/';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import { getAllRoles } from '../shared/db/roles';
|
||||
import { createUser } from '../shared/db/users';
|
||||
import { createWorkflow, createWorkflowWithTrigger } from '../shared/db/workflows';
|
||||
import { createTag } from '../shared/db/tags';
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
import { Push } from '@/push';
|
||||
import { ExecutionService } from '@/executions/execution.service';
|
||||
|
||||
let workflowOwnerRole: Role;
|
||||
let owner: User;
|
||||
let member: User;
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
|
@ -34,17 +31,13 @@ mockInstance(Push);
|
|||
mockInstance(ExecutionService);
|
||||
|
||||
beforeAll(async () => {
|
||||
const [globalOwnerRole, globalMemberRole, fetchedWorkflowOwnerRole] = await getAllRoles();
|
||||
|
||||
workflowOwnerRole = fetchedWorkflowOwnerRole;
|
||||
|
||||
owner = await createUser({
|
||||
globalRole: globalOwnerRole,
|
||||
role: 'global:owner',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
member = await createUser({
|
||||
globalRole: globalMemberRole,
|
||||
role: 'global:member',
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
|
@ -693,12 +686,12 @@ describe('POST /workflows', () => {
|
|||
userId: member.id,
|
||||
workflowId: response.body.id,
|
||||
},
|
||||
relations: ['workflow', 'role'],
|
||||
relations: ['workflow'],
|
||||
});
|
||||
|
||||
expect(sharedWorkflow?.workflow.name).toBe(name);
|
||||
expect(sharedWorkflow?.workflow.createdAt.toISOString()).toBe(createdAt);
|
||||
expect(sharedWorkflow?.role).toEqual(workflowOwnerRole);
|
||||
expect(sharedWorkflow?.role).toEqual('workflow:owner');
|
||||
});
|
||||
|
||||
test('should create workflow history version when licensed', async () => {
|
||||
|
@ -1110,13 +1103,13 @@ describe('PUT /workflows/:id', () => {
|
|||
userId: member.id,
|
||||
workflowId: response.body.id,
|
||||
},
|
||||
relations: ['workflow', 'role'],
|
||||
relations: ['workflow'],
|
||||
});
|
||||
|
||||
expect(sharedWorkflow?.workflow.name).toBe(payload.name);
|
||||
expect(sharedWorkflow?.workflow.updatedAt.getTime()).toBeGreaterThan(
|
||||
workflow.updatedAt.getTime(),
|
||||
);
|
||||
expect(sharedWorkflow?.role).toEqual(workflowOwnerRole);
|
||||
expect(sharedWorkflow?.role).toEqual('workflow:owner');
|
||||
});
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue