A community based topic aggregation platform built on atproto
1package auth
2
3import (
4 "testing"
5 "time"
6
7 "github.com/golang-jwt/jwt/v5"
8)
9
10func TestParseJWT(t *testing.T) {
11 // Create a test JWT token
12 claims := &Claims{
13 RegisteredClaims: jwt.RegisteredClaims{
14 Subject: "did:plc:test123",
15 Issuer: "https://test-pds.example.com",
16 ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
17 IssuedAt: jwt.NewNumericDate(time.Now()),
18 },
19 Scope: "atproto transition:generic",
20 }
21
22 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
23 tokenString, err := token.SignedString([]byte("test-secret"))
24 if err != nil {
25 t.Fatalf("Failed to create test token: %v", err)
26 }
27
28 // Test parsing
29 parsedClaims, err := ParseJWT(tokenString)
30 if err != nil {
31 t.Fatalf("ParseJWT failed: %v", err)
32 }
33
34 if parsedClaims.Subject != "did:plc:test123" {
35 t.Errorf("Expected subject 'did:plc:test123', got '%s'", parsedClaims.Subject)
36 }
37
38 if parsedClaims.Issuer != "https://test-pds.example.com" {
39 t.Errorf("Expected issuer 'https://test-pds.example.com', got '%s'", parsedClaims.Issuer)
40 }
41
42 if parsedClaims.Scope != "atproto transition:generic" {
43 t.Errorf("Expected scope 'atproto transition:generic', got '%s'", parsedClaims.Scope)
44 }
45}
46
47func TestParseJWT_MissingSubject(t *testing.T) {
48 // Create a token without subject
49 claims := &Claims{
50 RegisteredClaims: jwt.RegisteredClaims{
51 Issuer: "https://test-pds.example.com",
52 ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
53 },
54 }
55
56 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
57 tokenString, err := token.SignedString([]byte("test-secret"))
58 if err != nil {
59 t.Fatalf("Failed to create test token: %v", err)
60 }
61
62 // Test parsing - should fail
63 _, err = ParseJWT(tokenString)
64 if err == nil {
65 t.Error("Expected error for missing subject, got nil")
66 }
67}
68
69func TestParseJWT_MissingIssuer(t *testing.T) {
70 // Create a token without issuer
71 claims := &Claims{
72 RegisteredClaims: jwt.RegisteredClaims{
73 Subject: "did:plc:test123",
74 ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
75 },
76 }
77
78 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
79 tokenString, err := token.SignedString([]byte("test-secret"))
80 if err != nil {
81 t.Fatalf("Failed to create test token: %v", err)
82 }
83
84 // Test parsing - should fail
85 _, err = ParseJWT(tokenString)
86 if err == nil {
87 t.Error("Expected error for missing issuer, got nil")
88 }
89}
90
91func TestParseJWT_WithBearerPrefix(t *testing.T) {
92 // Create a test JWT token
93 claims := &Claims{
94 RegisteredClaims: jwt.RegisteredClaims{
95 Subject: "did:plc:test123",
96 Issuer: "https://test-pds.example.com",
97 ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
98 },
99 }
100
101 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
102 tokenString, err := token.SignedString([]byte("test-secret"))
103 if err != nil {
104 t.Fatalf("Failed to create test token: %v", err)
105 }
106
107 // Test parsing with Bearer prefix
108 parsedClaims, err := ParseJWT("Bearer " + tokenString)
109 if err != nil {
110 t.Fatalf("ParseJWT failed with Bearer prefix: %v", err)
111 }
112
113 if parsedClaims.Subject != "did:plc:test123" {
114 t.Errorf("Expected subject 'did:plc:test123', got '%s'", parsedClaims.Subject)
115 }
116}
117
118func TestValidateClaims_Expired(t *testing.T) {
119 claims := &Claims{
120 RegisteredClaims: jwt.RegisteredClaims{
121 Subject: "did:plc:test123",
122 Issuer: "https://test-pds.example.com",
123 ExpiresAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)), // Expired
124 },
125 }
126
127 err := validateClaims(claims)
128 if err == nil {
129 t.Error("Expected error for expired token, got nil")
130 }
131}
132
133func TestValidateClaims_InvalidDID(t *testing.T) {
134 claims := &Claims{
135 RegisteredClaims: jwt.RegisteredClaims{
136 Subject: "invalid-did-format",
137 Issuer: "https://test-pds.example.com",
138 ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
139 },
140 }
141
142 err := validateClaims(claims)
143 if err == nil {
144 t.Error("Expected error for invalid DID format, got nil")
145 }
146}
147
148func TestExtractKeyID(t *testing.T) {
149 // Create a test JWT token with kid in header
150 token := jwt.New(jwt.SigningMethodRS256)
151 token.Header["kid"] = "test-key-id"
152 token.Claims = &Claims{
153 RegisteredClaims: jwt.RegisteredClaims{
154 Subject: "did:plc:test123",
155 Issuer: "https://test-pds.example.com",
156 },
157 }
158
159 // Sign with a dummy RSA key (we just need a valid token structure)
160 tokenString, err := token.SignedString([]byte("dummy"))
161 if err == nil {
162 // If it succeeds (shouldn't with wrong key type, but let's handle it)
163 kid, err := ExtractKeyID(tokenString)
164 if err != nil {
165 t.Logf("ExtractKeyID failed (expected if signing fails): %v", err)
166 } else if kid != "test-key-id" {
167 t.Errorf("Expected kid 'test-key-id', got '%s'", kid)
168 }
169 }
170}