Normalize CORS origins before server wiring

This commit is contained in:
weishu
2026-01-14 09:23:45 +08:00
parent 5d1899616c
commit 4a2e30ee28
3 changed files with 52 additions and 5 deletions
+46 -3
View File
@@ -61,6 +61,42 @@ function resolveRelayFlag(args: string[]): { enabled: boolean; source: RelayFlag
return { enabled, source } return { enabled, source }
} }
function normalizeOrigin(value: string): string {
const trimmed = value.trim()
if (!trimmed) {
return ''
}
try {
return new URL(trimmed).origin
} catch {
return trimmed
}
}
function normalizeOrigins(origins: string[]): string[] {
const normalized = origins
.map(normalizeOrigin)
.filter(Boolean)
if (normalized.includes('*')) {
return ['*']
}
return Array.from(new Set(normalized))
}
function mergeCorsOrigins(base: string[], extra: string[]): string[] {
if (base.includes('*') || extra.includes('*')) {
return ['*']
}
const merged = new Set<string>()
for (const origin of base) {
merged.add(origin)
}
for (const origin of extra) {
merged.add(origin)
}
return Array.from(merged)
}
let syncEngine: SyncEngine | null = null let syncEngine: SyncEngine | null = null
let happyBot: HappyBot | null = null let happyBot: HappyBot | null = null
let webServer: BunServer<WebSocketData> | null = null let webServer: BunServer<WebSocketData> | null = null
@@ -73,9 +109,15 @@ async function main() {
console.log('HAPI Server starting...') console.log('HAPI Server starting...')
// Load configuration (async - loads from env/file with persistence) // Load configuration (async - loads from env/file with persistence)
const config = await createConfiguration()
const relayApiDomain = process.env.HAPI_RELAY_API || 'relay.hapi.run' const relayApiDomain = process.env.HAPI_RELAY_API || 'relay.hapi.run'
const relayFlag = resolveRelayFlag(process.argv) const relayFlag = resolveRelayFlag(process.argv)
const officialWebUrl = process.env.HAPI_OFFICIAL_WEB_URL || 'https://app.hapi.run'
const config = await createConfiguration()
const baseCorsOrigins = normalizeOrigins(config.corsOrigins)
const relayCorsOrigin = normalizeOrigin(officialWebUrl)
const corsOrigins = relayFlag.enabled
? mergeCorsOrigins(baseCorsOrigins, relayCorsOrigin ? [relayCorsOrigin] : [])
: baseCorsOrigins
// Display CLI API token information // Display CLI API token information
if (config.cliApiTokenIsNew) { if (config.cliApiTokenIsNew) {
@@ -127,6 +169,7 @@ async function main() {
const socketServer = createSocketServer({ const socketServer = createSocketServer({
store, store,
jwtSecret, jwtSecret,
corsOrigins,
getSession: (sessionId) => syncEngine?.getSession(sessionId) ?? store.sessions.getSession(sessionId), getSession: (sessionId) => syncEngine?.getSession(sessionId) ?? store.sessions.getSession(sessionId),
onWebappEvent: (event: SyncEvent) => syncEngine?.handleRealtimeEvent(event), onWebappEvent: (event: SyncEvent) => syncEngine?.handleRealtimeEvent(event),
onSessionAlive: (payload) => syncEngine?.handleSessionAlive(payload), onSessionAlive: (payload) => syncEngine?.handleSessionAlive(payload),
@@ -164,7 +207,8 @@ async function main() {
jwtSecret, jwtSecret,
store, store,
vapidPublicKey: vapidKeys.publicKey, vapidPublicKey: vapidKeys.publicKey,
socketEngine: socketServer.engine socketEngine: socketServer.engine,
corsOrigins
}) })
// Start the bot if configured // Start the bot if configured
@@ -207,7 +251,6 @@ async function main() {
console.log('[Web] Public: ' + tunnelUrl) console.log('[Web] Public: ' + tunnelUrl)
// Generate direct access link with server and token // Generate direct access link with server and token
const officialWebUrl = process.env.HAPI_OFFICIAL_WEB_URL || 'https://app.hapi.run'
const params = new URLSearchParams({ const params = new URLSearchParams({
server: tunnelUrl, server: tunnelUrl,
token: config.cliApiToken token: config.cliApiToken
+2 -1
View File
@@ -33,6 +33,7 @@ function resolveEnvNumber(name: string, fallback: number): number {
export type SocketServerDeps = { export type SocketServerDeps = {
store: Store store: Store
jwtSecret: Uint8Array jwtSecret: Uint8Array
corsOrigins?: string[]
getSession?: (sessionId: string) => { active: boolean; namespace: string } | null getSession?: (sessionId: string) => { active: boolean; namespace: string } | null
onWebappEvent?: (event: SyncEvent) => void onWebappEvent?: (event: SyncEvent) => void
onSessionAlive?: (payload: { sid: string; time: number; thinking?: boolean; mode?: 'local' | 'remote' }) => void onSessionAlive?: (payload: { sid: string; time: number; thinking?: boolean; mode?: 'local' | 'remote' }) => void
@@ -45,7 +46,7 @@ export function createSocketServer(deps: SocketServerDeps): {
engine: Engine engine: Engine
rpcRegistry: RpcRegistry rpcRegistry: RpcRegistry
} { } {
const corsOrigins = configuration.corsOrigins const corsOrigins = deps.corsOrigins ?? configuration.corsOrigins
const allowAllOrigins = corsOrigins.includes('*') const allowAllOrigins = corsOrigins.includes('*')
const io = new Server<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, SocketData>({ const io = new Server<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, SocketData>({
+4 -1
View File
@@ -59,6 +59,7 @@ function createWebApp(options: {
jwtSecret: Uint8Array jwtSecret: Uint8Array
store: Store store: Store
vapidPublicKey: string vapidPublicKey: string
corsOrigins?: string[]
embeddedAssetMap: Map<string, EmbeddedWebAsset> | null embeddedAssetMap: Map<string, EmbeddedWebAsset> | null
}): Hono<WebAppEnv> { }): Hono<WebAppEnv> {
const app = new Hono<WebAppEnv>() const app = new Hono<WebAppEnv>()
@@ -68,7 +69,7 @@ function createWebApp(options: {
// Health check endpoint (no auth required) // Health check endpoint (no auth required)
app.get('/health', (c) => c.json({ status: 'ok' })) app.get('/health', (c) => c.json({ status: 'ok' }))
const corsOrigins = configuration.corsOrigins const corsOrigins = options.corsOrigins ?? configuration.corsOrigins
const corsOriginOption = corsOrigins.includes('*') ? '*' : corsOrigins const corsOriginOption = corsOrigins.includes('*') ? '*' : corsOrigins
const corsMiddleware = cors({ const corsMiddleware = cors({
origin: corsOriginOption, origin: corsOriginOption,
@@ -178,6 +179,7 @@ export async function startWebServer(options: {
store: Store store: Store
vapidPublicKey: string vapidPublicKey: string
socketEngine: SocketEngine socketEngine: SocketEngine
corsOrigins?: string[]
}): Promise<BunServer<WebSocketData>> { }): Promise<BunServer<WebSocketData>> {
const isCompiled = isBunCompiled() const isCompiled = isBunCompiled()
const embeddedAssetMap = isCompiled ? await loadEmbeddedAssetMap() : null const embeddedAssetMap = isCompiled ? await loadEmbeddedAssetMap() : null
@@ -188,6 +190,7 @@ export async function startWebServer(options: {
jwtSecret: options.jwtSecret, jwtSecret: options.jwtSecret,
store: options.store, store: options.store,
vapidPublicKey: options.vapidPublicKey, vapidPublicKey: options.vapidPublicKey,
corsOrigins: options.corsOrigins,
embeddedAssetMap embeddedAssetMap
}) })