eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "crypto/rand"
5 "crypto/sha256"
6 "database/sql"
7 "encoding/base64"
8 "encoding/hex"
9 "time"
10
11 _ "modernc.org/sqlite"
12)
13
14// DB holds the broker's only state: the per-instance bearer tokens the admin has
15// minted. Each token's label IS the instance's origin (e.g. "https://test.example.com")
16// — used both for logging and as the redirect-relay allowlist, so the relay only
17// ever bounces a code back to an origin an admin has explicitly registered. It
18// stores NO OAuth traffic — no codes, no access tokens ever touch disk. Tokens
19// are stored hashed (cf. the push relay), so a stolen DB yields no live credential.
20type DB struct{ sql *sql.DB }
21
22func openDB(path string) (*DB, error) {
23 d, err := sql.Open("sqlite", path)
24 if err != nil {
25 return nil, err
26 }
27 d.SetMaxOpenConns(1)
28 for _, p := range []string{
29 "PRAGMA journal_mode=WAL",
30 "PRAGMA busy_timeout=5000",
31 "PRAGMA synchronous=NORMAL",
32 } {
33 if _, err := d.Exec(p); err != nil {
34 return nil, err
35 }
36 }
37 db := &DB{sql: d}
38 if err := db.migrate(); err != nil {
39 return nil, err
40 }
41 return db, nil
42}
43
44func (db *DB) migrate() error {
45 _, err := db.sql.Exec(`
46CREATE TABLE IF NOT EXISTS instance_tokens (
47 id TEXT PRIMARY KEY, -- random public handle, used to revoke
48 label TEXT NOT NULL, -- the instance ORIGIN (scheme+host), e.g. https://test.example.com
49 token_hash TEXT NOT NULL UNIQUE, -- sha256(bearer token); the secret itself is never stored
50 created_at INTEGER NOT NULL,
51 last_used_at INTEGER
52);`)
53 return err
54}
55
56func hashToken(t string) string {
57 h := sha256.Sum256([]byte(t))
58 return hex.EncodeToString(h[:])
59}
60
61func randToken() (string, error) {
62 b := make([]byte, 32)
63 if _, err := rand.Read(b); err != nil {
64 return "", err
65 }
66 return base64.RawURLEncoding.EncodeToString(b), nil
67}
68
69// InstanceToken is a token row for the admin listing (never carries the secret).
70type InstanceToken struct {
71 ID string `json:"id"`
72 Label string `json:"label"`
73 CreatedAt int64 `json:"created_at"`
74 LastUsedAt *int64 `json:"last_used_at,omitempty"`
75}
76
77// mintToken creates a per-instance token whose label is the instance origin,
78// stores only its hash, and returns the plaintext ONCE (unrecoverable after).
79func (db *DB) mintToken(label string) (id, plaintext string, err error) {
80 if id, err = randToken(); err != nil {
81 return "", "", err
82 }
83 if plaintext, err = randToken(); err != nil {
84 return "", "", err
85 }
86 _, err = db.sql.Exec(
87 `INSERT INTO instance_tokens(id, label, token_hash, created_at) VALUES(?, ?, ?, ?)`,
88 id, label, hashToken(plaintext), time.Now().Unix(),
89 )
90 if err != nil {
91 return "", "", err
92 }
93 return id, plaintext, nil
94}
95
96// authInstance resolves a bearer token to its instance origin (label), bumping
97// last_used_at. Returns ""/false for an empty or unknown token.
98func (db *DB) authInstance(token string) (origin string, ok bool) {
99 if token == "" {
100 return "", false
101 }
102 var id string
103 err := db.sql.QueryRow(
104 `SELECT id, label FROM instance_tokens WHERE token_hash = ?`, hashToken(token),
105 ).Scan(&id, &origin)
106 if err != nil {
107 return "", false
108 }
109 _, _ = db.sql.Exec(`UPDATE instance_tokens SET last_used_at = ? WHERE id = ?`, time.Now().Unix(), id)
110 return origin, true
111}
112
113// originAllowed reports whether an admin has registered this exact origin (via a
114// minted token). The redirect relay uses it so it can never be an open redirector.
115func (db *DB) originAllowed(origin string) bool {
116 if origin == "" {
117 return false
118 }
119 var x int
120 return db.sql.QueryRow(`SELECT 1 FROM instance_tokens WHERE label = ?`, origin).Scan(&x) == nil
121}
122
123func (db *DB) listTokens() ([]InstanceToken, error) {
124 rows, err := db.sql.Query(
125 `SELECT id, label, created_at, last_used_at FROM instance_tokens ORDER BY created_at`,
126 )
127 if err != nil {
128 return nil, err
129 }
130 defer rows.Close()
131 out := []InstanceToken{}
132 for rows.Next() {
133 var t InstanceToken
134 var last sql.NullInt64
135 if err := rows.Scan(&t.ID, &t.Label, &t.CreatedAt, &last); err != nil {
136 return nil, err
137 }
138 if last.Valid {
139 t.LastUsedAt = &last.Int64
140 }
141 out = append(out, t)
142 }
143 return out, rows.Err()
144}
145
146func (db *DB) revokeToken(id string) (bool, error) {
147 res, err := db.sql.Exec(`DELETE FROM instance_tokens WHERE id = ?`, id)
148 if err != nil {
149 return false, err
150 }
151 n, _ := res.RowsAffected()
152 return n > 0, nil
153}
154