34 lines
1.3 KiB
TypeScript
34 lines
1.3 KiB
TypeScript
import { createHmac } from "node:crypto";
|
|
|
|
/**
|
|
* Derives a daily-rotating, non-reversible pseudonymous visitor identifier
|
|
* for session grouping - the same approach used by privacy-friendly
|
|
* analytics tools (Plausible/Fathom-style): hash(ip + userAgent + host)
|
|
* salted with a value that changes every calendar day. Raw IP/UA are never
|
|
* persisted anywhere, only this hash - and since the salt rotates daily,
|
|
* the same visitor hashes differently tomorrow, so there is no cross-day
|
|
* linkage and no long-term individual profile.
|
|
*
|
|
* ANALYTICS_SALT_SECRET is a dedicated secret (not PAYLOAD_SECRET) - this
|
|
* codebase keeps one secret per purpose on purpose (see
|
|
* MEETING_SESSION_SECRET's comment in .env.example).
|
|
*/
|
|
function secret(): string {
|
|
const value = process.env.ANALYTICS_SALT_SECRET;
|
|
if (!value) throw new Error("ANALYTICS_SALT_SECRET is not set");
|
|
return value;
|
|
}
|
|
|
|
function dateKey(date: Date): string {
|
|
return date.toISOString().slice(0, 10);
|
|
}
|
|
|
|
function dailySalt(date: Date): string {
|
|
return createHmac("sha256", secret()).update(dateKey(date)).digest("hex");
|
|
}
|
|
|
|
export function visitorHash(args: { ip: string; userAgent: string; host: string; date?: Date }): string {
|
|
const salt = dailySalt(args.date ?? new Date());
|
|
return createHmac("sha256", salt).update(`${args.ip}|${args.userAgent}|${args.host}`).digest("hex");
|
|
}
|