Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/services/auth/sessionLifecycle.server.ts
T

72 lines
3.0 KiB
TypeScript

import { hashIp, lookupCountry } from "~/modules/ipAddress/services/geoip.server";
import { getTrustedClientIpOrNull } from "~/utils/server/IpUtils";
import { loadRequiredWebConfig } from "@hivegrid/config";
import { createAdminSession, revokeAdminSessionByToken } from "@hivegrid/db/admin-sessions.server";
import { getHostRoutingContext, getRequestRoutingHost } from "~/utils/app/HostRouting.server";
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 getTrustedClientIpOrNull(request);
}
// 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,
};
}
// Every authenticated cookie is backed by this row. Store failures propagate
// so callers cannot issue an unrevocable fallback cookie.
export async function startServerSession({
request,
userId,
adminAuthenticationMethods,
adminMfaAssuredAt,
}: {
request: Request;
userId: string;
adminAuthenticationMethods?: readonly string[];
adminMfaAssuredAt?: Date;
}): Promise<string> {
const context = await buildSessionDeviceContext(request);
const routing = loadRequiredWebConfig().routing;
const host = getRequestRoutingHost(request, new URL(request.url), routing);
if (getHostRoutingContext(host, routing) === "admin") {
if (!adminAuthenticationMethods || !adminMfaAssuredAt) {
throw new Error("Admin server session requires Authentik MFA assurance");
}
const { rawToken } = await createAdminSession({
adminUserId: userId,
authenticationMethods: adminAuthenticationMethods,
mfaAssuredAt: adminMfaAssuredAt,
...context,
});
return rawToken;
}
const { rawToken } = await createServerSessionRow({ userId, ...context });
return rawToken;
}
export async function endServerSession(rawToken: string | undefined | null, request?: Request): Promise<void> {
if (request) {
const routing = loadRequiredWebConfig().routing;
const host = getRequestRoutingHost(request, new URL(request.url), routing);
if (getHostRoutingContext(host, routing) === "admin") {
await revokeAdminSessionByToken(rawToken);
return;
}
}
await revokeSessionByToken(rawToken);
}