* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 deletions
+353
View File
@@ -0,0 +1,353 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
// TestSIOPAuthorization tests Self-Issued OpenID Provider authorization
func TestSIOPAuthorization(t *testing.T) {
e := echo.New()
t.Run("ValidSIOPRequest", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/siop/authorize", nil)
q := req.URL.Query()
q.Set("response_type", "id_token")
q.Set("client_id", "test-client")
q.Set("redirect_uri", "http://localhost:3000/callback")
q.Set("scope", "openid did")
q.Set("nonce", "test-nonce")
q.Set("state", "test-state")
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
// Set authenticated user DID
c.Set("user_did", "did:sonr:testuser")
err := HandleSIOPAuthorization(c)
assert.NoError(t, err)
// Should redirect with ID token
assert.Equal(t, http.StatusFound, rec.Code)
location := rec.Header().Get("Location")
assert.Contains(t, location, "id_token=")
assert.Contains(t, location, "state=test-state")
})
t.Run("InvalidResponseType", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/siop/authorize", nil)
q := req.URL.Query()
q.Set("response_type", "code") // SIOP only supports id_token
q.Set("client_id", "test-client")
q.Set("redirect_uri", "http://localhost:3000/callback")
q.Set("scope", "openid")
q.Set("nonce", "test-nonce")
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPAuthorization(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, rec.Code)
var errorResp map[string]string
err = json.Unmarshal(rec.Body.Bytes(), &errorResp)
assert.NoError(t, err)
assert.Equal(t, "unsupported_response_type", errorResp["error"])
})
t.Run("MissingScopeOpenID", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/siop/authorize", nil)
q := req.URL.Query()
q.Set("response_type", "id_token")
q.Set("client_id", "test-client")
q.Set("redirect_uri", "http://localhost:3000/callback")
q.Set("scope", "profile email") // Missing 'openid'
q.Set("nonce", "test-nonce")
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPAuthorization(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, rec.Code)
var errorResp map[string]string
err = json.Unmarshal(rec.Body.Bytes(), &errorResp)
assert.NoError(t, err)
assert.Equal(t, "invalid_scope", errorResp["error"])
})
t.Run("UnauthenticatedUser", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/siop/authorize", nil)
q := req.URL.Query()
q.Set("response_type", "id_token")
q.Set("client_id", "test-client")
q.Set("redirect_uri", "http://localhost:3000/callback")
q.Set("scope", "openid")
q.Set("nonce", "test-nonce")
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
// No user_did set - user not authenticated
err := HandleSIOPAuthorization(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, rec.Code)
var errorResp map[string]string
err = json.Unmarshal(rec.Body.Bytes(), &errorResp)
assert.NoError(t, err)
assert.Equal(t, "authentication_required", errorResp["error"])
})
}
// TestSIOPRegistration tests dynamic client registration for SIOP
func TestSIOPRegistration(t *testing.T) {
e := echo.New()
t.Run("ValidRegistration", func(t *testing.T) {
registration := map[string]any{
"client_name": "Test SIOP Client",
"client_purpose": "Testing SIOP functionality",
"logo_uri": "https://example.com/logo.png",
}
body, _ := json.Marshal(registration)
req := httptest.NewRequest(http.MethodPost, "/siop/register", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPRegistration(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var response map[string]any
err = json.Unmarshal(rec.Body.Bytes(), &response)
assert.NoError(t, err)
assert.NotEmpty(t, response["client_id"])
assert.Equal(t, "Test SIOP Client", response["client_name"])
assert.Contains(t, response["subject_syntax_types_supported"], "did")
})
t.Run("MissingClientName", func(t *testing.T) {
registration := map[string]any{
"client_purpose": "Testing",
}
body, _ := json.Marshal(registration)
req := httptest.NewRequest(http.MethodPost, "/siop/register", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPRegistration(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, rec.Code)
var errorResp map[string]string
err = json.Unmarshal(rec.Body.Bytes(), &errorResp)
assert.NoError(t, err)
assert.Equal(t, "invalid_request", errorResp["error"])
assert.Contains(t, errorResp["error_description"], "Client name is required")
})
}
// TestSIOPMetadata tests SIOP metadata endpoint
func TestSIOPMetadata(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/siop/metadata", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPMetadata(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var metadata map[string]any
err = json.Unmarshal(rec.Body.Bytes(), &metadata)
assert.NoError(t, err)
// Verify required SIOP metadata fields
assert.Equal(t, "https://self-issued.me/v2", metadata["issuer"])
assert.Equal(t, "openid://", metadata["authorization_endpoint"])
assert.Contains(t, metadata["response_types_supported"], "id_token")
assert.Contains(t, metadata["scopes_supported"], "openid")
assert.Contains(t, metadata["subject_types_supported"], "pairwise")
assert.NotNil(t, metadata["vp_formats_supported"])
}
// TestValidateSIOPRequest tests SIOP request validation
func TestValidateSIOPRequest(t *testing.T) {
t.Run("ValidRequest", func(t *testing.T) {
requestURI := "openid://?" +
"response_type=id_token" +
"&client_id=test-client" +
"&redirect_uri=" + url.QueryEscape("http://localhost:3000/callback") +
"&scope=openid+did" +
"&nonce=test-nonce" +
"&state=test-state"
req, err := ValidateSIOPRequest(requestURI)
assert.NoError(t, err)
assert.NotNil(t, req)
assert.Equal(t, "id_token", req.ResponseType)
assert.Equal(t, "test-client", req.ClientID)
assert.Equal(t, "openid did", req.Scope)
assert.Equal(t, "test-nonce", req.Nonce)
})
t.Run("InvalidURI", func(t *testing.T) {
req, err := ValidateSIOPRequest("not-a-valid-uri")
assert.Error(t, err)
assert.Nil(t, req)
})
t.Run("WithClaims", func(t *testing.T) {
claims := map[string]any{
"id_token": map[string]any{
"email": map[string]any{
"essential": true,
},
"name": nil,
},
}
claimsJSON, _ := json.Marshal(claims)
requestURI := "openid://?" +
"response_type=id_token" +
"&client_id=test-client" +
"&redirect_uri=" + url.QueryEscape("http://localhost:3000/callback") +
"&scope=openid" +
"&nonce=test-nonce" +
"&claims=" + url.QueryEscape(string(claimsJSON))
req, err := ValidateSIOPRequest(requestURI)
assert.NoError(t, err)
assert.NotNil(t, req)
assert.NotNil(t, req.Claims.IDToken)
})
}
// TestSIOPWithVPToken tests SIOP with Verifiable Presentation
func TestSIOPWithVPToken(t *testing.T) {
e := echo.New()
vpClaims := map[string]any{
"vp_token": map[string]any{
"presentation_definition": map[string]any{
"id": "test-presentation",
"input_descriptors": []map[string]any{
{
"id": "id_credential",
"constraints": map[string]any{
"fields": []map[string]any{
{
"path": []string{"$.type"},
"filter": map[string]any{
"type": "string",
"const": "IdentityCredential",
},
},
},
},
},
},
},
},
}
claimsJSON, _ := json.Marshal(vpClaims)
req := httptest.NewRequest(http.MethodPost, "/siop/authorize", nil)
q := req.URL.Query()
q.Set("response_type", "id_token")
q.Set("client_id", "test-client")
q.Set("redirect_uri", "http://localhost:3000/callback")
q.Set("scope", "openid")
q.Set("nonce", "test-nonce")
q.Set("claims", string(claimsJSON))
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.Set("user_did", "did:sonr:testuser")
// Parse request to set claims
siopReq := &SIOPRequest{
ResponseType: "id_token",
ClientID: "test-client",
RedirectURI: "http://localhost:3000/callback",
Scope: "openid",
Nonce: "test-nonce",
}
err := json.Unmarshal(claimsJSON, &siopReq.Claims)
assert.NoError(t, err)
c.Set("siop_request", siopReq)
err = HandleSIOPAuthorization(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusFound, rec.Code)
location := rec.Header().Get("Location")
assert.Contains(t, location, "id_token=")
// VP token generation is optional, so we don't assert its presence
}
// TestHandleSIOPRequest tests complete SIOP request flow
func TestHandleSIOPRequest(t *testing.T) {
e := echo.New()
t.Run("ValidRequestURI", func(t *testing.T) {
requestURI := "openid://?" +
"response_type=id_token" +
"&client_id=test-client" +
"&redirect_uri=" + url.QueryEscape("http://localhost:3000/callback") +
"&scope=openid" +
"&nonce=test-nonce"
req := httptest.NewRequest(http.MethodGet, "/siop/request", nil)
q := req.URL.Query()
q.Set("request_uri", requestURI)
req.URL.RawQuery = q.Encode()
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.Set("user_did", "did:sonr:testuser")
err := HandleSIOPRequest(c)
assert.NoError(t, err)
// Should process as authorization request
assert.Equal(t, http.StatusFound, rec.Code)
})
t.Run("MissingRequestURI", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/siop/request", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := HandleSIOPRequest(c)
assert.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, rec.Code)
var errorResp map[string]string
err = json.Unmarshal(rec.Body.Bytes(), &errorResp)
assert.NoError(t, err)
assert.Equal(t, "invalid_request", errorResp["error"])
assert.Contains(t, errorResp["error_description"], "Missing request_uri")
})
}