Files
smirk-backend-core/migrations/20260622000002_login_and_restore.sql
T
jwinterm 3caa678dea feat: db query layer (sessions, keys, audit, grin relay, login, restore)
Six entity query modules on Database, all explicit-column (no SELECT *), legacy
methods dropped:
- sessions: create + active-lookup by (already-peppered) refresh_token_hash,
  revoke/revoke-all (race-aware bool), touch, cleanup-expired
- user_keys: upsert (ON CONFLICT user_id+asset+key_type), list, get-by-asset
- audit: log_action + recent-by-user/action
- grin_slatepacks: relay create/fetch/pending/respond/finalize/cancel/expire
- login_events: salted IP hash, retention sweep, erasure purge (LoginStats only)
- restore_attempts: peppered fingerprint + salted IP (never raw), rate-limit
  counts, 7-day sweep

migrations/...02: login_events + restore_attempts tables (keyed-hash columns,
covering indexes for the aggregations). Shared unique_violation_as + hash_ip
helpers on Database.
2026-06-22 14:32:17 -04:00

32 lines
1.6 KiB
SQL

-- Login analytics + restore-attempt abuse tracking.
--
-- Both tables store only keyed hashes of sensitive values: login_events.ip_hash
-- and restore_attempts.ip_hash are salted IP hashes; restore_attempts.fingerprint
-- is a peppered HMAC of the seed fingerprint. No raw IPs or reproducible
-- fingerprints are stored.
CREATE TABLE login_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
asset TEXT NOT NULL, -- login method/asset (btc, ..., nostr, extension)
platform TEXT NOT NULL, -- extension | web | nostr
origin TEXT,
ip_hash TEXT, -- salted hash of the client IP
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_login_events_created_at ON login_events (created_at);
CREATE INDEX idx_login_events_user_id ON login_events (user_id);
-- Covering index for the (asset, platform) GROUP BY aggregations.
CREATE INDEX idx_login_events_asset_platform ON login_events (asset, platform, created_at);
CREATE TABLE restore_attempts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
fingerprint TEXT NOT NULL, -- peppered HMAC of the seed fingerprint
ip_hash TEXT, -- salted hash of the client IP
success BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_restore_attempts_fingerprint ON restore_attempts (fingerprint, created_at);
CREATE INDEX idx_restore_attempts_ip_hash ON restore_attempts (ip_hash, created_at);
CREATE INDEX idx_restore_attempts_created_at ON restore_attempts (created_at);