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:
@@ -24,7 +24,7 @@ import (
|
||||
// userIsolatedTables are the tables that carry a user_id and whose policy keys
|
||||
// directly off the tapir.current_user_id GUC.
|
||||
var userIsolatedTables = []string{
|
||||
"users", "videos", "transcripts", "summaries", "summary_actions", "video_connections",
|
||||
"users", "videos", "transcripts", "summaries", "summary_actions", "login_events", "video_connections",
|
||||
}
|
||||
|
||||
// allIsolatedTables adds sink_deliveries, whose ownership is derived from its
|
||||
@@ -69,6 +69,10 @@ func seedUser(t *testing.T, p *pgxpool.Pool, userID string) seeded {
|
||||
VALUES ($1, $2, 'watched')`, userID, videoID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = p.Exec(ctx,
|
||||
`INSERT INTO login_events (user_id) VALUES ($1)`, userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = p.Exec(ctx,
|
||||
`INSERT INTO sink_deliveries (summary_id, sink, status)
|
||||
VALUES ($1, 'store', 'delivered')`, summaryID)
|
||||
@@ -185,10 +189,12 @@ func TestRLSEnforcesPerUserIsolation(t *testing.T) {
|
||||
{"update transcripts", `UPDATE transcripts SET content = 'hacked' WHERE user_id = $1`, b.userID},
|
||||
{"update summaries", `UPDATE summaries SET summary = 'hacked' WHERE user_id = $1`, b.userID},
|
||||
{"update summary_actions", `UPDATE summary_actions SET action = 'skipped' WHERE user_id = $1`, b.userID},
|
||||
{"update login_events", `UPDATE login_events SET seen_at = NOW() WHERE user_id = $1`, b.userID},
|
||||
{"update sink_deliveries", `UPDATE sink_deliveries SET status = 'hacked' WHERE summary_id = $1`, b.summaryID},
|
||||
{"update video_connections", `UPDATE video_connections SET token_ref = 'hacked' WHERE user_id = $1`, b.userID},
|
||||
{"delete summaries", `DELETE FROM summaries WHERE user_id = $1`, b.userID},
|
||||
{"delete summary_actions", `DELETE FROM summary_actions WHERE user_id = $1`, b.userID},
|
||||
{"delete login_events", `DELETE FROM login_events WHERE user_id = $1`, b.userID},
|
||||
{"delete sink_deliveries", `DELETE FROM sink_deliveries WHERE summary_id = $1`, b.summaryID},
|
||||
{"delete video_connections", `DELETE FROM video_connections WHERE user_id = $1`, b.userID},
|
||||
}
|
||||
@@ -205,17 +211,20 @@ func TestRLSEnforcesPerUserIsolation(t *testing.T) {
|
||||
`SELECT summary FROM summaries WHERE user_id = $1`, b.userID).Scan(&bSummary))
|
||||
require.Equal(t, "sum", bSummary, "B's summary must be untouched by A's writes")
|
||||
|
||||
var bSummaries, bActions, bDeliveries, bConnections int
|
||||
var bSummaries, bActions, bLogins, bDeliveries, bConnections int
|
||||
require.NoError(t, super.QueryRow(ctx,
|
||||
`SELECT count(*) FROM summaries WHERE user_id = $1`, b.userID).Scan(&bSummaries))
|
||||
require.NoError(t, super.QueryRow(ctx,
|
||||
`SELECT count(*) FROM summary_actions WHERE user_id = $1`, b.userID).Scan(&bActions))
|
||||
require.NoError(t, super.QueryRow(ctx,
|
||||
`SELECT count(*) FROM login_events WHERE user_id = $1`, b.userID).Scan(&bLogins))
|
||||
require.NoError(t, super.QueryRow(ctx,
|
||||
fmt.Sprintf(`SELECT count(*) FROM sink_deliveries WHERE summary_id = '%s'`, b.summaryID)).Scan(&bDeliveries))
|
||||
require.NoError(t, super.QueryRow(ctx,
|
||||
`SELECT count(*) FROM video_connections WHERE user_id = $1 AND token_ref <> 'hacked'`, b.userID).Scan(&bConnections))
|
||||
require.Equal(t, 1, bSummaries, "A's DELETE must not have removed B's summary")
|
||||
require.Equal(t, 1, bActions, "A's DELETE must not have removed B's action")
|
||||
require.Equal(t, 1, bLogins, "A's DELETE must not have removed B's login event")
|
||||
require.Equal(t, 1, bDeliveries, "A's DELETE must not have removed B's delivery")
|
||||
require.Equal(t, 1, bConnections, "A's writes must not have touched B's connection")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user