Caddy module to require at-proto authentication and restrict routes to DIDs
1package caddyatprotoauth
2
3import (
4 "fmt"
5 "net/http"
6
7 "github.com/caddyserver/caddy/v2"
8 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
9 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
10 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
11)
12
13func init() {
14 caddy.RegisterModule(Gate{})
15 httpcaddyfile.RegisterHandlerDirective("atproto_gate", parseCaddyfileGate)
16}
17
18// Gate acts as a middleware that guards endpoints
19// and validates the session cookie.
20type Gate struct {
21 Allow []string `json:"allow,omitempty"`
22}
23
24// CaddyModule returns the Caddy module information.
25func (Gate) CaddyModule() caddy.ModuleInfo {
26 return caddy.ModuleInfo{
27 ID: "http.handlers.atproto_gate",
28 New: func() caddy.Module { return new(Gate) },
29 }
30}
31
32// Provision sets up the module.
33func (g *Gate) Provision(ctx caddy.Context) error {
34 // Initialize identities and cache resolved handles here.
35 return nil
36}
37
38// Validate checks that the configuration is valid.
39func (g *Gate) Validate() error {
40 if len(g.Allow) == 0 {
41 return fmt.Errorf("atproto_gate requires at least one 'allow' entry")
42 }
43 return nil
44}
45
46// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
47func (g *Gate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
48 for d.Next() {
49 for nesting := d.Nesting(); d.NextBlock(nesting); {
50 switch d.Val() {
51 case "allow":
52 g.Allow = append(g.Allow, d.RemainingArgs()...)
53 default:
54 return d.Errf("unrecognized subdirective '%s'", d.Val())
55 }
56 }
57 }
58 return nil
59}
60
61// parseCaddyfileGate parses the atproto_gate directive from a Caddyfile.
62func parseCaddyfileGate(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
63 var g Gate
64 err := g.UnmarshalCaddyfile(h.Dispenser)
65 return &g, err
66}
67
68// ServeHTTP implements caddyhttp.MiddlewareHandler.
69func (g *Gate) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
70 // 1. Verify stateless cookie here
71 // 2. If invalid/missing, initiate redirect to PDS or Auth Hub
72 // 3. If valid, set headers (X-Atproto-Did) and proceed
73
74 // Example: inject header for downstream (to be implemented correctly)
75 // r.Header.Set("X-Atproto-Did", "did:plc:xxx")
76
77 return next.ServeHTTP(w, r)
78}