fix(stage-tamagotchi): restrict auth relay cors origin

This commit is contained in:
Codex
2026-06-23 02:22:48 +08:00
parent 6559190f96
commit 4e14a852a9
2 changed files with 33 additions and 9 deletions
@@ -37,5 +37,23 @@ describe('startLoopbackServer', () => {
expect(response.status).toBe(204)
expect(response.headers.get('Access-Control-Allow-Private-Network')).toBe('true')
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://accounts.airi.build')
})
it('does not allow private-network preflight from untrusted origins', async () => {
const server = await startLoopbackServer()
servers.push(server)
const response = await fetch(`http://127.0.0.1:${server.port}/callback`, {
method: 'OPTIONS',
headers: {
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Private-Network': 'true',
'Origin': 'https://example.invalid',
},
})
expect(response.headers.get('Access-Control-Allow-Private-Network')).toBeNull()
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull()
})
})
@@ -1,4 +1,6 @@
import { eventHandler, getQuery, H3, handleCors } from 'h3'
import type { CorsOptions } from 'h3'
import { eventHandler, getQuery, getRequestHeader, H3, handleCors } from 'h3'
import { createH3Server } from '../../server'
@@ -7,6 +9,8 @@ export interface LoopbackCallbackResult {
state: string
}
const AIRI_AUTH_ORIGIN = 'https://accounts.airi.build'
/**
* Starts a temporary loopback callback server for the Electron OIDC flow.
*
@@ -39,16 +43,18 @@ export async function startLoopbackServer(): Promise<{
const app = new H3()
const loopbackServer = createH3Server({ app, host })
const corsOptions = {
origin: '*',
const corsOptions: CorsOptions = {
origin: [AIRI_AUTH_ORIGIN],
methods: '*',
preflight: {
statusCode: 204,
},
} as const
}
function appendPrivateNetworkAccessHeaders<T extends { headers: Headers }>(event: Parameters<typeof handleCors>[0], response: T) {
if (getRequestHeader(event, 'origin') === AIRI_AUTH_ORIGIN)
response.headers.set('Access-Control-Allow-Private-Network', 'true')
function appendPrivateNetworkAccessHeaders<T extends { headers: Headers }>(response: T) {
response.headers.set('Access-Control-Allow-Private-Network', 'true')
return response
}
@@ -69,16 +75,16 @@ export async function startLoopbackServer(): Promise<{
app.options('/callback', eventHandler(async (event) => {
const corsResponse = handleCors(event, corsOptions)
if (corsResponse !== false) {
return appendPrivateNetworkAccessHeaders(corsResponse)
return appendPrivateNetworkAccessHeaders(event, corsResponse)
}
return appendPrivateNetworkAccessHeaders(new Response(null, { status: 204 }))
return new Response(null, { status: 204 })
}))
app.get('/callback', eventHandler(async (event) => {
const corsResponse = handleCors(event, corsOptions)
if (corsResponse !== false) {
return appendPrivateNetworkAccessHeaders(corsResponse)
return appendPrivateNetworkAccessHeaders(event, corsResponse)
}
const query = getQuery(event)