diff --git a/apps/api-server/src/context.ts b/apps/api-server/src/context.ts index 4e4458b..ac030e6 100644 --- a/apps/api-server/src/context.ts +++ b/apps/api-server/src/context.ts @@ -21,6 +21,8 @@ export interface APIContext { reqHeaders: Headers; /** Response headers (for setting cookies) */ resHeaders: Headers; + /** Client IP address from direct connection (fallback when no proxy headers) */ + clientIP?: string | null; } /** diff --git a/apps/api-server/src/index.ts b/apps/api-server/src/index.ts index 15eb2d9..e2a8926 100644 --- a/apps/api-server/src/index.ts +++ b/apps/api-server/src/index.ts @@ -39,7 +39,7 @@ const rpName = Bun.env.RP_NAME ?? DEFAULT_RP_NAME; Bun.serve({ port, - async fetch(request) { + async fetch(request, server) { const url = new URL(request.url); if (url.pathname.startsWith("/api/v1/rpc")) { @@ -50,6 +50,10 @@ Bun.serve({ // Create response headers for setting cookies const resHeaders = new Headers(); + // Get client IP from Bun's server (fallback for when no proxy headers) + const socketInfo = server.requestIP(request); + const clientIP = socketInfo?.address ?? null; + const context: APIContext = { db, origin, @@ -57,6 +61,7 @@ Bun.serve({ rpName, reqHeaders: request.headers, resHeaders, + clientIP, }; const { response } = await handler.handle(request, { diff --git a/apps/api-server/src/procedures/auth/create-login-request.ts b/apps/api-server/src/procedures/auth/create-login-request.ts index 0cc0935..551bdb7 100644 --- a/apps/api-server/src/procedures/auth/create-login-request.ts +++ b/apps/api-server/src/procedures/auth/create-login-request.ts @@ -102,7 +102,7 @@ export const createLoginRequest = os.auth.createLoginRequest.handler( const hasPassword = user.password_hash !== null; // Get geo info and user agent - const geo = getGeoInfo(context.reqHeaders); + const geo = getGeoInfo(context.reqHeaders, context.clientIP); const userAgent = getUserAgent(context.reqHeaders); // Create login request with secure token diff --git a/apps/api-server/src/procedures/auth/login-if-completed.ts b/apps/api-server/src/procedures/auth/login-if-completed.ts index 513429b..cf438e1 100644 --- a/apps/api-server/src/procedures/auth/login-if-completed.ts +++ b/apps/api-server/src/procedures/auth/login-if-completed.ts @@ -86,7 +86,7 @@ export const loginIfRequestIsCompleted = } // Get current request info - const geo = getGeoInfo(context.reqHeaders); + const geo = getGeoInfo(context.reqHeaders, context.clientIP); const userAgent = getUserAgent(context.reqHeaders); // Upsert user device diff --git a/apps/api-server/src/procedures/auth/signup.ts b/apps/api-server/src/procedures/auth/signup.ts index 3015c8f..b2efabf 100644 --- a/apps/api-server/src/procedures/auth/signup.ts +++ b/apps/api-server/src/procedures/auth/signup.ts @@ -225,7 +225,7 @@ export const signup = os.auth.signup.handler(async ({ input, context }) => { } // Get geo info and user agent for session creation - const geo = getGeoInfo(context.reqHeaders); + const geo = getGeoInfo(context.reqHeaders, context.clientIP); const userAgent = getUserAgent(context.reqHeaders); let userId: number; diff --git a/apps/api-server/src/utils/geo.ts b/apps/api-server/src/utils/geo.ts index abccdb9..4c24374 100644 --- a/apps/api-server/src/utils/geo.ts +++ b/apps/api-server/src/utils/geo.ts @@ -126,9 +126,16 @@ export const lookupGeoFromIP = ( /** * Extract geolocation info from request headers. * Uses Cloudflare headers when available, falls back to GeoIP database lookup. + * + * @param headers - Request headers to extract proxy IP headers from + * @param fallbackIP - Optional fallback IP from direct socket connection (e.g., from Bun's server.requestIP) */ -export const getGeoInfo = (headers: Headers): GeoInfo => { - const ip = extractClientIP(headers); +export const getGeoInfo = ( + headers: Headers, + fallbackIP?: string | null, +): GeoInfo => { + // Try proxy headers first, then fall back to direct connection IP + const ip = extractClientIP(headers) ?? fallbackIP ?? null; // Try Cloudflare geo headers first const cfCountry = headers.get("CF-IPCountry");