diff --git a/packages/cli/package.json b/packages/cli/package.json index 2572a67b26..68d6c2b3fa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -173,7 +173,7 @@ "shelljs": "^0.8.5", "source-map-support": "^0.5.21", "sqlite3": "^5.1.2", - "sse-channel": "^3.1.1", + "sse-channel": "^4.0.0", "swagger-ui-express": "^4.3.0", "tslib": "1.14.1", "typeorm": "0.2.45", diff --git a/packages/cli/src/Push.ts b/packages/cli/src/Push.ts index 89fba39248..4179b161f8 100644 --- a/packages/cli/src/Push.ts +++ b/packages/cli/src/Push.ts @@ -1,48 +1,20 @@ -// @ts-ignore -import sseChannel from 'sse-channel'; -import express from 'express'; +import SSEChannel from 'sse-channel'; +import type { Request, Response } from 'express'; import { LoggerProxy as Logger } from 'n8n-workflow'; import type { IPushData, IPushDataType } from '@/Interfaces'; -interface SSEChannelOptions { - cors?: { - origins: string[]; - }; -} - -namespace SSE { - export type Channel = { - on(event: string, handler: (channel: string, res: express.Response) => void): void; - removeClient: (res: express.Response) => void; - addClient: (req: express.Request, res: express.Response) => void; - send: (msg: string, clients?: express.Response[]) => void; - }; -} - export class Push { - private channel: SSE.Channel; + private channel = new SSEChannel(); - private connections: { - [key: string]: express.Response; - } = {}; + private connections: Record = {}; constructor() { - const options: SSEChannelOptions = {}; - if (process.env.NODE_ENV !== 'production') { - options.cors = { - // Allow access also from frontend when developing - origins: ['http://localhost:8080'], - }; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - this.channel = new sseChannel(options) as SSE.Channel; - - this.channel.on('disconnect', (channel: string, res: express.Response) => { + this.channel.on('disconnect', (channel: string, res: Response) => { if (res.req !== undefined) { - Logger.debug(`Remove editor-UI session`, { sessionId: res.req.query.sessionId }); - delete this.connections[res.req.query.sessionId as string]; + const { sessionId } = res.req.query; + Logger.debug(`Remove editor-UI session`, { sessionId }); + delete this.connections[sessionId as string]; } }); } @@ -51,10 +23,10 @@ export class Push { * Adds a new push connection * * @param {string} sessionId The id of the session - * @param {express.Request} req The request - * @param {express.Response} res The response + * @param {Request} req The request + * @param {Response} res The response */ - add(sessionId: string, req: express.Request, res: express.Response) { + add(sessionId: string, req: Request, res: Response) { Logger.debug(`Add editor-UI session`, { sessionId }); if (this.connections[sessionId] !== undefined) { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 1e96f9baac..d3da7849fa 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -158,6 +158,7 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData' import { toHttpNodeParameters } from '@/CurlConverterHelper'; import { setupErrorMiddleware } from '@/ErrorReporting'; import { getLicense } from '@/License'; +import { corsMiddleware } from './middlewares/cors'; require('body-parser-xml')(bodyParser); @@ -624,30 +625,25 @@ class App { this.app.use(cookieParser()); // Get push connections - this.app.use( - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (req.url.indexOf(`/${this.restEndpoint}/push`) === 0) { - if (req.query.sessionId === undefined) { - next(new Error('The query parameter "sessionId" is missing!')); - return; - } + this.app.use(`/${this.restEndpoint}/push`, corsMiddleware, async (req, res, next) => { + const { sessionId } = req.query; + if (sessionId === undefined) { + next(new Error('The query parameter "sessionId" is missing!')); + return; + } - if (isUserManagementEnabled()) { - try { - const authCookie = req.cookies?.[AUTH_COOKIE_NAME] ?? ''; - await resolveJwt(authCookie); - } catch (error) { - res.status(401).send('Unauthorized'); - return; - } - } - - this.push.add(req.query.sessionId as string, req, res); + if (isUserManagementEnabled()) { + try { + const authCookie = req.cookies?.[AUTH_COOKIE_NAME] ?? ''; + await resolveJwt(authCookie); + } catch (error) { + res.status(401).send('Unauthorized'); return; } - next(); - }, - ); + } + + this.push.add(sessionId as string, req, res); + }); // Compress the response data this.app.use(compression()); @@ -719,19 +715,7 @@ class App { }), ); - if (process.env.NODE_ENV !== 'production') { - this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - // Allow access also from frontend when developing - res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); - res.header('Access-Control-Allow-Credentials', 'true'); - res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.header( - 'Access-Control-Allow-Headers', - 'Origin, X-Requested-With, Content-Type, Accept, sessionid', - ); - next(); - }); - } + this.app.use(corsMiddleware); // eslint-disable-next-line consistent-return this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { diff --git a/packages/cli/src/WebhookServer.ts b/packages/cli/src/WebhookServer.ts index 5fbfbaa3e9..cedf2b4712 100644 --- a/packages/cli/src/WebhookServer.ts +++ b/packages/cli/src/WebhookServer.ts @@ -27,6 +27,7 @@ import type { ICustomRequest, IExternalHooksClass, IPackageVersions } from '@/In import config from '@/config'; import { WEBHOOK_METHODS } from '@/WebhookHelpers'; import { setupErrorMiddleware } from '@/ErrorReporting'; +import { corsMiddleware } from './middlewares/cors'; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call require('body-parser-xml')(bodyParser); @@ -278,18 +279,7 @@ class App { }), ); - if (process.env.NODE_ENV !== 'production') { - this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { - // Allow access also from frontend when developing - res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); - res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.header( - 'Access-Control-Allow-Headers', - 'Origin, X-Requested-With, Content-Type, Accept, sessionid', - ); - next(); - }); - } + this.app.use(corsMiddleware); this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { if (!Db.isInitialized) { diff --git a/packages/cli/src/middlewares/cors.ts b/packages/cli/src/middlewares/cors.ts new file mode 100644 index 0000000000..00d2ed3434 --- /dev/null +++ b/packages/cli/src/middlewares/cors.ts @@ -0,0 +1,18 @@ +import type { RequestHandler } from 'express'; + +const { NODE_ENV } = process.env; +const inDevelopment = !NODE_ENV || NODE_ENV === 'development'; + +export const corsMiddleware: RequestHandler = (req, res, next) => { + if (inDevelopment && 'origin' in req.headers) { + // Allow access also from frontend when developing + res.header('Access-Control-Allow-Origin', req.headers.origin); + res.header('Access-Control-Allow-Credentials', 'true'); + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + res.header( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept, sessionid', + ); + } + next(); +}; diff --git a/packages/cli/src/sse-channel.d.ts b/packages/cli/src/sse-channel.d.ts new file mode 100644 index 0000000000..2dc8ef5e56 --- /dev/null +++ b/packages/cli/src/sse-channel.d.ts @@ -0,0 +1,17 @@ +import type { Request, Response } from 'express'; + +declare module 'sse-channel' { + declare class Channel { + constructor(); + + on(event: string, handler: (channel: string, res: Response) => void): void; + + removeClient: (res: Response) => void; + + addClient: (req: Request, res: Response) => void; + + send: (msg: string, clients?: Response[]) => void; + } + + export = Channel; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18bec9067d..cd25e75922 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,7 +195,7 @@ importers: shelljs: ^0.8.5 source-map-support: ^0.5.21 sqlite3: ^5.1.2 - sse-channel: ^3.1.1 + sse-channel: ^4.0.0 supertest: ^6.2.2 swagger-ui-express: ^4.3.0 ts-node: ^9.1.1 @@ -278,7 +278,7 @@ importers: shelljs: 0.8.5 source-map-support: 0.5.21 sqlite3: 5.1.2 - sse-channel: 3.1.1 + sse-channel: 4.0.0 swagger-ui-express: 4.5.0_express@4.18.2 tslib: 1.14.1 typeorm: 0.2.45_6spgkqhramqg35yodisibk43rm @@ -6848,14 +6848,6 @@ packages: mime-types: 2.1.35 negotiator: 0.6.3 - /access-control/1.0.1: - resolution: {integrity: sha512-H5aqjkogmFxfaOrfn/e42vyspHVXuJ8er63KuljJXpOyJ1ZO/U5CrHfO8BLKIy2w7mBM02L5quL0vbfQqrGQbA==} - dependencies: - millisecond: 0.1.2 - setheader: 1.0.2 - vary: 1.1.2 - dev: false - /acorn-globals/6.0.0: resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} dependencies: @@ -8951,10 +8943,6 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true - /colornames/1.1.1: - resolution: {integrity: sha512-/pyV40IrsdulWv+wFPmERh9k/mjsPZ64yUMDmWrtj/k1nmgrzzIENWKdaVKyBbvFdQWqkcaRxr+polCo3VMe7A==} - dev: false - /colorspace/1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} dependencies: @@ -10096,14 +10084,6 @@ packages: wrappy: 1.0.2 dev: true - /diagnostics/1.1.1: - resolution: {integrity: sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==} - dependencies: - colorspace: 1.1.4 - enabled: 1.0.2 - kuler: 1.0.1 - dev: false - /diff-sequences/28.1.1: resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -10390,12 +10370,6 @@ packages: engines: {node: '>= 4'} dev: true - /enabled/1.0.2: - resolution: {integrity: sha512-nnzgVSpB35qKrUN8358SjO1bYAmxoThECTWw9s3J0x5G8A9hokKHVDFzBjVpCoSryo6MhN8woVyascN5jheaNA==} - dependencies: - env-variable: 0.0.6 - dev: false - /enabled/2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: false @@ -10467,10 +10441,6 @@ packages: dev: false optional: true - /env-variable/0.0.6: - resolution: {integrity: sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==} - dev: false - /err-code/2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} dev: false @@ -14675,12 +14645,6 @@ packages: engines: {node: '>= 8'} dev: true - /kuler/1.0.1: - resolution: {integrity: sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==} - dependencies: - colornames: 1.1.1 - dev: false - /kuler/2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false @@ -15509,10 +15473,6 @@ packages: brorand: 1.1.0 dev: true - /millisecond/0.1.2: - resolution: {integrity: sha512-BJ8XtxY+woL+5TkP6uS6XvOArm0JVrX2otkgtWZseHpIax0oOOPW3cnwhOjRqbEJg7YRO/BDF7fO/PTWNT3T9Q==} - dev: false - /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -18903,12 +18863,6 @@ packages: split-string: 3.1.0 dev: true - /setheader/1.0.2: - resolution: {integrity: sha512-A704nIwzqGed0CnJZIqDE+0udMPS839ocgf1R9OJ8aq8vw4U980HWeNaD9ec8VnmBni9lyGEWDedOWXT/C5kxA==} - dependencies: - diagnostics: 1.1.1 - dev: false - /setimmediate/1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} dev: true @@ -19327,11 +19281,8 @@ packages: engines: {node: '>= 0.6'} dev: false - /sse-channel/3.1.1: - resolution: {integrity: sha512-vgf4QFh60vlAMX0vGJpn6S+7gTO3ckRn7xq4DOgQGcgDs7ULBkaQFQxy4b3vj/umyk0ydhGu7i4A1nHQc5HcYw==} - dependencies: - access-control: 1.0.1 - lodash: 4.17.21 + /sse-channel/4.0.0: + resolution: {integrity: sha512-I539Tc0gyDTQ2QCSg4v78Flxo/UbqR9x7JoyPcqaPtwo+qzeOw/fF+aPSbk0xTvBQAAAZk7Dlkc8K1bum5GUnw==} dev: false /ssf/0.11.2: