feat(store): throttled per-day StampLogin + login_events delete cascade

StampLogin appends one login_events row per user per day via an atomic
INSERT ... SELECT ... WHERE NOT EXISTS, run through withUser so the throttle
probe is itself RLS-scoped to the caller. DeleteUser now deletes login_events
explicitly (no FK = no cascade — the summary_actions footgun, repeated).

Extends the two-user RLS isolation proof and the delete-account proof to cover
login_events, and adds throttle / new-day / user-scoping tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 23:46:03 +02:00
co-authored by Claude Opus 4.8
parent b070347597
commit de54cd33b2
4 changed files with 144 additions and 6 deletions
+41
View File
@@ -0,0 +1,41 @@
package store
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
)
// StampLogin records that the user was active today, throttled to one row per
// user per day. It is the read-side counterpart to SetAction: the middleware
// calls it on every authenticated request, but the append happens at most once a
// day so login_events stays small and the signal clean (one row = one active
// day, not one request).
//
// The check-and-insert is a single atomic statement: the INSERT ... SELECT ...
// WHERE NOT EXISTS only writes when no row for this user has seen_at in today
// (date_trunc('day', NOW()), server timezone). It runs through withUser, so the
// NOT EXISTS probe is itself RLS-scoped to the calling user via the
// tapir.current_user_id GUC — one user's stamp can never be suppressed or
// triggered by another user's rows. The explicit user_id predicate also keeps the
// probe on the (user_id, seen_at) index.
//
// A unique constraint is deliberately not used: under concurrent same-day
// requests the worst case is two rows for one day, which the gate query collapses
// to a single week bucket anyway — not worth a write-blocking constraint.
func (s *Store) StampLogin(ctx context.Context, userID string) error {
if err := s.withUser(ctx, userID, func(tx pgx.Tx) error {
_, err := tx.Exec(ctx,
`INSERT INTO login_events (user_id)
SELECT $1
WHERE NOT EXISTS (
SELECT 1 FROM login_events
WHERE user_id = $1 AND seen_at >= date_trunc('day', NOW())
)`, userID)
return err
}); err != nil {
return fmt.Errorf("store: stamp login: %w", err)
}
return nil
}