ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1import { Context } from "hono";
2
3export interface SessionFingerprint {
4 userAgent: string;
5 ipAddress: string;
6 createdAt: number;
7}
8
9/**
10 * Session Security Service
11 * Provides session replay protection and fingerprinting for Hono
12 */
13export class SessionSecurityService {
14 /**
15 * Generate a session fingerprint from request headers
16 */
17 static generateFingerprint(c: Context): SessionFingerprint {
18 const userAgent = c.req.header("user-agent") || "unknown";
19 const forwardedFor = c.req.header("x-forwarded-for");
20 const ipAddress = forwardedFor?.split(",")[0].trim() || c.req.header("client-ip") || "unknown";
21
22 return {
23 userAgent,
24 ipAddress,
25 createdAt: Date.now(),
26 };
27 }
28
29 /**
30 * Verify session fingerprint matches current request
31 * Helps detect session hijacking
32 */
33 static verifyFingerprint(
34 stored: SessionFingerprint,
35 current: SessionFingerprint,
36 ): boolean {
37 // User agent must match exactly
38 if (stored.userAgent !== current.userAgent) {
39 console.warn("Session fingerprint mismatch: User-Agent changed");
40 return false;
41 }
42
43 // IP can change (mobile networks, VPN) but log if it does
44 if (stored.ipAddress !== current.ipAddress) {
45 console.info(
46 `Session IP changed: ${stored.ipAddress} -> ${current.ipAddress}`,
47 );
48 // Don't fail - just log for monitoring
49 }
50
51 return true;
52 }
53}
54
55/**
56 * Add session fingerprint to new sessions
57 * Call this in oauth-callback when creating session
58 */
59export function createSecureSessionData(
60 c: Context,
61 did: string,
62): { did: string; fingerprint: SessionFingerprint } {
63 return {
64 did,
65 fingerprint: SessionSecurityService.generateFingerprint(c),
66 };
67}