Skip to content

Commit

Permalink
Merge branch 'master' into ADO-2149-update-webhook-path-when-duplicating
Browse files Browse the repository at this point in the history
* master:
  feat(editor): Show expression infobox on hover and cursor position (#9507)
  fix(core): Block Public API related REST calls when Public API is not enabled (#9521)
  test(core): Align test names with route names (no-changelog) (#9518)
  refactor(core): Prevent reporting to Sentry IMAP server error (no-changelog) (#9515)
  fix(editor): Executions view popup in dark mode (#9517)
  refactor: Delete dead crash recovery code (no-changelog) (#9512)
  • Loading branch information
MiloradFilipovic committed May 29, 2024
2 parents fa9a982 + ec0373f commit 9639b8f
Show file tree
Hide file tree
Showing 30 changed files with 758 additions and 281 deletions.
7 changes: 1 addition & 6 deletions packages/cli/src/PublicApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,6 @@ export const loadPublicApiVersions = async (
};
};

function isApiEnabledByLicense(): boolean {
const license = Container.get(License);
return !license.isAPIDisabled();
}

export function isApiEnabled(): boolean {
return !config.get('publicApi.disabled') && isApiEnabledByLicense();
return !config.get('publicApi.disabled') && !Container.get(License).isAPIDisabled();
}
17 changes: 13 additions & 4 deletions packages/cli/src/controllers/me.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import validator from 'validator';
import { plainToInstance } from 'class-transformer';
import { Response } from 'express';
import { type RequestHandler, Response } from 'express';
import { randomBytes } from 'crypto';

import { AuthService } from '@/auth/auth.service';
Expand All @@ -22,6 +22,15 @@ import { ExternalHooks } from '@/ExternalHooks';
import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UserRepository } from '@/databases/repositories/user.repository';
import { isApiEnabled } from '@/PublicApi';

export const isApiEnabledMiddleware: RequestHandler = (_, res, next) => {
if (isApiEnabled()) {
next();
} else {
res.status(404).end();
}
};

@RestController('/me')
export class MeController {
Expand Down Expand Up @@ -185,7 +194,7 @@ export class MeController {
/**
* Creates an API Key
*/
@Post('/api-key')
@Post('/api-key', { middlewares: [isApiEnabledMiddleware] })
async createAPIKey(req: AuthenticatedRequest) {
const apiKey = `n8n_api_${randomBytes(40).toString('hex')}`;

Expand All @@ -202,15 +211,15 @@ export class MeController {
/**
* Get an API Key
*/
@Get('/api-key')
@Get('/api-key', { middlewares: [isApiEnabledMiddleware] })
async getAPIKey(req: AuthenticatedRequest) {
return { apiKey: req.user.apiKey };
}

/**
* Deletes an API Key
*/
@Delete('/api-key')
@Delete('/api-key', { middlewares: [isApiEnabledMiddleware] })
async deleteAPIKey(req: AuthenticatedRequest) {
await this.userService.update(req.user.id, { apiKey: null });

Expand Down
8 changes: 0 additions & 8 deletions packages/cli/src/eventbus/EventMessageClasses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,3 @@ export type EventMessageTypes =
| EventMessageAudit
| EventMessageNode
| EventMessageAiNode;

export interface FailedEventSummary {
lastNodeExecuted: string;
executionId: string;
name: string;
event: string;
timestamp: string;
}
48 changes: 1 addition & 47 deletions packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { OrchestrationService } from '@/services/orchestration.service';
import { Logger } from '@/Logger';

import type {
EventMessageTypes,
EventNamesTypes,
FailedEventSummary,
} from '../EventMessageClasses/';
import type { EventMessageTypes } from '../EventMessageClasses/';
import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee';
import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventBusLogWriter';
import { messageEventBusDestinationFromDb } from '../MessageEventBusDestination/MessageEventBusDestinationFromDb';
Expand Down Expand Up @@ -361,48 +357,6 @@ export class MessageEventBus extends EventEmitter {
);
}

async getEventsFailed(amount = 5): Promise<FailedEventSummary[]> {
const result: FailedEventSummary[] = [];
try {
const queryResult = await this.logWriter?.getMessagesAll();
const uniques = uniqby(queryResult, 'id');
const filteredExecutionIds = uniques
.filter((e) =>
(['n8n.workflow.crashed', 'n8n.workflow.failed'] as EventNamesTypes[]).includes(
e.eventName,
),
)
.map((e) => ({
executionId: e.payload.executionId as string,
name: e.payload.workflowName,
timestamp: e.ts,
event: e.eventName,
}))
.filter((e) => e)
.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1))
.slice(-amount);

for (const execution of filteredExecutionIds) {
const data = await this.recoveryService.recoverExecutionData(
execution.executionId,
queryResult,
false,
);
if (data) {
const lastNodeExecuted = data.resultData.lastNodeExecuted;
result.push({
lastNodeExecuted: lastNodeExecuted ?? '',
executionId: execution.executionId,
name: execution.name as string,
event: execution.event,
timestamp: execution.timestamp.toISO(),
});
}
}
} catch {}
return result;
}

async getEventsAll(): Promise<EventMessageTypes[]> {
const queryResult = await this.logWriter?.getMessagesAll();
const filtered = uniqby(queryResult, 'id');
Expand Down
31 changes: 2 additions & 29 deletions packages/cli/src/eventbus/eventBus.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import express from 'express';
import type { IRunExecutionData } from 'n8n-workflow';
import { EventMessageTypeNames } from 'n8n-workflow';

import { RestController, Get, Post, GlobalScope } from '@/decorators';
Expand All @@ -11,13 +10,12 @@ import type { EventMessageWorkflowOptions } from './EventMessageClasses/EventMes
import { EventMessageWorkflow } from './EventMessageClasses/EventMessageWorkflow';
import type { EventMessageReturnMode } from './MessageEventBus/MessageEventBus';
import { MessageEventBus } from './MessageEventBus/MessageEventBus';
import type { EventMessageTypes, FailedEventSummary } from './EventMessageClasses';
import type { EventMessageTypes } from './EventMessageClasses';
import { eventNamesAll } from './EventMessageClasses';
import type { EventMessageAuditOptions } from './EventMessageClasses/EventMessageAudit';
import { EventMessageAudit } from './EventMessageClasses/EventMessageAudit';
import type { EventMessageNodeOptions } from './EventMessageClasses/EventMessageNode';
import { EventMessageNode } from './EventMessageClasses/EventMessageNode';
import { ExecutionDataRecoveryService } from './executionDataRecovery.service';

// ----------------------------------------
// TypeGuards
Expand All @@ -35,10 +33,7 @@ const isWithQueryString = (candidate: unknown): candidate is { query: string } =

@RestController('/eventbus')
export class EventBusController {
constructor(
private readonly eventBus: MessageEventBus,
private readonly recoveryService: ExecutionDataRecoveryService,
) {}
constructor(private readonly eventBus: MessageEventBus) {}

// ----------------------------------------
// Events
Expand All @@ -65,13 +60,6 @@ export class EventBusController {
}
}

@Get('/failed')
@GlobalScope('eventBusEvent:list')
async getFailedEvents(req: express.Request): Promise<FailedEventSummary[]> {
const amount = parseInt(req.query?.amount as string) ?? 5;
return await this.eventBus.getEventsFailed(amount);
}

@Get('/execution/:id')
@GlobalScope('eventBusEvent:read')
async getEventForExecutionId(req: express.Request): Promise<EventMessageTypes[] | undefined> {
Expand All @@ -85,21 +73,6 @@ export class EventBusController {
return;
}

@Get('/execution-recover/:id')
@GlobalScope('eventBusEvent:read')
async getRecoveryForExecutionId(req: express.Request): Promise<IRunExecutionData | undefined> {
const { id } = req.params;
if (req.params?.id) {
const logHistory = parseInt(req.query.logHistory as string, 10) || undefined;
const applyToDb = req.query.applyToDb !== undefined ? !!req.query.applyToDb : true;
const messages = await this.eventBus.getEventsByExecutionId(id, logHistory);
if (messages.length > 0) {
return await this.recoveryService.recoverExecutionData(id, messages, applyToDb);
}
}
return;
}

@Post('/event')
@GlobalScope('eventBusEvent:create')
async postEvent(req: express.Request): Promise<EventMessageTypes | undefined> {
Expand Down
2 changes: 0 additions & 2 deletions packages/cli/src/permissions/global-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
'eventBusEvent:read',
'eventBusEvent:update',
'eventBusEvent:delete',
'eventBusEvent:list',
'eventBusEvent:query',
'eventBusEvent:create',
'eventBusDestination:create',
Expand Down Expand Up @@ -80,7 +79,6 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
export const GLOBAL_ADMIN_SCOPES = GLOBAL_OWNER_SCOPES.concat();

export const GLOBAL_MEMBER_SCOPES: Scope[] = [
'eventBusEvent:list',
'eventBusEvent:read',
'eventBusDestination:list',
'eventBusDestination:test',
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/services/frontend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { CommunityPackagesService } from '@/services/communityPackages.serv
import { Logger } from '@/Logger';
import { UrlService } from './url.service';
import { InternalHooks } from '@/InternalHooks';
import { isApiEnabled } from '@/PublicApi';

@Service()
export class FrontendService {
Expand Down Expand Up @@ -143,7 +144,7 @@ export class FrontendService {
},
},
publicApi: {
enabled: !config.get('publicApi.disabled') && !this.license.isAPIDisabled(),
enabled: isApiEnabled(),
latestVersion: 1,
path: config.getEnv('publicApi.path'),
swaggerUi: {
Expand Down
36 changes: 32 additions & 4 deletions packages/cli/test/integration/me.api.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Container } from 'typedi';
import type { SuperAgentTest } from 'supertest';
import { IsNull } from '@n8n/typeorm';
import validator from 'validator';

import type { User } from '@db/entities/User';
import { UserRepository } from '@db/repositories/user.repository';
import { ProjectRepository } from '@db/repositories/project.repository';

import { SUCCESS_RESPONSE_BODY } from './shared/constants';
import {
randomApiKey,
Expand All @@ -12,15 +17,38 @@ import {
} from './shared/random';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils/';
import { addApiKey, createUser, createUserShell } from './shared/db/users';
import Container from 'typedi';
import { UserRepository } from '@db/repositories/user.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { addApiKey, createOwner, createUser, createUserShell } from './shared/db/users';
import config from '@/config';

const testServer = utils.setupTestServer({ endpointGroups: ['me'] });

beforeEach(async () => {
await testDb.truncate(['User']);
config.set('publicApi.disabled', false);
});

describe('When public API is disabled', () => {
let owner: User;
let authAgent: SuperAgentTest;

beforeEach(async () => {
owner = await createOwner();
await addApiKey(owner);
authAgent = testServer.authAgentFor(owner);
config.set('publicApi.disabled', true);
});

test('POST /me/api-key should 404', async () => {
await authAgent.post('/me/api-key').expect(404);
});

test('GET /me/api-key should 404', async () => {
await authAgent.get('/me/api-key').expect(404);
});

test('DELETE /me/api-key should 404', async () => {
await authAgent.delete('/me/api-key').expect(404);
});
});

describe('Owner shell', () => {
Expand Down
Loading

0 comments on commit 9639b8f

Please sign in to comment.