import { hashIp, lookupCountry } from "~/modules/ipAddress/services/geoip.server"; import { getClientIPAddress } from "~/utils/server/IpUtils"; import { createUserSession as createServerSessionRow, revokeSessionByToken } from "./sessionStore.server"; // R-P2a S4b — Bruecke zwischen Cookie-Login und dem serverseitigen Session-Store. Additiv: // das Login-Cookie bleibt der primaere Auth-Mechanismus, parallel wird eine UserSession-Zeile // gefuehrt, damit Geraeteliste, Revoke und "ueberall abmelden" greifen. export function extractRawClientIp(request: Request): string | null { return getClientIPAddress(request.headers)?.toString() ?? null; } // AD-22: persistiert KEINE Klartext-IP. Die IP wird gehasht (hashIp, Wiedererkennung "gleiches // Geraet") und das Land lokal aufgeloest (lookupCountry; die IP verlaesst die Infra nicht). export async function buildSessionDeviceContext(request: Request): Promise<{ ipHash: string | null; userAgent: string | null; approxLocation: string | null }> { const rawIp = extractRawClientIp(request); const country = rawIp ? await lookupCountry(rawIp) : null; return { ipHash: rawIp ? hashIp(rawIp) : null, userAgent: request.headers.get("user-agent"), approxLocation: country?.countryName ?? null, }; } // Beim Login/Signup aufgerufen: legt die Session-Zeile mit (pseudonymisiertem) Geraete-Kontext an und // liefert den rohen Token zurueck, der ins Cookie wandert. Best-effort: ein Store-Fehler darf den Login // nicht verhindern (Cookie ist Source-of-Truth fuer die Identitaet). export async function startServerSession({ request, userId }: { request: Request; userId: string }): Promise { const context = await buildSessionDeviceContext(request); const { rawToken } = await createServerSessionRow({ userId, ...context }); return rawToken; } export async function endServerSession(rawToken: string | undefined | null): Promise { await revokeSessionByToken(rawToken); }