A community based topic aggregation platform built on atproto
1package oauth 2 3import ( 4 "encoding/base64" 5 "encoding/json" 6 "strings" 7 "testing" 8) 9 10// TestCreateDPoPProof tests DPoP proof generation and structure 11func TestCreateDPoPProof(t *testing.T) { 12 // Generate a test DPoP key 13 dpopKey, err := GenerateDPoPKey() 14 if err != nil { 15 t.Fatalf("Failed to generate DPoP key: %v", err) 16 } 17 18 // Create a DPoP proof 19 proof, err := CreateDPoPProof(dpopKey, "POST", "https://example.com/token", "", "") 20 if err != nil { 21 t.Fatalf("Failed to create DPoP proof: %v", err) 22 } 23 24 // DPoP proof should be a JWT in form: header.payload.signature 25 parts := strings.Split(proof, ".") 26 if len(parts) != 3 { 27 t.Fatalf("Expected 3 parts in JWT, got %d", len(parts)) 28 } 29 30 // Decode and inspect the header 31 headerJSON, err := base64.RawURLEncoding.DecodeString(parts[0]) 32 if err != nil { 33 t.Fatalf("Failed to decode header: %v", err) 34 } 35 36 var header map[string]interface{} 37 if err := json.Unmarshal(headerJSON, &header); err != nil { 38 t.Fatalf("Failed to unmarshal header: %v", err) 39 } 40 41 t.Logf("DPoP Header: %s", string(headerJSON)) 42 43 // Verify required header fields 44 if header["alg"] != "ES256" { 45 t.Errorf("Expected alg=ES256, got %v", header["alg"]) 46 } 47 if header["typ"] != "dpop+jwt" { 48 t.Errorf("Expected typ=dpop+jwt, got %v", header["typ"]) 49 } 50 51 // Verify JWK is present and is a JSON object 52 jwkValue, hasJWK := header["jwk"] 53 if !hasJWK { 54 t.Fatal("Header missing 'jwk' field") 55 } 56 57 // JWK should be a map/object, not a string 58 jwkMap, ok := jwkValue.(map[string]interface{}) 59 if !ok { 60 t.Fatalf("JWK is not a JSON object, got type: %T, value: %v", jwkValue, jwkValue) 61 } 62 63 // Verify JWK has required fields for EC key 64 if jwkMap["kty"] != "EC" { 65 t.Errorf("Expected kty=EC, got %v", jwkMap["kty"]) 66 } 67 if jwkMap["crv"] != "P-256" { 68 t.Errorf("Expected crv=P-256, got %v", jwkMap["crv"]) 69 } 70 if _, hasX := jwkMap["x"]; !hasX { 71 t.Error("JWK missing 'x' coordinate") 72 } 73 if _, hasY := jwkMap["y"]; !hasY { 74 t.Error("JWK missing 'y' coordinate") 75 } 76 77 // Verify private key is NOT in the public JWK 78 if _, hasD := jwkMap["d"]; hasD { 79 t.Error("SECURITY: JWK contains private key component 'd'!") 80 } 81 82 // Decode and inspect the payload 83 payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1]) 84 if err != nil { 85 t.Fatalf("Failed to decode payload: %v", err) 86 } 87 88 var payload map[string]interface{} 89 if err := json.Unmarshal(payloadJSON, &payload); err != nil { 90 t.Fatalf("Failed to unmarshal payload: %v", err) 91 } 92 93 t.Logf("DPoP Payload: %s", string(payloadJSON)) 94 95 // Verify required payload claims 96 if payload["htm"] != "POST" { 97 t.Errorf("Expected htm=POST, got %v", payload["htm"]) 98 } 99 if payload["htu"] != "https://example.com/token" { 100 t.Errorf("Expected htu=https://example.com/token, got %v", payload["htu"]) 101 } 102 if _, hasIAT := payload["iat"]; !hasIAT { 103 t.Error("Payload missing 'iat' (issued at)") 104 } 105 if _, hasJTI := payload["jti"]; !hasJTI { 106 t.Error("Payload missing 'jti' (JWT ID)") 107 } 108} 109 110// TestDPoPProofWithNonce tests DPoP proof with nonce 111func TestDPoPProofWithNonce(t *testing.T) { 112 dpopKey, err := GenerateDPoPKey() 113 if err != nil { 114 t.Fatalf("Failed to generate DPoP key: %v", err) 115 } 116 117 testNonce := "test-nonce-12345" 118 proof, err := CreateDPoPProof(dpopKey, "POST", "https://example.com/token", testNonce, "") 119 if err != nil { 120 t.Fatalf("Failed to create DPoP proof: %v", err) 121 } 122 123 // Decode payload 124 parts := strings.Split(proof, ".") 125 payloadJSON, _ := base64.RawURLEncoding.DecodeString(parts[1]) 126 var payload map[string]interface{} 127 json.Unmarshal(payloadJSON, &payload) 128 129 if payload["nonce"] != testNonce { 130 t.Errorf("Expected nonce=%s, got %v", testNonce, payload["nonce"]) 131 } 132} 133 134// TestDPoPProofWithAccessToken tests DPoP proof with access token hash 135func TestDPoPProofWithAccessToken(t *testing.T) { 136 dpopKey, err := GenerateDPoPKey() 137 if err != nil { 138 t.Fatalf("Failed to generate DPoP key: %v", err) 139 } 140 141 testToken := "test-access-token" 142 proof, err := CreateDPoPProof(dpopKey, "GET", "https://example.com/resource", "", testToken) 143 if err != nil { 144 t.Fatalf("Failed to create DPoP proof: %v", err) 145 } 146 147 // Decode payload 148 parts := strings.Split(proof, ".") 149 payloadJSON, _ := base64.RawURLEncoding.DecodeString(parts[1]) 150 var payload map[string]interface{} 151 json.Unmarshal(payloadJSON, &payload) 152 153 ath, hasATH := payload["ath"] 154 if !hasATH { 155 t.Fatal("Payload missing 'ath' (access token hash)") 156 } 157 if ath == "" { 158 t.Error("Access token hash is empty") 159 } 160 161 t.Logf("Access token hash: %v", ath) 162}