Caddy module to require at-proto authentication and restrict routes to DIDs
1package ui
2
3import (
4 "embed"
5 "fmt"
6 "html/template"
7 "io"
8 "os"
9)
10
11//go:embed templates/login.html templates/forbidden.html
12var embeddedTemplates embed.FS
13
14// Config holds user overrides for UI templates.
15type Config struct {
16 LoginTemplatePath string `json:"login_template_path,omitempty"`
17 ForbiddenTemplatePath string `json:"forbidden_template_path,omitempty"`
18}
19
20// Renderer handles rendering of HTML pages.
21type Renderer struct {
22 login *template.Template
23 forbidden *template.Template
24}
25
26// NewRenderer initializes a new template renderer, loading defaults and user overrides.
27func NewRenderer(config Config) (*Renderer, error) {
28 r := &Renderer{}
29
30 // Helper to load or fallback
31 load := func(userPath, defaultName string) (*template.Template, error) {
32 var tmplStr string
33 var b []byte
34 var err error
35
36 if userPath != "" {
37 b, err = os.ReadFile(userPath)
38 if err != nil {
39 return nil, fmt.Errorf("failed to read custom template %s: %w", userPath, err)
40 }
41 tmplStr = string(b)
42 } else {
43 b, err = embeddedTemplates.ReadFile("templates/" + defaultName)
44 if err != nil {
45 // Should not happen if embedded correctly
46 return nil, fmt.Errorf("failed to read embedded template %s: %w", defaultName, err)
47 }
48 tmplStr = string(b)
49 }
50 return template.New(defaultName).Parse(tmplStr)
51 }
52
53 var err error
54 if r.login, err = load(config.LoginTemplatePath, "login.html"); err != nil {
55 return nil, err
56 }
57 if r.forbidden, err = load(config.ForbiddenTemplatePath, "forbidden.html"); err != nil {
58 return nil, err
59 }
60
61 return r, nil
62}
63
64// LoginData is the context for login.html
65type LoginData struct {
66 AppName string
67 Error string
68 Redirect string
69 LoginURL string
70}
71
72// ForbiddenData is the context for forbidden.html
73type ForbiddenData struct {
74 AppName string
75 DID string
76 Handle string
77 LogoutURL string
78}
79
80func (r *Renderer) RenderLogin(w io.Writer, data LoginData) error {
81 return r.login.Execute(w, data)
82}
83
84func (r *Renderer) RenderForbidden(w io.Writer, data ForbiddenData) error {
85 return r.forbidden.Execute(w, data)
86}