v0.12.0 migrate sqlite3 to psql18, updated auth, deploy docs, account and replay updates
test / test (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-09-26 20:58:19 -04:00
1 parent 5d2be4abb2
commit d0326b059e
66 files changed
+4016 -2433

No files matched your search

+50 -61
View File
@@ -1,20 +1,30 @@
package storage
import (
"database/sql"
"fmt"
"log/slog"
"time"
)
// CreateSession creates or replaces the session for a user (single session per user)
// CreateSession creates or replaces the user's single session and records the
// login time in the same statement, so a login is one round trip.
func (s *Store) CreateSession(record SessionRecord) error {
const query = `INSERT INTO sessions (session_id, user_id, created_at, expires_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_id) DO UPDATE SET
session_id = excluded.session_id,
created_at = excluded.created_at,
expires_at = excluded.expires_at`
if _, err := s.db.Exec(query, record.SessionID, record.UserID, record.CreatedAt, record.ExpiresAt); err != nil {
ctx, cancel := opContext()
defer cancel()
const query = `WITH session AS (
INSERT INTO sessions (session_id, user_id, created_at, expires_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (user_id) DO UPDATE SET
session_id = excluded.session_id,
created_at = excluded.created_at,
expires_at = excluded.expires_at
RETURNING user_id
)
UPDATE users SET last_login_at = $3 WHERE user_id = (SELECT user_id FROM session)`
if _, err := s.db.ExecContext(ctx, query,
record.SessionID, record.UserID, record.CreatedAt, record.ExpiresAt,
); err != nil {
return fmt.Errorf("failed to create session: %w", err)
}
slog.Debug("storage session created", "user_id", record.UserID, "expires_at", record.ExpiresAt)
@@ -23,56 +33,44 @@ func (s *Store) CreateSession(record SessionRecord) error {
// GetSession retrieves a session by ID
func (s *Store) GetSession(sessionID string) (*SessionRecord, error) {
if !validUUID(sessionID) {
return nil, sql.ErrNoRows
}
ctx, cancel := opContext()
defer cancel()
var session SessionRecord
query := `SELECT session_id, user_id, created_at, expires_at FROM sessions WHERE session_id = ?`
err := s.db.QueryRow(query, sessionID).Scan(
&session.SessionID, &session.UserID, &session.CreatedAt, &session.ExpiresAt,
)
err := s.db.QueryRowContext(ctx,
`SELECT session_id, user_id, created_at, expires_at FROM sessions WHERE session_id = $1`,
sessionID,
).Scan(&session.SessionID, &session.UserID, &session.CreatedAt, &session.ExpiresAt)
if err != nil {
return nil, err
}
session.CreatedAt = session.CreatedAt.UTC()
session.ExpiresAt = session.ExpiresAt.UTC()
return &session, nil
}
// GetSessionByUserID retrieves the active session for a user
func (s *Store) GetSessionByUserID(userID string) (*SessionRecord, error) {
var session SessionRecord
query := `SELECT session_id, user_id, created_at, expires_at FROM sessions WHERE user_id = ?`
err := s.db.QueryRow(query, userID).Scan(
&session.SessionID, &session.UserID, &session.CreatedAt, &session.ExpiresAt,
)
if err != nil {
return nil, err
}
return &session, nil
}
// DeleteSession removes a session
// DeleteSession removes a session (logout). Deleting an absent session is not
// an error.
func (s *Store) DeleteSession(sessionID string) error {
query := `DELETE FROM sessions WHERE session_id = ?`
_, err := s.db.Exec(query, sessionID)
if err == nil {
slog.Debug("storage session deleted")
if !validUUID(sessionID) {
return nil
}
return err
}
// DeleteSessionByUserID removes all sessions for a user
func (s *Store) DeleteSessionByUserID(userID string) error {
query := `DELETE FROM sessions WHERE user_id = ?`
_, err := s.db.Exec(query, userID)
if err == nil {
slog.Debug("storage user sessions deleted", "user_id", userID)
ctx, cancel := opContext()
defer cancel()
if _, err := s.db.ExecContext(ctx, `DELETE FROM sessions WHERE session_id = $1`, sessionID); err != nil {
return err
}
return err
slog.Debug("storage session deleted")
return nil
}
// DeleteExpiredSessions removes expired sessions
func (s *Store) DeleteExpiredSessions() (int64, error) {
query := `DELETE FROM sessions WHERE expires_at < ?`
result, err := s.db.Exec(query, time.Now().UTC())
ctx, cancel := opContext()
defer cancel()
result, err := s.db.ExecContext(ctx, `DELETE FROM sessions WHERE expires_at < $1`, time.Now().UTC())
if err != nil {
return 0, err
}
@@ -83,29 +81,20 @@ func (s *Store) DeleteExpiredSessions() (int64, error) {
return deleted, err
}
// IsSessionValid checks if a session exists and is not expired
func (s *Store) IsSessionValid(sessionID string) (bool, error) {
var valid bool
const query = `SELECT EXISTS(
SELECT 1 FROM sessions WHERE session_id = ? AND expires_at > ?
)`
err := s.db.QueryRow(query, sessionID, time.Now().UTC()).Scan(&valid)
if err != nil {
return false, err
}
return valid, nil
}
// IsSessionValidForUser verifies both expiry and the binding between a JWT
// subject and its persisted session. Checking only the session ID would allow
// a malformed server-issued token to authenticate as the wrong subject.
func (s *Store) IsSessionValidForUser(sessionID, userID string) (bool, error) {
if !validUUID(sessionID) || !validUUID(userID) {
return false, nil
}
ctx, cancel := opContext()
defer cancel()
var valid bool
const query = `SELECT EXISTS(
err := s.db.QueryRowContext(ctx, `SELECT EXISTS(
SELECT 1 FROM sessions
WHERE session_id = ? AND user_id = ? AND expires_at > ?
)`
err := s.db.QueryRow(query, sessionID, userID, time.Now().UTC()).Scan(&valid)
WHERE session_id = $1 AND user_id = $2 AND expires_at > $3
)`, sessionID, userID, time.Now().UTC()).Scan(&valid)
if err != nil {
return false, err
}