Merge pull request #394 from Hexastack/fix/cors-webchannel
Some checks are pending
Build and Push Docker Images / paths-filter (push) Waiting to run
Build and Push Docker Images / build-and-push (push) Blocked by required conditions

Fix/cors webchannel
This commit is contained in:
Med Marrouchi 2024-12-02 16:33:37 +01:00 committed by GitHub
commit 00c540843f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 106 additions and 31 deletions

View File

@ -134,6 +134,59 @@ describe('WebChannelHandler', () => {
expect(handler.getName()).toEqual('web-channel');
});
it('should allow the request if the origin is in the allowed domains', async () => {
const req = {
headers: {
origin: 'https://example.com',
},
method: 'GET',
} as unknown as Request;
const res = {
set: jest.fn(),
} as any;
jest.spyOn(handler, 'getSettings').mockResolvedValue({
allowed_domains:
'https://example.com/,https://test.com,http://invalid-url',
});
await expect(handler['validateCors'](req, res)).resolves.not.toThrow();
expect(res.set).toHaveBeenCalledWith(
'Access-Control-Allow-Origin',
'https://example.com',
);
expect(res.set).toHaveBeenCalledWith(
'Access-Control-Allow-Credentials',
'true',
);
});
it('should reject the request if the origin is not in the allowed domains', async () => {
const req = {
headers: {
origin: 'https://notallowed.com',
},
method: 'GET',
} as unknown as Request;
jest.spyOn(handler, 'getSettings').mockResolvedValue({
allowed_domains:
'https://example.com/,https://test.com,http://invalid-url',
});
const res = {
set: jest.fn(),
} as any;
await expect(handler['validateCors'](req, res)).rejects.toThrow(
'CORS - Domain not allowed!',
);
expect(res.set).toHaveBeenCalledWith('Access-Control-Allow-Origin', '');
});
it('should format text properly', () => {
const formatted = handler._textFormat(textMessage, {});
expect(formatted).toEqual(webText);

View File

@ -294,42 +294,64 @@ export default abstract class BaseWebChannelHandler<
res: Response | SocketResponse,
) {
const settings = await this.getSettings<typeof WEB_CHANNEL_NAMESPACE>();
// If we have an origin header...
if (req.headers && req.headers.origin) {
// Get the allowed origins
const origins: string[] = settings.allowed_domains.split(',');
const foundOrigin = origins.some((origin: string) => {
origin = origin.trim();
// Check if we have an origin header...
if (!req.headers?.origin) {
this.logger.debug('Web Channel Handler : No origin ', req.headers);
throw new Error('CORS - No origin provided!');
}
const originUrl = new URL(req.headers.origin);
const allowedProtocols = new Set(['http:', 'https:']);
if (!allowedProtocols.has(originUrl.protocol)) {
throw new Error('CORS - Invalid origin!');
}
// Get the allowed origins
const origins: string[] = settings.allowed_domains.split(',');
const foundOrigin = origins
.map((origin) => {
try {
return new URL(origin.trim()).origin;
} catch (error) {
this.logger.error(
`Web Channel Handler : Invalid URL in allowed domains: ${origin}`,
error,
);
return null;
}
})
.filter(
(normalizedOrigin): normalizedOrigin is string =>
normalizedOrigin !== null,
)
.some((origin: string) => {
// If we find a whitelisted origin, send the Access-Control-Allow-Origin header
// to greenlight the request.
return origin == req.headers.origin || origin == '*';
return origin === originUrl.origin;
});
if (!foundOrigin) {
// For HTTP requests, set the Access-Control-Allow-Origin header to '', which the browser will
// interpret as, 'no way Jose.'
res.set('Access-Control-Allow-Origin', '');
this.logger.debug(
'Web Channel Handler : No origin found ',
req.headers.origin,
);
throw new Error('CORS - Domain not allowed!');
} else {
res.set('Access-Control-Allow-Origin', req.headers.origin);
}
// Determine whether or not to allow cookies to be passed cross-origin
res.set('Access-Control-Allow-Credentials', 'true');
// This header lets a server whitelist headers that browsers are allowed to access
res.set('Access-Control-Expose-Headers', '');
// Handle preflight requests
if (req.method == 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'content-type');
}
return;
if (!foundOrigin && !origins.includes('*')) {
// For HTTP requests, set the Access-Control-Allow-Origin header to '', which the browser will
// interpret as, 'no way Jose.'
res.set('Access-Control-Allow-Origin', '');
this.logger.debug(
'Web Channel Handler : No origin found ',
req.headers.origin,
);
throw new Error('CORS - Domain not allowed!');
} else {
res.set('Access-Control-Allow-Origin', originUrl.origin);
}
// Determine whether or not to allow cookies to be passed cross-origin
res.set('Access-Control-Allow-Credentials', 'true');
// This header lets a server whitelist headers that browsers are allowed to access
res.set('Access-Control-Expose-Headers', '');
// Handle preflight requests
if (req.method == 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'content-type');
}
this.logger.debug('Web Channel Handler : No origin ', req.headers);
throw new Error('CORS - No origin provided!');
}
/**