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:
committed by
GitHub
parent
1fc17b5d81
commit
7e1a13f9b2
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user