mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
On a completed sign-in (password, second-factor, or passkey) from a device or location not seen before, send one notification through the dispatcher cascade, honouring the user's per-channel preferences via a new SECURITY_ALERT event type. Dedupe on a one-way, per-user fingerprint: a SHA-256 over the userId, the coarse User-Agent family, and a country/ASN-level geo signal, stored in the new user_known_devices ledger (migration 0203). The first sighting of a fingerprint alerts and records; later sightings only bump last-seen and stay silent. The raw User-Agent and IP are never persisted — only the salted hash and a coarse label. Registration records its device silently so a fresh account does not alert on its own first session. The whole path is fire-and-forget and never blocks or fails a sign-in.
34 lines
1.7 KiB
SQL
34 lines
1.7 KiB
SQL
-- v1.23 — new-device sign-in alert dedupe ledger.
|
|
--
|
|
-- One row per (user, coarse device fingerprint). The fingerprint
|
|
-- (`device_hash`) is a one-way SHA-256 hash of
|
|
-- `userId | normalised-User-Agent | coarse-location-or-asn`. The raw
|
|
-- User-Agent and IP are NEVER stored — the table holds only the salted hash
|
|
-- (the userId is part of the digest input, so the same browser on two
|
|
-- accounts produces two unrelated hashes) plus an optional coarse,
|
|
-- human-readable `label` for the security-activity surface (e.g.
|
|
-- "Firefox on macOS — Berlin, DE"; the label omits the IP).
|
|
--
|
|
-- A successful sign-in upserts on (user_id, device_hash): the first sighting
|
|
-- of a hash inserts the row and fires one new-device notification through the
|
|
-- dispatcher; subsequent sightings only bump `last_seen_at` and stay silent.
|
|
|
|
CREATE TABLE "user_known_devices" (
|
|
"id" TEXT NOT NULL,
|
|
"user_id" TEXT NOT NULL,
|
|
"device_hash" TEXT NOT NULL,
|
|
"label" TEXT,
|
|
"first_seen_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"last_seen_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT "user_known_devices_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- Dedupe is per-user: a coarse fingerprint can never be inserted twice for
|
|
-- one account, so "first sighting → alert" is a single unique-constrained
|
|
-- upsert with no race window.
|
|
CREATE UNIQUE INDEX "user_known_devices_user_id_device_hash_key" ON "user_known_devices"("user_id", "device_hash");
|
|
CREATE INDEX "user_known_devices_user_id_idx" ON "user_known_devices"("user_id");
|
|
|
|
ALTER TABLE "user_known_devices" ADD CONSTRAINT "user_known_devices_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|