mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #3227 from logto-io/gao-fix-local-cors
fix(core): allow localhost CORS when only one endpoint available
This commit is contained in:
commit
75b6d65b3e
16 changed files with 158 additions and 11 deletions
5
.changeset/modern-days-float.md
Normal file
5
.changeset/modern-days-float.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/core": patch
|
||||
---
|
||||
|
||||
Allow localhost CORS when only one endpoint available
|
|
@ -37,6 +37,7 @@
|
|||
"lemon-cars-wonder",
|
||||
"lovely-rivers-sniff",
|
||||
"many-avocados-know",
|
||||
"modern-days-float",
|
||||
"moody-timers-grin",
|
||||
"neat-snakes-smash",
|
||||
"pink-maps-yell",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/cli",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"description": "Logto CLI.",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
### Major Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/console",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"description": "> TODO: description",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5e1466f40: Allow localhost CORS when only one endpoint available
|
||||
- @logto/cli@1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
### Major Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/core",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"description": "The open source identity solution.",
|
||||
"main": "build/index.js",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
|
|
112
packages/core/src/middleware/koa-cors.test.ts
Normal file
112
packages/core/src/middleware/koa-cors.test.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { createMockUtils } from '@logto/shared/esm';
|
||||
import type { RequestMethod } from 'node-mocks-http';
|
||||
|
||||
import GlobalValues from '#src/env-set/GlobalValues.js';
|
||||
import UrlSet from '#src/env-set/UrlSet.js';
|
||||
import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
await mockEsmWithActual('#src/env-set/index.js', () => ({
|
||||
EnvSet: {
|
||||
get values() {
|
||||
return new GlobalValues();
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const { default: koaCors } = await import('./koa-cors.js');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = async () => {};
|
||||
|
||||
const mockContext = (method: RequestMethod, url: string) => {
|
||||
const ctx = createMockContext({ method, url });
|
||||
|
||||
const setSpy = jest.spyOn(ctx, 'set');
|
||||
|
||||
return [ctx, setSpy] as const;
|
||||
};
|
||||
|
||||
const expectCorsHeaders = (setSpy: jest.SpyInstance, origin: string) => {
|
||||
if (origin) {
|
||||
expect(setSpy).toHaveBeenCalledWith('Access-Control-Expose-Headers', '*');
|
||||
expect(setSpy).toHaveBeenCalledWith('Access-Control-Allow-Origin', origin);
|
||||
} else {
|
||||
expect(setSpy).not.toHaveBeenCalledWith(
|
||||
'Access-Control-Expose-Headers',
|
||||
expect.stringMatching('.*')
|
||||
);
|
||||
expect(setSpy).not.toHaveBeenCalledWith(
|
||||
'Access-Control-Allow-Origin',
|
||||
expect.stringMatching('.*')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
describe('koaCors() middleware', () => {
|
||||
const envBackup = Object.freeze({ ...process.env });
|
||||
|
||||
afterEach(() => {
|
||||
process.env = { ...envBackup };
|
||||
});
|
||||
|
||||
it('should set proper CORS response headers for a single URL Set', async () => {
|
||||
const endpoint = 'https://logto.io';
|
||||
process.env.ENDPOINT = endpoint;
|
||||
process.env.NODE_ENV = 'dev';
|
||||
const urlSet = new UrlSet(false, 3001);
|
||||
const run = koaCors(urlSet);
|
||||
|
||||
const [ctx1, setSpy1] = mockContext('GET', endpoint + '/api');
|
||||
await run(ctx1, noop);
|
||||
expectCorsHeaders(setSpy1, endpoint);
|
||||
|
||||
const [ctx2, setSpy2] = mockContext('GET', 'http://localhost:3001/api');
|
||||
await run(ctx2, noop);
|
||||
expectCorsHeaders(setSpy2, 'http://localhost:3001');
|
||||
});
|
||||
|
||||
it('should set proper CORS response headers for multiple URL Sets', async () => {
|
||||
const endpoint = 'https://logto.io';
|
||||
const adminEndpoint = 'https://logto.admin';
|
||||
|
||||
process.env.ENDPOINT = endpoint;
|
||||
process.env.ADMIN_ENDPOINT = adminEndpoint;
|
||||
process.env.NODE_ENV = 'dev';
|
||||
const run = koaCors(new UrlSet(false, 3001), new UrlSet(true, 3002, 'ADMIN_'));
|
||||
|
||||
const [ctx1, setSpy1] = mockContext('PUT', 'https://localhost:3002/api');
|
||||
await run(ctx1, noop);
|
||||
expectCorsHeaders(setSpy1, 'https://localhost:3002');
|
||||
|
||||
const [ctx2, setSpy2] = mockContext('POST', adminEndpoint + '/api');
|
||||
await run(ctx2, noop);
|
||||
expectCorsHeaders(setSpy2, adminEndpoint);
|
||||
});
|
||||
|
||||
it('should set CORS response headers for localhost in production when endpoint is unavailable', async () => {
|
||||
process.env.ENDPOINT = undefined;
|
||||
process.env.NODE_ENV = 'production';
|
||||
const urlSet = new UrlSet(true, 3002);
|
||||
const run = koaCors(urlSet);
|
||||
|
||||
const [ctx, setSpy] = mockContext('POST', 'https://localhost:3002/api');
|
||||
await run(ctx, noop);
|
||||
expectCorsHeaders(setSpy, 'https://localhost:3002');
|
||||
});
|
||||
|
||||
it('should not to set CORS response headers for localhost in production when endpoint is available', async () => {
|
||||
const endpoint = 'https://logto.io';
|
||||
process.env.ENDPOINT = endpoint;
|
||||
process.env.NODE_ENV = 'production';
|
||||
const urlSet = new UrlSet(false, 3001);
|
||||
const run = koaCors(urlSet);
|
||||
|
||||
const [ctx, setSpy] = mockContext('DELETE', 'http://localhost:3001/api');
|
||||
await run(ctx, noop);
|
||||
expectCorsHeaders(setSpy, '');
|
||||
});
|
||||
});
|
|
@ -9,18 +9,26 @@ export default function koaCors<StateT, ContextT, ResponseBodyT>(
|
|||
): MiddlewareType<StateT, ContextT, ResponseBodyT> {
|
||||
return cors({
|
||||
origin: (ctx) => {
|
||||
const { origin } = ctx.request.headers;
|
||||
const { origin } = ctx;
|
||||
|
||||
if (
|
||||
origin &&
|
||||
urlSets.some((set) =>
|
||||
set.deduplicated().some(
|
||||
urlSets.some((set) => {
|
||||
const deduplicated = set.deduplicated();
|
||||
|
||||
// The URL Set has only one endpoint available, just use that endpoint.
|
||||
if (deduplicated.length <= 1) {
|
||||
return deduplicated.some((url) => url.origin === origin);
|
||||
}
|
||||
|
||||
// For multiple endpoints, should filter out localhost in production.
|
||||
return deduplicated.some(
|
||||
(url) =>
|
||||
url.origin === origin &&
|
||||
// Disable localhost CORS in production since it's unsafe
|
||||
!(EnvSet.values.isProduction && url.hostname === 'localhost')
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
) {
|
||||
return origin;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @logto/cli@1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/create",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
## 1.0.0-rc.1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/integration-tests",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"description": "Integration tests for Logto.",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Change Log
|
||||
|
||||
## 1.0.0-rc.3
|
||||
|
||||
## 1.0.0-rc.2
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/ui",
|
||||
"version": "1.0.0-rc.2",
|
||||
"version": "1.0.0-rc.3",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
|
Loading…
Reference in a new issue