0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00

fix(core): allow all origins for profile and verification api ()

This commit is contained in:
wangsijie 2024-11-15 10:53:08 +08:00 committed by GitHub
parent 2791fca224
commit 06a1bd1394
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 86 additions and 56 deletions
packages
core/src
integration-tests/src/tests/api/profile

View file

@ -52,60 +52,72 @@ describe('koaCors() middleware', () => {
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);
describe('with URL sets', () => {
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 [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');
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, '');
});
});
it('should set proper CORS response headers for multiple URL Sets', async () => {
const endpoint = 'https://logto.io';
const adminEndpoint = 'https://logto.admin';
describe('with allowed prefixes', () => {
it('should allow any origin if the path starts with an allowed prefix', async () => {
const run = koaCors([], ['/api']);
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, '');
const [ctx, setSpy] = mockContext('GET', 'https://logto.io/api');
await run(ctx, noop);
expectCorsHeaders(setSpy, 'https://logto.io');
});
});
});

View file

@ -5,11 +5,20 @@ import type { MiddlewareType } from 'koa';
import { EnvSet } from '#src/env-set/index.js';
export default function koaCors<StateT, ContextT, ResponseBodyT>(
...urlSets: UrlSet[]
urlSets: UrlSet[],
allowedPrefixes: string[] = []
): MiddlewareType<StateT, ContextT, ResponseBodyT> {
return cors({
origin: (ctx) => {
const { origin } = ctx.request.headers;
const {
headers: { origin },
path,
} = ctx.request;
// Allow any origin if the path starts with an allowed prefix
if (allowedPrefixes.some((prefix) => path.startsWith(prefix))) {
return origin ?? '*';
}
if (!EnvSet.values.isProduction) {
return origin ?? '';

View file

@ -40,7 +40,7 @@ export default function initMeApis(tenant: TenantContext): Koa {
userAssetsRoutes(meRouter, tenant);
const meApp = new Koa();
meApp.use(koaCors(EnvSet.values.cloudUrlSet));
meApp.use(koaCors([EnvSet.values.cloudUrlSet]));
meApp.use(meRouter.routes()).use(meRouter.allowedMethods());
return meApp;

View file

@ -5,13 +5,13 @@ import Router from 'koa-router';
import { EnvSet } from '#src/env-set/index.js';
import koaAuditLog from '#src/middleware/koa-audit-log.js';
import koaBodyEtag from '#src/middleware/koa-body-etag.js';
import koaCors from '#src/middleware/koa-cors.js';
import { koaManagementApiHooks } from '#src/middleware/koa-management-api-hooks.js';
import koaTenantGuard from '#src/middleware/koa-tenant-guard.js';
import type TenantContext from '#src/tenants/TenantContext.js';
import koaAuth from '../middleware/koa-auth/index.js';
import koaOidcAuth from '../middleware/koa-auth/koa-oidc-auth.js';
import koaCors from '../middleware/koa-cors.js';
import accountCentersRoutes from './account-center/index.js';
import adminUserRoutes from './admin-user/index.js';
@ -135,9 +135,8 @@ const createRouters = (tenant: TenantContext) => {
export default function initApis(tenant: TenantContext): Koa {
const apisApp = new Koa();
const { adminUrlSet, cloudUrlSet } = EnvSet.values;
apisApp.use(koaCors(adminUrlSet, cloudUrlSet));
apisApp.use(koaCors([adminUrlSet, cloudUrlSet], ['/profile', '/verifications']));
apisApp.use(koaBodyEtag());
for (const router of createRouters(tenant)) {

View file

@ -48,6 +48,16 @@ describe('profile', () => {
});
describe('GET /profile', () => {
it('should allow all origins', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password);
const response = await api.get('api/profile');
expect(response.status).toBe(200);
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
await deleteDefaultTenantUser(user.id);
});
it('should be able to get profile with default scopes', async () => {
const { user, username, password } = await createDefaultTenantUserWithPassword();
const api = await signInAndGetUserApi(username, password);