store: oidc_state CRUD + 5-minute cleanup
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PutOIDCState stores the (state_hash, code_verifier) pair created
|
||||
// at /auth/oidc/login start. Called once per login attempt.
|
||||
func (s *Store) PutOIDCState(ctx context.Context, stateHash, verifier string, createdAt time.Time) error {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO oidc_state (state_hash, code_verifier, created_at)
|
||||
VALUES (?, ?, ?)`,
|
||||
stateHash, verifier,
|
||||
createdAt.UTC().Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
return fmt.Errorf("store: put oidc state: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConsumeOIDCState atomically reads + deletes the row in one go,
|
||||
// returning the code_verifier. Single-use — a re-play returns
|
||||
// ErrNotFound. Used by the OIDC callback handler.
|
||||
func (s *Store) ConsumeOIDCState(ctx context.Context, stateHash string) (string, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("store: begin: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
var verifier string
|
||||
err = tx.QueryRowContext(ctx,
|
||||
`SELECT code_verifier FROM oidc_state WHERE state_hash = ?`,
|
||||
stateHash).Scan(&verifier)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return "", fmt.Errorf("store: consume oidc state: %w", err)
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
`DELETE FROM oidc_state WHERE state_hash = ?`, stateHash); err != nil {
|
||||
return "", fmt.Errorf("store: delete oidc state: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return "", fmt.Errorf("store: commit: %w", err)
|
||||
}
|
||||
return verifier, nil
|
||||
}
|
||||
|
||||
// CleanupExpiredOIDCState removes entries created before cutoff.
|
||||
// Called on the alert engine's 60s tick alongside setup-token sweep.
|
||||
func (s *Store) CleanupExpiredOIDCState(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx,
|
||||
`DELETE FROM oidc_state WHERE created_at < ?`,
|
||||
cutoff.UTC().Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("store: cleanup oidc state: %w", err)
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return n, nil
|
||||
}
|
||||
Reference in New Issue
Block a user