feat(auth): implement port 51121 with OS fallback

Add port fallback logic to OAuth callback server:
- Try port 51121 (ANTIGRAVITY_CALLBACK_PORT) first
- Fallback to OS-assigned port on EADDRINUSE
- Add redirectUri property to CallbackServerHandle
- Return actual bound port in handle.port

Add comprehensive port handling tests (5 new tests):
- Should prefer port 51121
- Should return actual bound port
- Should fallback when port occupied
- Should cleanup and release port on close
- Should provide redirect URI with actual port

All 16 tests passing (11 existing + 5 new).

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim 2026-01-06 16:24:59 +09:00
parent 534142da12
commit e27bceb97e
2 changed files with 116 additions and 33 deletions

View File

@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"
import { buildAuthURL, exchangeCode } from "./oauth"
import { ANTIGRAVITY_CLIENT_ID, GOOGLE_TOKEN_URL } from "./constants"
import { buildAuthURL, exchangeCode, startCallbackServer } from "./oauth"
import { ANTIGRAVITY_CLIENT_ID, GOOGLE_TOKEN_URL, ANTIGRAVITY_CALLBACK_PORT } from "./constants"
describe("OAuth PKCE Removal", () => {
describe("buildAuthURL", () => {
@ -188,4 +188,75 @@ describe("OAuth PKCE Removal", () => {
expect(result1.state).not.toBe(result2.state)
})
})
describe("startCallbackServer Port Handling", () => {
it("should prefer port 51121", () => {
// #given
// Port 51121 should be free
// #when
const handle = startCallbackServer()
// #then
// If 51121 is available, should use it
// If not available, should use valid fallback
expect(handle.port).toBeGreaterThan(0)
expect(handle.port).toBeLessThan(65536)
handle.close()
})
it("should return actual bound port", () => {
// #when
const handle = startCallbackServer()
// #then
expect(typeof handle.port).toBe("number")
expect(handle.port).toBeGreaterThan(0)
handle.close()
})
it("should fallback to OS-assigned port if 51121 is occupied (EADDRINUSE)", async () => {
// #given - Occupy port 51121 first
const blocker = Bun.serve({
port: ANTIGRAVITY_CALLBACK_PORT,
fetch: () => new Response("blocked")
})
try {
// #when
const handle = startCallbackServer()
// #then
expect(handle.port).not.toBe(ANTIGRAVITY_CALLBACK_PORT)
expect(handle.port).toBeGreaterThan(0)
handle.close()
} finally {
// Cleanup blocker
blocker.stop()
}
})
it("should cleanup server on close", () => {
// #given
const handle = startCallbackServer()
const port = handle.port
// #when
handle.close()
// #then - port should be released (can bind again)
const testServer = Bun.serve({ port, fetch: () => new Response("test") })
expect(testServer.port).toBe(port)
testServer.stop()
})
it("should provide redirect URI with actual port", () => {
// #given
const handle = startCallbackServer()
// #then
expect(handle.redirectUri).toBe(`http://localhost:${handle.port}/oauth-callback`)
handle.close()
})
})
})

View File

@ -148,6 +148,7 @@ export async function fetchUserInfo(
export interface CallbackServerHandle {
port: number
redirectUri: string
waitForCallback: () => Promise<CallbackResult>
close: () => void
}
@ -171,43 +172,53 @@ export function startCallbackServer(
}
}
server = Bun.serve({
port: 0,
fetch(request: Request): Response {
const url = new URL(request.url)
const fetchHandler = (request: Request): Response => {
const url = new URL(request.url)
if (url.pathname === "/oauth-callback") {
const code = url.searchParams.get("code") || ""
const state = url.searchParams.get("state") || ""
const error = url.searchParams.get("error") || undefined
if (url.pathname === "/oauth-callback") {
const code = url.searchParams.get("code") || ""
const state = url.searchParams.get("state") || ""
const error = url.searchParams.get("error") || undefined
let responseBody: string
if (code && !error) {
responseBody =
"<html><body><h1>Login successful</h1><p>You can close this window.</p></body></html>"
} else {
responseBody =
"<html><body><h1>Login failed</h1><p>Please check the CLI output.</p></body></html>"
}
setTimeout(() => {
cleanup()
if (resolveCallback) {
resolveCallback({ code, state, error })
}
}, 100)
return new Response(responseBody, {
status: 200,
headers: { "Content-Type": "text/html" },
})
let responseBody: string
if (code && !error) {
responseBody =
"<html><body><h1>Login successful</h1><p>You can close this window.</p></body></html>"
} else {
responseBody =
"<html><body><h1>Login failed</h1><p>Please check the CLI output.</p></body></html>"
}
return new Response("Not Found", { status: 404 })
},
})
setTimeout(() => {
cleanup()
if (resolveCallback) {
resolveCallback({ code, state, error })
}
}, 100)
return new Response(responseBody, {
status: 200,
headers: { "Content-Type": "text/html" },
})
}
return new Response("Not Found", { status: 404 })
}
try {
server = Bun.serve({
port: ANTIGRAVITY_CALLBACK_PORT,
fetch: fetchHandler,
})
} catch (error) {
server = Bun.serve({
port: 0,
fetch: fetchHandler,
})
}
const actualPort = server.port as number
const redirectUri = `http://localhost:${actualPort}/oauth-callback`
const waitForCallback = (): Promise<CallbackResult> => {
return new Promise((resolve, reject) => {
@ -223,6 +234,7 @@ export function startCallbackServer(
return {
port: actualPort,
redirectUri,
waitForCallback,
close: cleanup,
}