store: DeleteSessionsByUserID for force-logout

This commit is contained in:
2026-05-05 09:08:04 +01:00
parent 12391abef0
commit f0828782c1
3 changed files with 69 additions and 0 deletions
+9
View File
@@ -2,10 +2,19 @@
Project-specific rules for Claude when working in this repo.
## Commands
Is the user types in any of the following, follow the instructions in the table
| Command | Action |
| --- | --- |
| :release | trigger subagent to commit (if needed), push (if needed), raise PR, wait for PR to pass or fail. If fail, report back. If pass, merge in to main |
## Repo
The repo lives inside a Gitea instance; `tea` CLI is available for use by agents
## Run `go vet` before every commit
CI runs `go vet ./...` and will fail the build on any vet error.
+15
View File
@@ -86,3 +86,18 @@ func (s *Store) PurgeExpiredSessions(ctx context.Context) (int64, error) {
n, _ := res.RowsAffected()
return n, nil
}
// DeleteSessionsByUserID removes every session row owned by the
// user. Returns count for caller logging. Used by:
// - admin "Force logout" button
// - admin Disable user (sessions outlive the disable flag, so we
// also clear them so the user gets bounced immediately)
func (s *Store) DeleteSessionsByUserID(ctx context.Context, userID string) (int64, error) {
res, err := s.db.ExecContext(ctx,
`DELETE FROM sessions WHERE user_id = ?`, userID)
if err != nil {
return 0, fmt.Errorf("store: delete sessions by user: %w", err)
}
n, _ := res.RowsAffected()
return n, nil
}
+45
View File
@@ -0,0 +1,45 @@
package store
import (
"context"
"testing"
"time"
)
func TestDeleteSessionsByUserID(t *testing.T) {
t.Parallel()
s := openTestStore(t)
ctx := context.Background()
now := time.Now().UTC()
uid := "u-force"
if err := s.CreateUser(ctx, User{
ID: uid, Username: "victim",
PasswordHash: "x", Role: RoleOperator, CreatedAt: now,
}); err != nil {
t.Fatalf("create user: %v", err)
}
// Create two sessions for that user.
for i, h := range []string{"hash1", "hash2"} {
if err := s.CreateSession(ctx, Session{
ID: h,
UserID: uid,
CreatedAt: now,
ExpiresAt: now.Add(time.Hour),
}, h); err != nil {
t.Fatalf("create session %d: %v", i, err)
}
}
n, err := s.DeleteSessionsByUserID(ctx, uid)
if err != nil {
t.Fatalf("delete: %v", err)
}
if n != 2 {
t.Errorf("count: got %d want 2", n)
}
if _, err := s.LookupSession(ctx, "hash1"); err == nil {
t.Error("hash1 should be gone")
}
}