A social RSS reader built on the AT Protocol. glean.at
glean atproto atmosphere rss feed social app
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Enforce GLEAN_SESSION_KEY requirement

+13 -12
+1
.env.example
··· 1 1 GLEAN_ADDR=:8080 2 2 GLEAN_DB=glean.db 3 + GLEAN_SESSION_KEY=change-me-to-a-random-string 3 4 GLEAN_JETSTREAM=wss://jetstream.glean.at 4 5 GLEAN_PLC_URL=https://didplc.glean.at 5 6 GLEAN_SYNC_INTERVAL=10m
+2
docs/specs.md
··· 986 986 987 987 ## 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 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 993 1. User visits `/`, clicks "Sign in with Bluesky" (or any AT Proto PDS)
+2 -2
internal/server/server.go
··· 71 71 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 75 oauthStore := db.NewOAuthStore(dbs) 76 76 77 77 var config oauth.ClientConfig ··· 99 99 scraper: scraper.New(logger), 100 100 clientID: clientID, 101 101 callbackURL: callbackURL, 102 - sessionKey: loadSessionKey(), 102 + sessionKey: sessionKey, 103 103 } 104 104 105 105 s.setupMiddleware()
-9
internal/server/session.go
··· 7 7 "encoding/base64" 8 8 "encoding/json" 9 9 "net/http" 10 - "os" 11 10 12 11 "pkg.rbrt.fr/glean/internal/atproto" 13 12 "pkg.rbrt.fr/glean/internal/db" ··· 94 93 return nil 95 94 } 96 95 return data 97 - } 98 - 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 96 } 106 97 107 98 func encodeSession(key []byte, data sessionData) (string, error) {
+7 -1
main.go
··· 28 28 fetchInterval := flag.Duration("fetch-interval", envDuration("GLEAN_FETCH_INTERVAL", 5*time.Minute), "feed fetch tick interval") 29 29 collectionDirURL := flag.String("collection-dir", envOr("GLEAN_COLLECTION_DIR_URL", ""), "collection directory URL for startup backfill") 30 30 backfillConcurrency := flag.Int("backfill-concurrency", envInt("GLEAN_BACKFILL_CONCURRENCY", 5), "max concurrent backfill workers") 31 + sessionKey := envOr("GLEAN_SESSION_KEY", "") 31 32 flag.Parse() 33 + 34 + if sessionKey == "" { 35 + fmt.Fprintln(os.Stderr, "GLEAN_SESSION_KEY is required") 36 + os.Exit(1) 37 + } 32 38 33 39 atproto.InitIdentity(envOr("GLEAN_PLC_URL", "https://didplc.glean.at")) 34 40 ··· 49 55 50 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 60 cron := cluster.NewCron(engine, *clusterInterval, logger) 55 61
+1
readme.md
··· 39 39 40 40 | Variable | Default | What it does | 41 41 | -------------------------- | -------------------------- | --------------------------------------------------------- | 42 + | `GLEAN_SESSION_KEY` | _(required)_ | Secret key for signing session cookies (any random string) | 42 43 | `GLEAN_ADDR` | `:8080` | Listen address | 43 44 | `GLEAN_DB` | `glean.db` | SQLite base path (`_users`, `_articles`, `_recs` suffixes) | 44 45 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |