Caddy module to require at-proto authentication and restrict routes to DIDs
3

Configure Feed

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

1package caddyatprotoauth 2 3import ( 4 "fmt" 5 6 "github.com/caddyserver/caddy/v2" 7 "github.com/caddyserver/caddy/v2/caddyconfig" 8 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 9 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" 10 11 "github.com/vvill/caddy-atproto-auth/internal/db" 12 "github.com/vvill/caddy-atproto-auth/internal/oauth" 13) 14 15func init() { 16 caddy.RegisterModule(App{}) 17 httpcaddyfile.RegisterGlobalOption("atproto", parseGlobalAtproto) 18} 19 20// App configures the global atproto integration. 21type App struct { 22 StoragePath string `json:"storage_path,omitempty"` 23 CookieSecret string `json:"cookie_secret,omitempty"` 24 25 // Internal state 26 Store *db.Store `json:"-"` 27 OAuthManager *oauth.Manager `json:"-"` 28} 29 30// CaddyModule returns the Caddy module information. 31func (App) CaddyModule() caddy.ModuleInfo { 32 return caddy.ModuleInfo{ 33 ID: "atproto", 34 New: func() caddy.Module { return new(App) }, 35 } 36} 37 38// Provision sets up the global app state. 39func (a *App) Provision(ctx caddy.Context) error { 40 // Defaults 41 if a.StoragePath == "" { 42 a.StoragePath = "atproto.db" // Relative to workdir or specific path 43 } 44 // Resolve relative path against Caddy's storage or workdir if needed. 45 // For simplicity, we assume absolute or relative to CWD. 46 47 // Initialize DB 48 store, err := db.NewStore(a.StoragePath) 49 if err != nil { 50 return fmt.Errorf("failed to initialize atproto storage: %w", err) 51 } 52 a.Store = store 53 54 // Initialize OAuth Manager (requires client ID and callback URL to be fully configured, 55 // but those might be per-portal or global. The spec says "acts as an OAuth Client". 56 // If the plugin acts as a *single* client for many subdomains, we need global config for client ID. 57 // But spec says: "Path A: The Self-Contained Route" and "Path B: The Auth Hub". 58 // This implies potentially different client IDs for different sites OR one central hub. 59 // For now, let's defer OAuthManager creation to the Portal or Gate if it's per-route, 60 // OR we need to add ClientID/CallbackURL to the global config if it's shared. 61 // 62 // Looking at the spec: 63 // "The module acts as an OAuth Client" 64 // "Global Configuration: storage_path, cookie_secret" 65 // 66 // It seems the App module holds the *Storage* and *Keys*. 67 // The *Portal* (or Gate) defines the "Client" identity (metadata, callback). 68 // However, `oauth.NewManager` takes a `db.Store`. So the App owns the Store. 69 // The Portal will instantiate the Manager using the App's Store. 70 71 return nil 72} 73 74// Start starts the application. 75func (a *App) Start() error { 76 return nil 77} 78 79// Stop stops the application. 80func (a *App) Stop() error { 81 if a.Store != nil { 82 return a.Store.Close() 83 } 84 return nil 85} 86 87// Interface guards 88var ( 89 _ caddy.App = (*App)(nil) 90 _ caddy.Provisioner = (*App)(nil) 91) 92 93// parseGlobalAtproto parses the global 'atproto' Caddyfile option. 94// Format: 95// 96// atproto { 97// storage_path /path/to/db 98// cookie_secret <secret> 99// } 100func parseGlobalAtproto(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { 101 app := &App{} 102 for d.Next() { 103 for d.NextBlock(0) { 104 switch d.Val() { 105 case "storage_path": 106 if !d.NextArg() { 107 return nil, d.ArgErr() 108 } 109 app.StoragePath = d.Val() 110 case "cookie_secret": 111 if !d.NextArg() { 112 return nil, d.ArgErr() 113 } 114 app.CookieSecret = d.Val() 115 default: 116 return nil, d.Errf("unrecognized subdirective '%s'", d.Val()) 117 } 118 } 119 } 120 return httpcaddyfile.App{ 121 Name: "atproto", 122 Value: caddyconfig.JSON(app, nil), 123 }, nil 124}