mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
fix: Upgrade sse-channel to mitigate CVE-2019-10744 (#4835)
sse-channel 4 removed CORS support, that's why we need to handle CORS for `/push` ourselves now.
This commit is contained in:
parent
1fc17b5d81
commit
7e1a13f9b2
|
@ -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",
|
||||
|
|
|
@ -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<string, Response> = {};
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
18
packages/cli/src/middlewares/cors.ts
Normal file
18
packages/cli/src/middlewares/cors.ts
Normal file
|
@ -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();
|
||||
};
|
17
packages/cli/src/sse-channel.d.ts
vendored
Normal file
17
packages/cli/src/sse-channel.d.ts
vendored
Normal file
|
@ -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;
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue