Files
Pete/internal/storage/prefs.go
prosolis cbbedd9894 Add optional Authentik (OIDC) sign-in with server-side preference sync
Signed-in users get their preferences (hidden feeds, weather location,
weather toggle) stored server-side keyed by their OIDC subject and synced
across devices. Anonymous visitors keep using browser localStorage, so the
site stays public. First sign-in migrates existing localStorage prefs up.

- config: [web.auth] section (issuer, client_id/secret, redirect, session_secret)
- storage: user_preferences table + Get/PutUserPrefs
- web/auth: OIDC code flow, HMAC-signed session cookie, CSRF state + nonce
- web/prefs_api: GET/PUT /api/preferences (auth-gated, 64KB cap)
- frontend: prefs.js sync layer seeds localStorage from server, pushes on write
- header: sign-in / account control

OIDC discovery is non-fatal at boot: if Authentik is down, Pete serves
anonymously rather than refusing to start.
2026-06-21 15:44:53 -07:00

51 lines
1.5 KiB
Go

package storage
import (
"database/sql"
"errors"
"fmt"
)
// UserPrefs is one signed-in user's stored state. Prefs is an opaque JSON blob
// owned by the web frontend (disabled feeds, weather location, toggles, …) —
// the backend just persists and returns it, keyed by the OIDC subject.
type UserPrefs struct {
Sub string
Prefs string
Username string
Email string
UpdatedAt int64
}
// GetUserPrefs returns the stored prefs blob for an OIDC subject. It returns
// ("", nil) when the user has no record yet (first sign-in).
func GetUserPrefs(sub string) (string, error) {
var prefs string
err := Get().QueryRow(`SELECT prefs FROM user_preferences WHERE user_sub = ?`, sub).Scan(&prefs)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("get user prefs: %w", err)
}
return prefs, nil
}
// PutUserPrefs upserts a user's prefs blob along with identity hints (username,
// email) for later features. The prefs string is stored verbatim.
func PutUserPrefs(sub, prefs, username, email string) error {
_, err := Get().Exec(`
INSERT INTO user_preferences (user_sub, prefs, username, email, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(user_sub) DO UPDATE SET
prefs = excluded.prefs,
username = excluded.username,
email = excluded.email,
updated_at = excluded.updated_at`,
sub, prefs, username, email, nowUnix())
if err != nil {
return fmt.Errorf("put user prefs: %w", err)
}
return nil
}