Fix IP address not being set on sessions from localhost

The extractClientIP() function only checked proxy headers (X-Forwarded-For,
CF-Connecting-IP, etc.) which don't exist when running locally without a proxy.

Changes:
- Add clientIP field to APIContext
- Use Bun's server.requestIP() to get client IP from direct socket connection
- Update getGeoInfo() to accept fallback IP parameter
- Pass context.clientIP to getGeoInfo() in auth procedures

Now sessions will have IP address set even for local development (::1 or 127.0.0.1).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-10 18:08:21 +08:00
parent 74b26818ca
commit 319edf70db
6 changed files with 20 additions and 6 deletions

View File

@@ -21,6 +21,8 @@ export interface APIContext {
reqHeaders: Headers; reqHeaders: Headers;
/** Response headers (for setting cookies) */ /** Response headers (for setting cookies) */
resHeaders: Headers; resHeaders: Headers;
/** Client IP address from direct connection (fallback when no proxy headers) */
clientIP?: string | null;
} }
/** /**

View File

@@ -39,7 +39,7 @@ const rpName = Bun.env.RP_NAME ?? DEFAULT_RP_NAME;
Bun.serve({ Bun.serve({
port, port,
async fetch(request) { async fetch(request, server) {
const url = new URL(request.url); const url = new URL(request.url);
if (url.pathname.startsWith("/api/v1/rpc")) { if (url.pathname.startsWith("/api/v1/rpc")) {
@@ -50,6 +50,10 @@ Bun.serve({
// Create response headers for setting cookies // Create response headers for setting cookies
const resHeaders = new Headers(); 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 = { const context: APIContext = {
db, db,
origin, origin,
@@ -57,6 +61,7 @@ Bun.serve({
rpName, rpName,
reqHeaders: request.headers, reqHeaders: request.headers,
resHeaders, resHeaders,
clientIP,
}; };
const { response } = await handler.handle(request, { const { response } = await handler.handle(request, {

View File

@@ -102,7 +102,7 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
const hasPassword = user.password_hash !== null; const hasPassword = user.password_hash !== null;
// Get geo info and user agent // Get geo info and user agent
const geo = getGeoInfo(context.reqHeaders); const geo = getGeoInfo(context.reqHeaders, context.clientIP);
const userAgent = getUserAgent(context.reqHeaders); const userAgent = getUserAgent(context.reqHeaders);
// Create login request with secure token // Create login request with secure token

View File

@@ -86,7 +86,7 @@ export const loginIfRequestIsCompleted =
} }
// Get current request info // Get current request info
const geo = getGeoInfo(context.reqHeaders); const geo = getGeoInfo(context.reqHeaders, context.clientIP);
const userAgent = getUserAgent(context.reqHeaders); const userAgent = getUserAgent(context.reqHeaders);
// Upsert user device // Upsert user device

View File

@@ -225,7 +225,7 @@ export const signup = os.auth.signup.handler(async ({ input, context }) => {
} }
// Get geo info and user agent for session creation // 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); const userAgent = getUserAgent(context.reqHeaders);
let userId: number; let userId: number;

View File

@@ -126,9 +126,16 @@ export const lookupGeoFromIP = (
/** /**
* Extract geolocation info from request headers. * Extract geolocation info from request headers.
* Uses Cloudflare headers when available, falls back to GeoIP database lookup. * 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 => { export const getGeoInfo = (
const ip = extractClientIP(headers); 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 // Try Cloudflare geo headers first
const cfCountry = headers.get("CF-IPCountry"); const cfCountry = headers.get("CF-IPCountry");