Add complete auth backend (Workstream D): - Auth middleware for session/API key authentication - Signup with password or passkey (WebAuthn) - Login flow with device trust and email confirmation - Password reset and email verification - Session management and logout Utilities created: - cookies.ts: Cookie helpers and configuration - crypto.ts: Token generation and hashing - password.ts: zxcvbn validation, argon2id hashing - geo.ts: IP/location extraction from headers - email.ts: Stubbed email sending - session.ts: Session creation and device trust Code review improvements applied: - Use ORPCError instead of Error in procedures - Add ast-grep rule to enforce ORPCError usage - Remove error info leakage (generic messages) - Optimize N+1 query with JOIN in login-password - Extract signupWithPassword/signupWithPasskey for testability - Add 15-minute WebAuthn challenge expiry check - Strengthen CookieOptions type definitions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
export interface GeoInfo {
|
|
ip: string | null;
|
|
city: string | null;
|
|
region: string | null;
|
|
country: string | null;
|
|
}
|
|
|
|
/**
|
|
* Extract geolocation info from request headers
|
|
* Supports Cloudflare headers in production, falls back to standard headers
|
|
* @param headers - Request headers
|
|
* @returns Geolocation information extracted from headers
|
|
*/
|
|
export const getGeoInfo = (headers: Headers): GeoInfo => {
|
|
// Try Cloudflare headers first (production)
|
|
const cfIP = headers.get("CF-Connecting-IP");
|
|
const cfCountry = headers.get("CF-IPCountry");
|
|
const cfCity = headers.get("CF-IPCity");
|
|
const cfRegion = headers.get("CF-Region");
|
|
|
|
// Fallback to X-Forwarded-For or X-Real-IP
|
|
const forwardedFor = headers.get("X-Forwarded-For");
|
|
const realIP = headers.get("X-Real-IP");
|
|
|
|
const ip = cfIP ?? realIP ?? forwardedFor?.split(",")[0]?.trim() ?? null;
|
|
|
|
return {
|
|
ip,
|
|
city: cfCity ?? null,
|
|
region: cfRegion ?? null,
|
|
country: cfCountry ?? null,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Extract User-Agent from request headers
|
|
* @param headers - Request headers
|
|
* @returns User-Agent string or "Unknown" if not present
|
|
*/
|
|
export const getUserAgent = (headers: Headers): string => {
|
|
return headers.get("User-Agent") ?? "Unknown";
|
|
};
|