|
1 | 1 | // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
2 | 2 |
|
3 | | -import { describe, it, expect, vi, beforeEach } from 'vitest'; |
| 3 | +import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest'; |
4 | 4 | import { Hono } from 'hono'; |
5 | 5 |
|
6 | 6 | // Mock dispatcher instance accessible across tests |
@@ -947,4 +947,82 @@ describe('createHonoApp', () => { |
947 | 947 | expect(json.success).toBe(true); |
948 | 948 | }); |
949 | 949 | }); |
| 950 | + |
| 951 | + describe('CORS wildcard origin patterns', () => { |
| 952 | + const ORIG_CORS_ORIGIN = process.env.CORS_ORIGIN; |
| 953 | + const ORIG_CORS_CREDENTIALS = process.env.CORS_CREDENTIALS; |
| 954 | + |
| 955 | + beforeEach(() => { |
| 956 | + delete process.env.CORS_ORIGIN; |
| 957 | + delete process.env.CORS_CREDENTIALS; |
| 958 | + }); |
| 959 | + |
| 960 | + afterAll(() => { |
| 961 | + if (ORIG_CORS_ORIGIN === undefined) delete process.env.CORS_ORIGIN; |
| 962 | + else process.env.CORS_ORIGIN = ORIG_CORS_ORIGIN; |
| 963 | + if (ORIG_CORS_CREDENTIALS === undefined) delete process.env.CORS_CREDENTIALS; |
| 964 | + else process.env.CORS_CREDENTIALS = ORIG_CORS_CREDENTIALS; |
| 965 | + }); |
| 966 | + |
| 967 | + it('matches subdomain wildcard (https://*.example.com) for real subdomains', async () => { |
| 968 | + process.env.CORS_ORIGIN = 'https://*.example.com'; |
| 969 | + const app = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' }); |
| 970 | + |
| 971 | + const res = await app.request('/api/v1/meta', { |
| 972 | + method: 'GET', |
| 973 | + headers: { Origin: 'https://app.example.com' }, |
| 974 | + }); |
| 975 | + expect(res.headers.get('access-control-allow-origin')).toBe('https://app.example.com'); |
| 976 | + }); |
| 977 | + |
| 978 | + it('matches port wildcard (http://localhost:*) for any localhost port', async () => { |
| 979 | + process.env.CORS_ORIGIN = 'http://localhost:*'; |
| 980 | + const app = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' }); |
| 981 | + |
| 982 | + const res = await app.request('/api/v1/meta', { |
| 983 | + method: 'GET', |
| 984 | + headers: { Origin: 'http://localhost:5173' }, |
| 985 | + }); |
| 986 | + expect(res.headers.get('access-control-allow-origin')).toBe('http://localhost:5173'); |
| 987 | + }); |
| 988 | + |
| 989 | + it('matches the correct pattern from a comma-separated wildcard list', async () => { |
| 990 | + process.env.CORS_ORIGIN = |
| 991 | + 'https://*.objectui.org,https://*.objectstack.ai,http://localhost:*'; |
| 992 | + const app = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' }); |
| 993 | + |
| 994 | + const res = await app.request('/api/v1/meta', { |
| 995 | + method: 'GET', |
| 996 | + headers: { Origin: 'https://studio.objectstack.ai' }, |
| 997 | + }); |
| 998 | + expect(res.headers.get('access-control-allow-origin')).toBe('https://studio.objectstack.ai'); |
| 999 | + }); |
| 1000 | + |
| 1001 | + it('rejects origins that do not match any wildcard pattern', async () => { |
| 1002 | + process.env.CORS_ORIGIN = 'https://*.example.com'; |
| 1003 | + const app = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' }); |
| 1004 | + |
| 1005 | + const res = await app.request('/api/v1/meta', { |
| 1006 | + method: 'GET', |
| 1007 | + headers: { Origin: 'https://evil.com' }, |
| 1008 | + }); |
| 1009 | + // Hono's cors() returns no allow-origin header when the matcher rejects |
| 1010 | + expect(res.headers.get('access-control-allow-origin')).toBeNull(); |
| 1011 | + }); |
| 1012 | + |
| 1013 | + it('responds to preflight OPTIONS with matched wildcard origin', async () => { |
| 1014 | + process.env.CORS_ORIGIN = 'https://*.objectui.org'; |
| 1015 | + const app = createHonoApp({ kernel: mockKernel, prefix: '/api/v1' }); |
| 1016 | + |
| 1017 | + const res = await app.request('/api/v1/meta', { |
| 1018 | + method: 'OPTIONS', |
| 1019 | + headers: { |
| 1020 | + Origin: 'https://app.objectui.org', |
| 1021 | + 'Access-Control-Request-Method': 'POST', |
| 1022 | + }, |
| 1023 | + }); |
| 1024 | + expect(res.status).toBe(204); |
| 1025 | + expect(res.headers.get('access-control-allow-origin')).toBe('https://app.objectui.org'); |
| 1026 | + }); |
| 1027 | + }); |
950 | 1028 | }); |
0 commit comments