fix: allow proxied Pascal scene creation
This commit is contained in:
parent
98dddbcc2e
commit
4c1b97f210
@ -98,6 +98,9 @@ sudo env PASCAL_SMOKE_CONFIRM=1 pnpm pascal:smoke
|
||||
| `PASCAL_BUN_VERSION` | 固定 Bun 版本 |
|
||||
| `PASCAL_BACKUP_DIR` | 环境与 Nginx 配置备份目录 |
|
||||
|
||||
`PASCAL_PUBLIC_ORIGIN` 会同时写入 Pascal 的场景 API 允许来源,用于正确
|
||||
识别 Nginx 后的 HTTPS 同域请求,不应配置为 `*`。
|
||||
|
||||
## 运行维护
|
||||
|
||||
```bash
|
||||
|
||||
@ -231,6 +231,9 @@ write_pascal_env() {
|
||||
printf 'NEXT_PUBLIC_PASCAL_BASE_PATH=%s\n' "$PASCAL_BASE_PATH"
|
||||
printf 'PASCAL_REQUIRE_HOST_USER=true\n'
|
||||
printf 'PASCAL_DATA_DIR=%s\n' "$PASCAL_DATA_DIR"
|
||||
if [ -n "$PASCAL_PUBLIC_ORIGIN" ]; then
|
||||
printf 'PASCAL_SCENE_API_ORIGINS=%s\n' "${PASCAL_PUBLIC_ORIGIN%/}"
|
||||
fi
|
||||
printf 'PASCAL_SCENE_API_TOKEN=%s\n' "$secret"
|
||||
printf 'PASCAL_AGENT_PROXY_SECRET=%s\n' "$secret"
|
||||
printf 'PASCAL_AGENT_API_KEY=%s\n' "$secret"
|
||||
|
||||
@ -47,6 +47,38 @@ test('accepts bearer token auth when configured', () => {
|
||||
expect(guardSceneApiRequest(request)).toBeNull()
|
||||
})
|
||||
|
||||
test('accepts the public HTTPS origin behind a trusted reverse proxy', () => {
|
||||
process.env.PASCAL_SCENE_API_TOKEN = 'secret'
|
||||
const request = new Request('http://bbyy.ai/pascal-service/api/scenes', {
|
||||
headers: {
|
||||
host: 'bbyy.ai',
|
||||
origin: 'https://bbyy.ai',
|
||||
'x-forwarded-host': 'bbyy.ai',
|
||||
'x-forwarded-proto': 'https',
|
||||
'x-pascal-scene-token': 'secret',
|
||||
},
|
||||
})
|
||||
|
||||
expect(guardSceneApiRequest(request)).toBeNull()
|
||||
})
|
||||
|
||||
test('rejects an unrelated origin behind the reverse proxy', async () => {
|
||||
process.env.PASCAL_SCENE_API_TOKEN = 'secret'
|
||||
const request = new Request('http://bbyy.ai/pascal-service/api/scenes', {
|
||||
headers: {
|
||||
host: 'bbyy.ai',
|
||||
origin: 'https://evil.example',
|
||||
'x-forwarded-host': 'bbyy.ai',
|
||||
'x-forwarded-proto': 'https',
|
||||
'x-pascal-scene-token': 'secret',
|
||||
},
|
||||
})
|
||||
|
||||
const response = guardSceneApiRequest(request)
|
||||
expect(response?.status).toBe(403)
|
||||
expect(await response?.json()).toEqual({ error: 'origin_not_allowed' })
|
||||
})
|
||||
|
||||
test('applies configured CORS origins for preflight', () => {
|
||||
process.env.PASCAL_SCENE_API_ORIGINS = 'https://app.example'
|
||||
const request = new Request('https://editor.example/api/scenes', {
|
||||
|
||||
@ -145,10 +145,20 @@ function configuredOrigins(): Set<string> {
|
||||
function isSameOrigin(request: Request, origin: string): boolean {
|
||||
const parsedOrigin = parseUrl(origin)
|
||||
if (!parsedOrigin) return false
|
||||
const requestUrl = new URL(request.url)
|
||||
const forwardedProto = firstForwardedValue(request.headers.get('x-forwarded-proto'))
|
||||
const forwardedHost = firstForwardedValue(request.headers.get('x-forwarded-host'))
|
||||
const forwardedUrl =
|
||||
forwardedProto && forwardedHost && /^(?:http|https)$/.test(forwardedProto)
|
||||
? parseUrl(`${forwardedProto}://${forwardedHost}`)
|
||||
: null
|
||||
const requestUrl = forwardedUrl ?? new URL(request.url)
|
||||
return normalizeOrigin(parsedOrigin) === normalizeOrigin(requestUrl)
|
||||
}
|
||||
|
||||
function firstForwardedValue(value: string | null): string | null {
|
||||
return value?.split(',')[0]?.trim().toLowerCase() || null
|
||||
}
|
||||
|
||||
function isLoopbackRequest(request: Request): boolean {
|
||||
const host = request.headers.get('host') ?? new URL(request.url).host
|
||||
return isLoopbackHostname(stripPort(host))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user