refactor(web): harden oauth state store and session writes after review

- Drop the FIFO from oauthStates: consumed states left entries behind, so
  an unrate-limited start/cancel cycle grew the slice without bound.
  Evict by oldest expiry instead — the map alone now bounds memory.
- CreateSession runs INSERT + expiry sweep in one transaction.
- slices.Contains replaces a hand-rolled contains; APIBase typo fixed.
- Stale comments and test paths updated; login hover uses --ember-ink.
This commit is contained in:
2026-08-08 08:47:09 +07:00
parent 13e8e73da7
commit 1a7e130f9f
12 changed files with 51 additions and 49 deletions
@@ -1,7 +1,8 @@
-- One row per browser session. The id is an opaque random value the cookie
-- carries verbatim; a request is authenticated by looking the row up, and
-- deleting the row is how a session is revoked. Expired rows are removed
-- lazily on lookup, so nothing sweeps them.
-- lazily on lookup and swept by the next login, so nothing runs a background
-- cleanup.
CREATE TABLE sessions (
id text PRIMARY KEY,
reader_id bigint NOT NULL REFERENCES readers (id) ON DELETE CASCADE,
+17 -7
View File
@@ -15,17 +15,24 @@ type Session struct {
// CreateSession stores a new session row for reader. The id is generated by
// the caller (session.NewID) — the store only persists it. Expired rows that
// were never looked up are swept here: this is the one write every login
// makes, so the table stays bounded without a background job.
// were never looked up are swept in the same transaction: this is the one
// write every login makes, so the table stays bounded without a background
// job.
func (s *Store) CreateSession(id string, readerID int64, ttl time.Duration) (Session, error) {
expires := time.Now().Add(ttl)
_, err := s.db.Exec(`INSERT INTO sessions (id, reader_id, expires_at) VALUES ($1, $2, $3)`,
id, readerID, expires)
tx, err := s.db.Begin()
if err != nil {
return Session{}, err
}
_, err = s.db.Exec(`DELETE FROM sessions WHERE expires_at < now()`)
if err != nil {
defer tx.Rollback()
expires := time.Now().Add(ttl)
if _, err := tx.Exec(`INSERT INTO sessions (id, reader_id, expires_at) VALUES ($1, $2, $3)`,
id, readerID, expires); err != nil {
return Session{}, err
}
if _, err := tx.Exec(`DELETE FROM sessions WHERE expires_at < now()`); err != nil {
return Session{}, err
}
if err := tx.Commit(); err != nil {
return Session{}, err
}
return Session{ID: id, ReaderID: readerID, ExpiresAt: expires}, nil
@@ -46,6 +53,9 @@ func (s *Store) GetSession(id string, now time.Time) (Session, bool, error) {
return Session{}, false, err
}
if !sess.ExpiresAt.After(now) {
// Best-effort: the row is dead either way; failing the request over a
// cleanup delete would only hide the real error. CreateSession's
// sweep catches anything this misses.
_, _ = s.db.Exec(`DELETE FROM sessions WHERE id = $1`, id)
return Session{}, false, nil
}