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.0 kB View raw
1package caddyatprotoauth 2 3import ( 4 "net/http" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/caddyserver/caddy/v2" 10 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 11 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" 12 _ "github.com/caddyserver/caddy/v2/modules/standard" 13) 14 15func TestCaddyIntegration(t *testing.T) { 16 // Setup Caddyfile 17 // Try: Change the allow list on the gate to include a nonexistent handle. 18 // You should see a warning log that Caddy was unable to resolve it. 19 input := ` 20 { 21 admin off 22 atproto { 23 storage_path :memory: 24 } 25 } 26 27 :8080 { 28 route /auth/* { 29 uri strip_prefix /auth 30 atproto_portal { 31 name "Test Portal" 32 } 33 } 34 35 route /protected/* { 36 atproto_gate { 37 allow @tangled.org 38 portal_url http://localhost:8080/auth 39 } 40 respond "Authorized Content" 41 } 42 } 43 ` 44 45 // Parse and Load Config 46 adapter := caddyfile.Adapter{ 47 ServerType: httpcaddyfile.ServerType{}, 48 } 49 50 jsonConfig, warnings, err := adapter.Adapt([]byte(input), nil) 51 if err != nil { 52 t.Fatalf("Failed to adapt config: %v", err) 53 } 54 if len(warnings) > 0 { 55 t.Logf("Warnings: %v", warnings) 56 } 57 58 err = caddy.Load(jsonConfig, true) 59 if err != nil { 60 t.Fatalf("Failed to load caddy: %v", err) 61 } 62 defer caddy.Stop() 63 64 // Wait a moment for server start 65 time.Sleep(100 * time.Millisecond) 66 67 baseURL := "http://localhost:8080" 68 client := &http.Client{ 69 CheckRedirect: func(req *http.Request, via []*http.Request) error { 70 return http.ErrUseLastResponse // Don't follow redirects 71 }, 72 } 73 74 t.Run("Unauthorized Access Redirects to Portal", func(t *testing.T) { 75 resp, err := client.Get(baseURL + "/protected/resource") 76 if err != nil { 77 t.Fatalf("Request failed: %v", err) 78 } 79 defer resp.Body.Close() 80 81 if resp.StatusCode != http.StatusFound { 82 t.Errorf("Expected status 302, got %d", resp.StatusCode) 83 } 84 85 location := resp.Header.Get("Location") 86 if !strings.Contains(location, "/auth/login") { 87 t.Errorf("Expected redirect to portal login, got %s", location) 88 } 89 }) 90 91 t.Run("Portal Serves Login Page", func(t *testing.T) { 92 resp, err := client.Get(baseURL + "/auth/login") 93 if err != nil { 94 t.Fatalf("Request failed: %v", err) 95 } 96 defer resp.Body.Close() 97 98 if resp.StatusCode != http.StatusOK { 99 t.Errorf("Expected status 200, got %d", resp.StatusCode) 100 } 101 102 // Verify content type (HTML) 103 ct := resp.Header.Get("Content-Type") 104 if !strings.Contains(ct, "text/html") { 105 t.Errorf("Expected HTML content, got %s", ct) 106 } 107 }) 108 109 t.Run("Portal Serves Metadata", func(t *testing.T) { 110 resp, err := client.Get(baseURL + "/auth/.well-known/oauth-client-metadata.json") 111 if err != nil { 112 t.Fatalf("Request failed: %v", err) 113 } 114 defer resp.Body.Close() 115 116 if resp.StatusCode != http.StatusOK { 117 t.Errorf("Expected status 200, got %d", resp.StatusCode) 118 } 119 120 // Verify content type (JSON) 121 ct := resp.Header.Get("Content-Type") 122 if !strings.Contains(ct, "application/json") { 123 t.Errorf("Expected JSON content, got %s", ct) 124 } 125 }) 126}