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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user