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

Configure Feed

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

at main 3.9 kB View raw
1package caddyatprotoauth 2 3import ( 4 "fmt" 5 "net" 6 "path/filepath" 7 "strconv" 8 "time" 9 10 "github.com/caddyserver/caddy/v2" 11 "github.com/caddyserver/caddy/v2/caddyconfig" 12 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 13 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" 14 15 "tangled.org/vvill.dev/caddy-atproto-auth/internal/db" 16) 17 18func init() { 19 caddy.RegisterModule(&App{}) 20 httpcaddyfile.RegisterGlobalOption("atproto", parseGlobalAtproto) 21} 22 23// App configures the global atproto integration. 24type App struct { 25 StoragePath string `json:"storage_path,omitempty"` 26 CookieSecret string `json:"cookie_secret,omitempty"` 27 SessionDurationStr string `json:"session_duration,omitempty"` 28 OAuthManagerCacheSize int `json:"oauth_manager_cache_size,omitempty"` 29 AllowPrivateCIDRs []net.IPNet `json:"allow_private_cidrs,omitempty"` 30 31 // Internal state 32 Store *db.Store `json:"-"` 33 SessionDuration time.Duration `json:"-"` 34} 35 36// CaddyModule returns the Caddy module information. 37func (*App) CaddyModule() caddy.ModuleInfo { 38 return caddy.ModuleInfo{ 39 ID: "atproto", 40 New: func() caddy.Module { return new(App) }, 41 } 42} 43 44// Provision sets up the global app state. 45func (a *App) Provision(ctx caddy.Context) error { 46 // Defaults 47 if a.StoragePath == "" { 48 a.StoragePath = filepath.Join(caddy.AppDataDir(), "atproto.db") 49 } 50 51 // Initialize DB 52 store, err := db.NewStore(a.StoragePath) 53 if err != nil { 54 return fmt.Errorf("failed to initialize atproto storage: %w", err) 55 } 56 a.Store = store 57 58 // Resolve Cookie Secret 59 if a.CookieSecret == "" { 60 // Try to load/generate from DB 61 secret, err := a.Store.GetCookieSecret(ctx) 62 if err != nil { 63 return fmt.Errorf("failed to resolve cookie secret: %w", err) 64 } 65 a.CookieSecret = secret 66 } 67 68 // Parse session duration 69 a.SessionDuration = 24 * 7 * time.Hour 70 if a.SessionDurationStr != "" { 71 d, err := caddy.ParseDuration(a.SessionDurationStr) 72 if err != nil { 73 return fmt.Errorf("invalid session_duration: %w", err) 74 } 75 a.SessionDuration = time.Duration(d) 76 } 77 78 if a.OAuthManagerCacheSize <= 0 { 79 a.OAuthManagerCacheSize = 100 // Default max oauth managers 80 } 81 82 return nil 83} 84 85// Start starts the application. 86func (a *App) Start() error { 87 return nil 88} 89 90// Stop stops the application. 91func (a *App) Stop() error { 92 if a.Store != nil { 93 return a.Store.Close() 94 } 95 return nil 96} 97 98// Interface guards 99var ( 100 _ caddy.App = (*App)(nil) 101 _ caddy.Provisioner = (*App)(nil) 102) 103 104// parseGlobalAtproto parses the global 'atproto' Caddyfile option. 105// Format: 106// 107// atproto { 108// storage_path /path/to/db 109// cookie_secret <secret> 110// } 111func parseGlobalAtproto(d *caddyfile.Dispenser, _ any) (any, error) { 112 app := &App{} 113 for d.Next() { 114 for d.NextBlock(0) { 115 switch d.Val() { 116 case "storage_path": 117 if !d.NextArg() { 118 return nil, d.ArgErr() 119 } 120 app.StoragePath = d.Val() 121 case "cookie_secret": 122 if !d.NextArg() { 123 return nil, d.ArgErr() 124 } 125 app.CookieSecret = d.Val() 126 case "session_duration": 127 if !d.NextArg() { 128 return nil, d.ArgErr() 129 } 130 app.SessionDurationStr = d.Val() 131 case "oauth_manager_cache_size": 132 if !d.NextArg() { 133 return nil, d.ArgErr() 134 } 135 val, err := strconv.Atoi(d.Val()) 136 if err != nil { 137 return nil, d.Errf("invalid oauth_manager_cache_size: %v", err) 138 } 139 app.OAuthManagerCacheSize = val 140 case "allow_private_cidrs": 141 for _, c := range d.RemainingArgs() { 142 _, ipnet, err := net.ParseCIDR(c) 143 if err != nil || ipnet == nil { 144 return nil, d.ArgErr() 145 } 146 app.AllowPrivateCIDRs = append(app.AllowPrivateCIDRs, *ipnet) 147 } 148 default: 149 return nil, d.Errf("unrecognized subdirective '%s'", d.Val()) 150 } 151 } 152 } 153 return httpcaddyfile.App{ 154 Name: "atproto", 155 Value: caddyconfig.JSON(app, nil), 156 }, nil 157}