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 (#6799)
This commit is contained in:
parent
2791fca224
commit
06a1bd1394
5 changed files with 86 additions and 56 deletions
packages
core/src
integration-tests/src/tests/api/profile
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 ?? '';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue