mirror of
https://github.com/hexastack/hexabot
synced 2025-04-10 15:55:55 +00:00
Merge pull request #394 from Hexastack/fix/cors-webchannel
Fix/cors webchannel
This commit is contained in:
commit
00c540843f
@ -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);
|
||||
|
@ -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!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user