package main import ( "crypto/rand" "crypto/sha256" "database/sql" "encoding/base64" "encoding/hex" "time" _ "modernc.org/sqlite" ) // DB holds the broker's only state: the per-instance bearer tokens the admin has // minted. Each token's label IS the instance's origin (e.g. "https://test.example.com") // — used both for logging and as the redirect-relay allowlist, so the relay only // ever bounces a code back to an origin an admin has explicitly registered. It // stores NO OAuth traffic — no codes, no access tokens ever touch disk. Tokens // are stored hashed (cf. the push relay), so a stolen DB yields no live credential. type DB struct{ sql *sql.DB } func openDB(path string) (*DB, error) { d, err := sql.Open("sqlite", path) if err != nil { return nil, err } d.SetMaxOpenConns(1) for _, p := range []string{ "PRAGMA journal_mode=WAL", "PRAGMA busy_timeout=5000", "PRAGMA synchronous=NORMAL", } { if _, err := d.Exec(p); err != nil { return nil, err } } db := &DB{sql: d} if err := db.migrate(); err != nil { return nil, err } return db, nil } func (db *DB) migrate() error { _, err := db.sql.Exec(` CREATE TABLE IF NOT EXISTS instance_tokens ( id TEXT PRIMARY KEY, -- random public handle, used to revoke label TEXT NOT NULL, -- the instance ORIGIN (scheme+host), e.g. https://test.example.com token_hash TEXT NOT NULL UNIQUE, -- sha256(bearer token); the secret itself is never stored created_at INTEGER NOT NULL, last_used_at INTEGER );`) return err } func hashToken(t string) string { h := sha256.Sum256([]byte(t)) return hex.EncodeToString(h[:]) } func randToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(b), nil } // InstanceToken is a token row for the admin listing (never carries the secret). type InstanceToken struct { ID string `json:"id"` Label string `json:"label"` CreatedAt int64 `json:"created_at"` LastUsedAt *int64 `json:"last_used_at,omitempty"` } // mintToken creates a per-instance token whose label is the instance origin, // stores only its hash, and returns the plaintext ONCE (unrecoverable after). func (db *DB) mintToken(label string) (id, plaintext string, err error) { if id, err = randToken(); err != nil { return "", "", err } if plaintext, err = randToken(); err != nil { return "", "", err } _, err = db.sql.Exec( `INSERT INTO instance_tokens(id, label, token_hash, created_at) VALUES(?, ?, ?, ?)`, id, label, hashToken(plaintext), time.Now().Unix(), ) if err != nil { return "", "", err } return id, plaintext, nil } // authInstance resolves a bearer token to its instance origin (label), bumping // last_used_at. Returns ""/false for an empty or unknown token. func (db *DB) authInstance(token string) (origin string, ok bool) { if token == "" { return "", false } var id string err := db.sql.QueryRow( `SELECT id, label FROM instance_tokens WHERE token_hash = ?`, hashToken(token), ).Scan(&id, &origin) if err != nil { return "", false } _, _ = db.sql.Exec(`UPDATE instance_tokens SET last_used_at = ? WHERE id = ?`, time.Now().Unix(), id) return origin, true } // originAllowed reports whether an admin has registered this exact origin (via a // minted token). The redirect relay uses it so it can never be an open redirector. func (db *DB) originAllowed(origin string) bool { if origin == "" { return false } var x int return db.sql.QueryRow(`SELECT 1 FROM instance_tokens WHERE label = ?`, origin).Scan(&x) == nil } func (db *DB) listTokens() ([]InstanceToken, error) { rows, err := db.sql.Query( `SELECT id, label, created_at, last_used_at FROM instance_tokens ORDER BY created_at`, ) if err != nil { return nil, err } defer rows.Close() out := []InstanceToken{} for rows.Next() { var t InstanceToken var last sql.NullInt64 if err := rows.Scan(&t.ID, &t.Label, &t.CreatedAt, &last); err != nil { return nil, err } if last.Valid { t.LastUsedAt = &last.Int64 } out = append(out, t) } return out, rows.Err() } func (db *DB) revokeToken(id string) (bool, error) { res, err := db.sql.Exec(`DELETE FROM instance_tokens WHERE id = ?`, id) if err != nil { return false, err } n, _ := res.RowsAffected() return n > 0, nil }