Caddy module to require at-proto authentication and restrict routes to DIDs
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 "tangled.org/vvill.dev/caddy-atproto-auth/internal/db"
12 "tangled.org/vvill.dev/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"
43 }
44
45 // Initialize DB
46 store, err := db.NewStore(a.StoragePath)
47 if err != nil {
48 return fmt.Errorf("failed to initialize atproto storage: %w", err)
49 }
50 a.Store = store
51
52 return nil
53}
54
55// Start starts the application.
56func (a *App) Start() error {
57 return nil
58}
59
60// Stop stops the application.
61func (a *App) Stop() error {
62 if a.Store != nil {
63 return a.Store.Close()
64 }
65 return nil
66}
67
68// Interface guards
69var (
70 _ caddy.App = (*App)(nil)
71 _ caddy.Provisioner = (*App)(nil)
72)
73
74// parseGlobalAtproto parses the global 'atproto' Caddyfile option.
75// Format:
76//
77// atproto {
78// storage_path /path/to/db
79// cookie_secret <secret>
80// }
81func parseGlobalAtproto(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
82 app := &App{}
83 for d.Next() {
84 for d.NextBlock(0) {
85 switch d.Val() {
86 case "storage_path":
87 if !d.NextArg() {
88 return nil, d.ArgErr()
89 }
90 app.StoragePath = d.Val()
91 case "cookie_secret":
92 if !d.NextArg() {
93 return nil, d.ArgErr()
94 }
95 app.CookieSecret = d.Val()
96 default:
97 return nil, d.Errf("unrecognized subdirective '%s'", d.Val())
98 }
99 }
100 }
101 return httpcaddyfile.App{
102 Name: "atproto",
103 Value: caddyconfig.JSON(app, nil),
104 }, nil
105}