1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-06 09:37:36 +03: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:
कारतोफ्फेलस्क्रिप्ट™ 2022-12-07 15:13:36 +01:00 committed by GitHub
parent 1fc17b5d81
commit 7e1a13f9b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 139 deletions

View File

@ -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",

View File

@ -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) {

View File

@ -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) => {

View File

@ -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) {

View 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
View 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;
}

View File

@ -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: