Skip to content

Commit

Permalink
Merge branch 'master' into ADO-1505-fix-cloud-data-on-instance
Browse files Browse the repository at this point in the history
* master:
  fix(editor): Add telemetry to workflow history (#7811)
  fix(editor): Fix mouse position in workflow previews (#7853)
  fix(editor): Fix icon for unknown node type (#7842)
  ci: Fix editor tests when coverage is enabled (no-changelog) (#7827)
  fix(editor): Suppress dev server websocket messages in workflow view (#7808)
  feat(core): Update LLM applications building support (no-changelog) (#7710)
  fix(editor): Fix push connection on WorkerList and CommunityNodes pages (no-changelog) (#7851)
  fix(editor): Remove ability for users to select admin role in the UI (no-changelog) (#7850)
  feat(core): Set up endpoint for all existing roles with license flag (#7834)
  fix(editor): Fix credential icon for old node type version (#7843)
  feat: Add initial scope checks via decorators (#7737)
  refactor(editor): Delete some barrel files and reduce circular dependencies (no-changelog) (#7838)
  fix(editor): Allow owners and admins to share workflows and credentials they don't own (#7833)
  refactor(core): Reorganize error hierarchy in `cli` package (no-changelog) (#7839)
  fix(Google Calendar Trigger Node): Fix issue preventing birthday and holiday calendars from working (#7832)
  fix(Google Sheets Node): Read operation execute for each item (#7800)
  fix(core): Node version in the user added node to workflow canvas event (no-changelog) (#7814)
  • Loading branch information
MiloradFilipovic committed Nov 29, 2023
2 parents 89667e8 + d497041 commit a1b81c7
Show file tree
Hide file tree
Showing 374 changed files with 3,779 additions and 1,302 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.5]
node-version: [18.x, 20.x]

steps:
- uses: actions/checkout@v3.5.3
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
needs: install-and-build
strategy:
matrix:
node-version: [18.x, 20.5]
node-version: [18.x, 20.x]
with:
ref: ${{ inputs.branch }}
nodeVersion: ${{ matrix.node-version }}
Expand All @@ -61,7 +61,7 @@ jobs:
needs: install-and-build
strategy:
matrix:
node-version: [18.x, 20.5]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3.5.3
with:
Expand Down
12 changes: 8 additions & 4 deletions cypress/e2e/19-execution.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ describe('Execution', () => {
.canvasNodeByName('Manual')
.within(() => cy.get('.fa-check'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-check'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check'))
Expand Down Expand Up @@ -120,8 +124,8 @@ describe('Execution', () => {
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');

// Check warning toast (works because Cypress waits enough for the element to show after the http request node has finished)
workflowPage.getters.warningToast().should('be.visible');
// Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
workflowPage.getters.successToast().should('be.visible');
});

it('should test webhook workflow', () => {
Expand Down Expand Up @@ -267,7 +271,7 @@ describe('Execution', () => {
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');

// Check warning toast (works because Cypress waits enough for the element to show after the http request node has finished)
workflowPage.getters.warningToast().should('be.visible');
// Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
workflowPage.getters.successToast().should('be.visible');
});
});
9 changes: 8 additions & 1 deletion cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'cypress-real-events';
import { WorkflowPage } from '../pages';
import { BACKEND_BASE_URL, N8N_AUTH_COOKIE } from '../constants';
import { BACKEND_BASE_URL, INSTANCE_MEMBERS, INSTANCE_OWNER, N8N_AUTH_COOKIE } from '../constants';

Cypress.Commands.add('getByTestId', (selector, ...args) => {
return cy.get(`[data-test-id="${selector}"]`, ...args);
Expand Down Expand Up @@ -169,6 +169,13 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => {
});
});

Cypress.Commands.add('push', (type, data) => {
cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/push`, {
type,
data,
});
});

Cypress.Commands.add('shouldNotHaveConsoleErrors', () => {
cy.window().then((win) => {
const spy = cy.spy(win.console, 'error');
Expand Down
1 change: 1 addition & 0 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ declare global {
options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean },
): void;
draganddrop(draggableSelector: string, droppableSelector: string): void;
push(type: string, data: unknown): void;
shouldNotHaveConsoleErrors(): void;
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"@types/supertest": "^2.0.12",
"@vitest/coverage-v8": "^0.33.0",
"cross-env": "^7.0.3",
"cypress-otp": "^1.0.3",
"cypress": "^12.17.2",
"cypress-otp": "^1.0.3",
"cypress-real-events": "^1.9.1",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
Expand Down
41 changes: 34 additions & 7 deletions packages/@n8n/permissions/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ export type Resource =
| 'credential'
| 'variable'
| 'sourceControl'
| 'externalSecretsStore';
| 'externalSecretsProvider'
| 'externalSecret'
| 'eventBusEvent'
| 'eventBusDestination'
| 'orchestration'
| 'communityPackage'
| 'ldap'
| 'saml';

export type ResourceScope<
R extends Resource,
Expand All @@ -17,14 +24,27 @@ export type WildcardScope = `${Resource}:*` | '*';

export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>;
export type TagScope = ResourceScope<'tag'>;
export type UserScope = ResourceScope<'user'>;
export type CredentialScope = ResourceScope<'credential'>;
export type UserScope = ResourceScope<'user', DefaultOperations | 'resetPassword'>;
export type CredentialScope = ResourceScope<'credential', DefaultOperations | 'share'>;
export type VariableScope = ResourceScope<'variable'>;
export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>;
export type ExternalSecretStoreScope = ResourceScope<
'externalSecretsStore',
DefaultOperations | 'refresh'
export type ExternalSecretProviderScope = ResourceScope<
'externalSecretsProvider',
DefaultOperations | 'sync'
>;
export type ExternalSecretScope = ResourceScope<'externalSecret', 'list'>;
export type EventBusEventScope = ResourceScope<'eventBusEvent', DefaultOperations | 'query'>;
export type EventBusDestinationScope = ResourceScope<
'eventBusDestination',
DefaultOperations | 'test'
>;
export type OrchestrationScope = ResourceScope<'orchestration', 'read' | 'list'>;
export type CommunityPackageScope = ResourceScope<
'communityPackage',
'install' | 'uninstall' | 'update' | 'list'
>;
export type LdapScope = ResourceScope<'ldap', 'manage' | 'sync'>;
export type SamlScope = ResourceScope<'saml', 'manage'>;

export type Scope =
| WorkflowScope
Expand All @@ -33,7 +53,14 @@ export type Scope =
| CredentialScope
| VariableScope
| SourceControlScope
| ExternalSecretStoreScope;
| ExternalSecretProviderScope
| ExternalSecretScope
| EventBusEventScope
| EventBusDestinationScope
| OrchestrationScope
| CommunityPackageScope
| LdapScope
| SamlScope;

export type ScopeLevel = 'global' | 'project' | 'resource';
export type GetScopeLevel<T extends ScopeLevel> = Record<T, Scope[]>;
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/AbstractServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import * as Db from '@/Db';
import type { N8nInstanceType, IExternalHooksClass } from '@/Interfaces';
import { ExternalHooks } from '@/ExternalHooks';
import { send, sendErrorResponse, ServiceUnavailableError } from '@/ResponseHelper';
import { send, sendErrorResponse } from '@/ResponseHelper';
import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares';
import { TestWebhooks } from '@/TestWebhooks';
import { WaitingWebhooks } from '@/WaitingWebhooks';
import { webhookRequestHandler } from '@/WebhookHelpers';
import { generateHostInstanceId } from './databases/utils/generators';
import { Logger } from '@/Logger';
import { ServiceUnavailableError } from './errors/response-errors/service-unavailable.error';

export abstract class AbstractServer {
protected logger: Logger;
Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/ActiveWorkflowRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import type {
WebhookAccessControlOptions,
WebhookRequest,
} from '@/Interfaces';
import * as ResponseHelper from '@/ResponseHelper';
import * as WebhookHelpers from '@/WebhookHelpers';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';

Expand All @@ -68,6 +67,7 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.reposi
import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
import { ActivationErrorsService } from '@/ActivationErrors.service';
import { NotFoundError } from './errors/response-errors/not-found.error';

const WEBHOOK_PROD_UNREGISTERED_HINT =
"The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)";
Expand Down Expand Up @@ -165,9 +165,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
});

if (workflowData === null) {
throw new ResponseHelper.NotFoundError(
`Could not find workflow with id "${webhook.workflowId}"`,
);
throw new NotFoundError(`Could not find workflow with id "${webhook.workflowId}"`);
}

const workflow = new Workflow({
Expand Down Expand Up @@ -196,7 +194,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
const workflowStartNode = workflow.getNode(webhookData.node);

if (workflowStartNode === null) {
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.');
throw new NotFoundError('Could not find node to process webhook.');
}

return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -252,7 +250,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {

const webhook = await this.webhookService.findWebhook(httpMethod, path);
if (webhook === null) {
throw new ResponseHelper.NotFoundError(
throw new NotFoundError(
webhookNotFoundErrorMessage(path, httpMethod),
WEBHOOK_PROD_UNREGISTERED_HINT,
);
Expand Down
11 changes: 1 addition & 10 deletions packages/cli/src/CredentialsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import type {
INodeTypes,
IWorkflowExecuteAdditionalData,
ICredentialTestFunctions,
Severity,
} from 'n8n-workflow';
import {
ICredentialsHelper,
Expand All @@ -55,6 +54,7 @@ import { isObjectLiteral } from './utils';
import { Logger } from '@/Logger';
import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import { CredentialNotFoundError } from './errors/credential-not-found.error';

const { OAUTH2_CREDENTIAL_TEST_SUCCEEDED, OAUTH2_CREDENTIAL_TEST_FAILED } = RESPONSE_ERROR_MESSAGES;

Expand Down Expand Up @@ -87,15 +87,6 @@ const mockNodeTypes: INodeTypes = {
},
};

class CredentialNotFoundError extends Error {
severity: Severity;

constructor(credentialId: string, credentialType: string) {
super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`);
this.severity = 'warning';
}
}

@Service()
export class CredentialsHelper extends ICredentialsHelper {
constructor(
Expand Down
26 changes: 17 additions & 9 deletions packages/cli/src/ExternalSecrets/ExternalSecrets.controller.ee.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
import { Authorized, Get, Post, RestController } from '@/decorators';
import { Authorized, Get, Post, RestController, RequireGlobalScope } from '@/decorators';
import { ExternalSecretsRequest } from '@/requests';
import { NotFoundError } from '@/ResponseHelper';
import { Response } from 'express';
import { Service } from 'typedi';
import { ProviderNotFoundError, ExternalSecretsService } from './ExternalSecrets.service.ee';
import { ExternalSecretsService } from './ExternalSecrets.service.ee';
import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';

@Service()
@Authorized(['global', 'owner'])
@Authorized()
@RestController('/external-secrets')
export class ExternalSecretsController {
constructor(private readonly secretsService: ExternalSecretsService) {}

@Get('/providers')
@RequireGlobalScope('externalSecretsProvider:list')
async getProviders() {
return this.secretsService.getProviders();
}

@Get('/providers/:provider')
@RequireGlobalScope('externalSecretsProvider:read')
async getProvider(req: ExternalSecretsRequest.GetProvider) {
const providerName = req.params.provider;
try {
return this.secretsService.getProvider(providerName);
} catch (e) {
if (e instanceof ProviderNotFoundError) {
if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
}
throw e;
}
}

@Post('/providers/:provider/test')
@RequireGlobalScope('externalSecretsProvider:read')
async testProviderSettings(req: ExternalSecretsRequest.TestProviderSettings, res: Response) {
const providerName = req.params.provider;
try {
Expand All @@ -41,20 +45,21 @@ export class ExternalSecretsController {
}
return result;
} catch (e) {
if (e instanceof ProviderNotFoundError) {
if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
}
throw e;
}
}

@Post('/providers/:provider')
@RequireGlobalScope('externalSecretsProvider:create')
async setProviderSettings(req: ExternalSecretsRequest.SetProviderSettings) {
const providerName = req.params.provider;
try {
await this.secretsService.saveProviderSettings(providerName, req.body, req.user.id);
} catch (e) {
if (e instanceof ProviderNotFoundError) {
if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
}
throw e;
Expand All @@ -63,12 +68,13 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider/connect')
@RequireGlobalScope('externalSecretsProvider:update')
async setProviderConnected(req: ExternalSecretsRequest.SetProviderConnected) {
const providerName = req.params.provider;
try {
await this.secretsService.saveProviderConnected(providerName, req.body.connected);
} catch (e) {
if (e instanceof ProviderNotFoundError) {
if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
}
throw e;
Expand All @@ -77,6 +83,7 @@ export class ExternalSecretsController {
}

@Post('/providers/:provider/update')
@RequireGlobalScope('externalSecretsProvider:sync')
async updateProvider(req: ExternalSecretsRequest.UpdateProvider, res: Response) {
const providerName = req.params.provider;
try {
Expand All @@ -88,14 +95,15 @@ export class ExternalSecretsController {
}
return { updated: resp };
} catch (e) {
if (e instanceof ProviderNotFoundError) {
if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
}
throw e;
}
}

@Get('/secrets')
@RequireGlobalScope('externalSecret:list')
getSecretNames() {
return this.secretsService.getAllSecrets();
}
Expand Down
Loading

0 comments on commit a1b81c7

Please sign in to comment.