gongxiangp2/lib/__tests__/cors-origin.test.ts

124 lines
4.2 KiB
TypeScript

/** @jest-environment node */
import {
getAllowedCorsOrigins,
getRequestHostOrigin,
isAllowedCorsOrigin,
} from '@/lib/cors-origin'
const originalAppPublicUrl = process.env.APP_PUBLIC_URL
const originalNextAuthUrl = process.env.NEXTAUTH_URL
const originalCorsAllowedOrigins = process.env.CORS_ALLOWED_ORIGINS
const originalNextPublicAppUrl = process.env.NEXT_PUBLIC_APP_PUBLIC_URL
function createRequest(headers: Record<string, string>, nextUrlOrigin = 'http://127.0.0.1:3000') {
return {
headers: {
get(name: string) {
return headers[name.toLowerCase()] ?? null
},
},
nextUrl: {
origin: nextUrlOrigin,
protocol: new URL(nextUrlOrigin).protocol,
},
url: `${nextUrlOrigin}/api/work-review/projects`,
}
}
describe('cors origin helpers', () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_APP_PUBLIC_URL = 'https://sc.jiansun.vip'
process.env.APP_PUBLIC_URL = 'https://sc.jiansun.vip'
process.env.NEXTAUTH_URL = 'https://sc.jiansun.vip'
process.env.CORS_ALLOWED_ORIGINS = ''
})
afterAll(() => {
if (originalNextPublicAppUrl === undefined) delete process.env.NEXT_PUBLIC_APP_PUBLIC_URL
else process.env.NEXT_PUBLIC_APP_PUBLIC_URL = originalNextPublicAppUrl
if (originalAppPublicUrl === undefined) delete process.env.APP_PUBLIC_URL
else process.env.APP_PUBLIC_URL = originalAppPublicUrl
if (originalNextAuthUrl === undefined) delete process.env.NEXTAUTH_URL
else process.env.NEXTAUTH_URL = originalNextAuthUrl
if (originalCorsAllowedOrigins === undefined) delete process.env.CORS_ALLOWED_ORIGINS
else process.env.CORS_ALLOWED_ORIGINS = originalCorsAllowedOrigins
})
test('prefers forwarded public host origin when nextUrl resolves to an internal address', () => {
const request = createRequest({
host: 'sc.jiansun.vip',
'x-forwarded-proto': 'https',
origin: 'https://sc.jiansun.vip',
})
expect(getRequestHostOrigin(request)).toBe('https://sc.jiansun.vip')
expect(Array.from(getAllowedCorsOrigins(request))).toContain('https://sc.jiansun.vip')
expect(isAllowedCorsOrigin(request, 'https://sc.jiansun.vip')).toBe(true)
})
test('allows trusted intranet entry origins when host headers point to the intranet domain', () => {
const request = createRequest(
{
host: '192.168.58.20:4320',
'x-forwarded-proto': 'http',
origin: 'http://192.168.58.20:4320',
},
'https://sc.jiansun.vip',
)
expect(getRequestHostOrigin(request)).toBe('http://192.168.58.20:4320')
expect(isAllowedCorsOrigin(request, 'http://192.168.58.20:4320')).toBe(true)
})
test('keeps the public sc.jiansun.vip origin allowed when production only exposes an intranet NEXTAUTH_URL', () => {
delete process.env.NEXT_PUBLIC_APP_PUBLIC_URL
delete process.env.APP_PUBLIC_URL
process.env.NEXTAUTH_URL = 'http://192.168.58.20:4320'
process.env.CORS_ALLOWED_ORIGINS = ''
const request = createRequest(
{
host: '192.168.58.20:4320',
'x-forwarded-proto': 'http',
origin: 'https://sc.jiansun.vip',
},
'http://192.168.58.20:4320',
)
expect(Array.from(getAllowedCorsOrigins(request))).toContain('https://sc.jiansun.vip')
expect(isAllowedCorsOrigin(request, 'https://sc.jiansun.vip')).toBe(true)
})
test('rejects foreign origins that do not match the request host or configured whitelist', () => {
const request = createRequest({
host: 'sc.jiansun.vip',
'x-forwarded-proto': 'https',
origin: 'https://evil.example.com',
})
expect(isAllowedCorsOrigin(request, 'https://evil.example.com')).toBe(false)
})
test('does not re-open the retired legacy public domain unless it is explicitly configured', () => {
delete process.env.NEXT_PUBLIC_APP_PUBLIC_URL
delete process.env.APP_PUBLIC_URL
process.env.NEXTAUTH_URL = 'http://192.168.58.20:4320'
process.env.CORS_ALLOWED_ORIGINS = ''
const request = createRequest(
{
host: '192.168.58.20:4320',
'x-forwarded-proto': 'http',
origin: 'https://3d.yydd.ai',
},
'http://192.168.58.20:4320',
)
expect(isAllowedCorsOrigin(request, 'https://3d.yydd.ai')).toBe(false)
})
})