nandi/gleanpublic Fork 0
12d6fac
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Enforce GLEAN_SESSION_KEY requirementUnverified

Julien Robert committed 2026-04-23T22:54:50+02:00 Browse files
12d6fac parent: fa08b45
modified .env.example +1 -0
@@ -1,5 +1,6 @@
11 GLEAN_ADDR=:8080
22 GLEAN_DB=glean.db
3+GLEAN_SESSION_KEY=change-me-to-a-random-string
34 GLEAN_JETSTREAM=wss://jetstream.glean.at
45 GLEAN_PLC_URL=https://didplc.glean.at
56 GLEAN_SYNC_INTERVAL=10m
@@ -1,5 +1,6 @@
1 GLEAN_ADDR=:80801 GLEAN_ADDR=:8080
2 GLEAN_DB=glean.db2 GLEAN_DB=glean.db
3+GLEAN_SESSION_KEY=change-me-to-a-random-string
3 GLEAN_JETSTREAM=wss://jetstream.glean.at4 GLEAN_JETSTREAM=wss://jetstream.glean.at
4 GLEAN_PLC_URL=https://didplc.glean.at5 GLEAN_PLC_URL=https://didplc.glean.at
5 GLEAN_SYNC_INTERVAL=10m6 GLEAN_SYNC_INTERVAL=10m
modified docs/specs.md +2 -0
@@ -986,6 +986,8 @@ glean/
986986
987987 ## 10. Auth Flow
988988
989+Session cookies are HMAC-signed using `GLEAN_SESSION_KEY` (required, must be set to a random string). The server refuses to start without it.
990+
989991 DID resolution uses a configurable PLC directory (`GLEAN_PLC_URL`, defaults to `https://didplc.glean.at`). The identity directory is initialized once at startup via `InitIdentity()` with a caching layer (250k entries, 24h TTL).
990992
991993 1. User visits `/`, clicks "Sign in with Bluesky" (or any AT Proto PDS)
@@ -986,6 +986,8 @@ glean/
986 986
987 ## 10. Auth Flow987 ## 10. Auth Flow
988 988
989+Session cookies are HMAC-signed using `GLEAN_SESSION_KEY` (required, must be set to a random string). The server refuses to start without it.
990+
989 DID resolution uses a configurable PLC directory (`GLEAN_PLC_URL`, defaults to `https://didplc.glean.at`). The identity directory is initialized once at startup via `InitIdentity()` with a caching layer (250k entries, 24h TTL).991 DID resolution uses a configurable PLC directory (`GLEAN_PLC_URL`, defaults to `https://didplc.glean.at`). The identity directory is initialized once at startup via `InitIdentity()` with a caching layer (250k entries, 24h TTL).
990 992
991 1. User visits `/`, clicks "Sign in with Bluesky" (or any AT Proto PDS)993 1. User visits `/`, clicks "Sign in with Bluesky" (or any AT Proto PDS)
modified internal/server/server.go +2 -2
@@ -71,7 +71,7 @@ type Server struct {
7171 sessionKey []byte
7272 }
7373
74-func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.Scheduler, engine *cluster.Engine, logger *slog.Logger) *Server {
74+func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.Scheduler, engine *cluster.Engine, logger *slog.Logger, sessionKey []byte) *Server {
7575 oauthStore := db.NewOAuthStore(dbs)
7676
7777 var config oauth.ClientConfig
@@ -99,7 +99,7 @@ func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.
9999 scraper: scraper.New(logger),
100100 clientID: clientID,
101101 callbackURL: callbackURL,
102- sessionKey: loadSessionKey(),
102+ sessionKey: sessionKey,
103103 }
104104
105105 s.setupMiddleware()
@@ -71,7 +71,7 @@ type Server struct {
71 sessionKey []byte71 sessionKey []byte
72 }72 }
73 73
74-func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.Scheduler, engine *cluster.Engine, logger *slog.Logger) *Server {74+func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.Scheduler, engine *cluster.Engine, logger *slog.Logger, sessionKey []byte) *Server {
75 oauthStore := db.NewOAuthStore(dbs)75 oauthStore := db.NewOAuthStore(dbs)
76 76
77 var config oauth.ClientConfig77 var config oauth.ClientConfig
@@ -99,7 +99,7 @@ func New(dbs *db.Databases, clientID, callbackURL, addr string, scheduler *feed.
99 scraper: scraper.New(logger),99 scraper: scraper.New(logger),
100 clientID: clientID,100 clientID: clientID,
101 callbackURL: callbackURL,101 callbackURL: callbackURL,
102- sessionKey: loadSessionKey(),102+ sessionKey: sessionKey,
103 }103 }
104 104
105 s.setupMiddleware()105 s.setupMiddleware()
modified internal/server/session.go +0 -9
@@ -7,7 +7,6 @@ import (
77 "encoding/base64"
88 "encoding/json"
99 "net/http"
10- "os"
1110
1211 "pkg.rbrt.fr/glean/internal/atproto"
1312 "pkg.rbrt.fr/glean/internal/db"
@@ -96,14 +95,6 @@ func (s *Server) getSessionData(r *http.Request) *sessionData {
9695 return data
9796 }
9897
99-func loadSessionKey() []byte {
100- key := os.Getenv("GLEAN_SESSION_KEY")
101- if key == "" {
102- key = "default-dev-key-change-in-production"
103- }
104- return []byte(key)
105-}
106-
10798 func encodeSession(key []byte, data sessionData) (string, error) {
10899 payload, err := json.Marshal(data)
109100 if err != nil {
@@ -7,7 +7,6 @@ import (
7 "encoding/base64"7 "encoding/base64"
8 "encoding/json"8 "encoding/json"
9 "net/http"9 "net/http"
10- "os"
11 10
12 "pkg.rbrt.fr/glean/internal/atproto"11 "pkg.rbrt.fr/glean/internal/atproto"
13 "pkg.rbrt.fr/glean/internal/db"12 "pkg.rbrt.fr/glean/internal/db"
@@ -96,14 +95,6 @@ func (s *Server) getSessionData(r *http.Request) *sessionData {
96 return data95 return data
97 }96 }
98 97
99-func loadSessionKey() []byte {
100- key := os.Getenv("GLEAN_SESSION_KEY")
101- if key == "" {
102- key = "default-dev-key-change-in-production"
103- }
104- return []byte(key)
105-}
106-
107 func encodeSession(key []byte, data sessionData) (string, error) {98 func encodeSession(key []byte, data sessionData) (string, error) {
108 payload, err := json.Marshal(data)99 payload, err := json.Marshal(data)
109 if err != nil {100 if err != nil {
modified main.go +7 -1
@@ -28,8 +28,14 @@ func main() {
2828 fetchInterval := flag.Duration("fetch-interval", envDuration("GLEAN_FETCH_INTERVAL", 5*time.Minute), "feed fetch tick interval")
2929 collectionDirURL := flag.String("collection-dir", envOr("GLEAN_COLLECTION_DIR_URL", ""), "collection directory URL for startup backfill")
3030 backfillConcurrency := flag.Int("backfill-concurrency", envInt("GLEAN_BACKFILL_CONCURRENCY", 5), "max concurrent backfill workers")
31+ sessionKey := envOr("GLEAN_SESSION_KEY", "")
3132 flag.Parse()
3233
34+ if sessionKey == "" {
35+ fmt.Fprintln(os.Stderr, "GLEAN_SESSION_KEY is required")
36+ os.Exit(1)
37+ }
38+
3339 atproto.InitIdentity(envOr("GLEAN_PLC_URL", "https://didplc.glean.at"))
3440
3541 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
@@ -49,7 +55,7 @@ func main() {
4955
5056 engine := cluster.NewEngine(dbs.DB(), logger)
5157
52- srv := server.New(dbs, clientID, callbackURL, *addr, scheduler, engine, logger)
58+ srv := server.New(dbs, clientID, callbackURL, *addr, scheduler, engine, logger, []byte(sessionKey))
5359
5460 cron := cluster.NewCron(engine, *clusterInterval, logger)
5561
@@ -28,8 +28,14 @@ func main() {
28 fetchInterval := flag.Duration("fetch-interval", envDuration("GLEAN_FETCH_INTERVAL", 5*time.Minute), "feed fetch tick interval")28 fetchInterval := flag.Duration("fetch-interval", envDuration("GLEAN_FETCH_INTERVAL", 5*time.Minute), "feed fetch tick interval")
29 collectionDirURL := flag.String("collection-dir", envOr("GLEAN_COLLECTION_DIR_URL", ""), "collection directory URL for startup backfill")29 collectionDirURL := flag.String("collection-dir", envOr("GLEAN_COLLECTION_DIR_URL", ""), "collection directory URL for startup backfill")
30 backfillConcurrency := flag.Int("backfill-concurrency", envInt("GLEAN_BACKFILL_CONCURRENCY", 5), "max concurrent backfill workers")30 backfillConcurrency := flag.Int("backfill-concurrency", envInt("GLEAN_BACKFILL_CONCURRENCY", 5), "max concurrent backfill workers")
31+ sessionKey := envOr("GLEAN_SESSION_KEY", "")
31 flag.Parse()32 flag.Parse()
32 33
34+ if sessionKey == "" {
35+ fmt.Fprintln(os.Stderr, "GLEAN_SESSION_KEY is required")
36+ os.Exit(1)
37+ }
38+
33 atproto.InitIdentity(envOr("GLEAN_PLC_URL", "https://didplc.glean.at"))39 atproto.InitIdentity(envOr("GLEAN_PLC_URL", "https://didplc.glean.at"))
34 40
35 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))41 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
@@ -49,7 +55,7 @@ func main() {
49 55
50 engine := cluster.NewEngine(dbs.DB(), logger)56 engine := cluster.NewEngine(dbs.DB(), logger)
51 57
52- srv := server.New(dbs, clientID, callbackURL, *addr, scheduler, engine, logger)58+ srv := server.New(dbs, clientID, callbackURL, *addr, scheduler, engine, logger, []byte(sessionKey))
53 59
54 cron := cluster.NewCron(engine, *clusterInterval, logger)60 cron := cluster.NewCron(engine, *clusterInterval, logger)
55 61
modified readme.md +1 -0
@@ -39,6 +39,7 @@ Then open `http://localhost:8080`.
3939
4040 | Variable | Default | What it does |
4141 | -------------------------- | -------------------------- | --------------------------------------------------------- |
42+| `GLEAN_SESSION_KEY` | _(required)_ | Secret key for signing session cookies (any random string) |
4243 | `GLEAN_ADDR` | `:8080` | Listen address |
4344 | `GLEAN_DB` | `glean.db` | SQLite base path (`_users`, `_articles`, `_recs` suffixes) |
4445 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |
@@ -39,6 +39,7 @@ Then open `http://localhost:8080`.
39 39
40 | Variable | Default | What it does |40 | Variable | Default | What it does |
41 | -------------------------- | -------------------------- | --------------------------------------------------------- |41 | -------------------------- | -------------------------- | --------------------------------------------------------- |
42+| `GLEAN_SESSION_KEY` | _(required)_ | Secret key for signing session cookies (any random string) |
42 | `GLEAN_ADDR` | `:8080` | Listen address |43 | `GLEAN_ADDR` | `:8080` | Listen address |
43 | `GLEAN_DB` | `glean.db` | SQLite base path (`_users`, `_articles`, `_recs` suffixes) |44 | `GLEAN_DB` | `glean.db` | SQLite base path (`_users`, `_articles`, `_recs` suffixes) |
44 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |45 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |