11package server
22
33import (
4+ "bytes"
45 "context"
56 "crypto/subtle"
67 "encoding/json"
8+ "io"
79 "log/slog"
810 "net/http"
11+ "strconv"
912 "time"
1013
1114 "github.com/peerclaw/peerclaw-core/agentcard"
15+ pcerrors "github.com/peerclaw/peerclaw-core/errors"
16+ coreidentity "github.com/peerclaw/peerclaw-core/identity"
1217 "github.com/peerclaw/peerclaw-core/protocol"
1318 coresignaling "github.com/peerclaw/peerclaw-core/signaling"
1419 "github.com/peerclaw/peerclaw-server/internal/audit"
@@ -453,8 +458,15 @@ type peerclawReq struct {
453458}
454459
455460func (s * HTTPServer ) handleRegister (w http.ResponseWriter , r * http.Request ) {
461+ // Buffer body for PoP signature verification.
462+ bodyBytes , err := io .ReadAll (r .Body )
463+ if err != nil {
464+ s .jsonError (w , "failed to read request body" , http .StatusBadRequest )
465+ return
466+ }
467+
456468 var req registerRequest
457- if err := json .NewDecoder (r . Body ).Decode (& req ); err != nil {
469+ if err := json .NewDecoder (bytes . NewReader ( bodyBytes ) ).Decode (& req ); err != nil {
458470 s .jsonError (w , "invalid request body" , http .StatusBadRequest )
459471 return
460472 }
@@ -464,6 +476,30 @@ func (s *HTTPServer) handleRegister(w http.ResponseWriter, r *http.Request) {
464476 return
465477 }
466478
479+ // Proof-of-Possession: if public_key is provided, require matching signature.
480+ if req .PublicKey != "" {
481+ sigHeader := r .Header .Get ("X-PeerClaw-Signature" )
482+ pubKeyHeader := r .Header .Get ("X-PeerClaw-PublicKey" )
483+ if sigHeader == "" || pubKeyHeader == "" {
484+ s .jsonError (w , "proof-of-possession required: sign the request body with your Ed25519 key" , http .StatusBadRequest )
485+ return
486+ }
487+ if pubKeyHeader != req .PublicKey {
488+ s .jsonError (w , "X-PeerClaw-PublicKey must match public_key in request body" , http .StatusBadRequest )
489+ return
490+ }
491+ // Verify signature over the raw request body.
492+ pubKey , parseErr := coreidentity .ParsePublicKey (pubKeyHeader )
493+ if parseErr != nil {
494+ s .jsonError (w , "invalid public key: " + parseErr .Error (), http .StatusBadRequest )
495+ return
496+ }
497+ if verifyErr := coreidentity .Verify (pubKey , bodyBytes , sigHeader ); verifyErr != nil {
498+ s .jsonError (w , "proof-of-possession signature verification failed" , http .StatusBadRequest )
499+ return
500+ }
501+ }
502+
467503 protocols := make ([]protocol.Protocol , len (req .Protocols ))
468504 for i , p := range req .Protocols {
469505 protocols [i ] = protocol .Protocol (p )
@@ -522,11 +558,24 @@ func (s *HTTPServer) handleRegister(w http.ResponseWriter, r *http.Request) {
522558}
523559
524560func (s * HTTPServer ) handleListAgents (w http.ResponseWriter , r * http.Request ) {
561+ q := r .URL .Query ()
525562 filter := registry.ListFilter {
526- Protocol : r .URL .Query ().Get ("protocol" ),
527- Capability : r .URL .Query ().Get ("capability" ),
528- Status : agentcard .AgentStatus (r .URL .Query ().Get ("status" )),
529- PageToken : r .URL .Query ().Get ("page_token" ),
563+ Protocol : q .Get ("protocol" ),
564+ Capability : q .Get ("capability" ),
565+ Status : agentcard .AgentStatus (q .Get ("status" )),
566+ PageToken : q .Get ("page_token" ),
567+ SortBy : q .Get ("sort" ),
568+ Search : q .Get ("search" ),
569+ }
570+ if ms := q .Get ("min_score" ); ms != "" {
571+ if score , err := strconv .ParseFloat (ms , 64 ); err == nil {
572+ filter .MinScore = score
573+ }
574+ }
575+ if ps := q .Get ("page_size" ); ps != "" {
576+ if size , err := strconv .Atoi (ps ); err == nil {
577+ filter .PageSize = size
578+ }
530579 }
531580 result , err := s .registry .ListAgents (r .Context (), filter )
532581 if err != nil {
@@ -931,5 +980,26 @@ func (s *HTTPServer) jsonResponse(w http.ResponseWriter, status int, data any) {
931980}
932981
933982func (s * HTTPServer ) jsonError (w http.ResponseWriter , message string , status int ) {
934- s .jsonResponse (w , status , map [string ]string {"error" : message })
983+ code := statusToErrorCode (status )
984+ s .jsonResponse (w , status , pcerrors.Error {Code : code , Message : message })
985+ }
986+
987+ // statusToErrorCode maps HTTP status codes to structured error codes.
988+ func statusToErrorCode (status int ) pcerrors.Code {
989+ switch status {
990+ case http .StatusNotFound :
991+ return pcerrors .CodeNotFound
992+ case http .StatusBadRequest :
993+ return pcerrors .CodeValidation
994+ case http .StatusUnauthorized , http .StatusForbidden :
995+ return pcerrors .CodeAuth
996+ case http .StatusConflict :
997+ return pcerrors .CodeConflict
998+ case http .StatusRequestTimeout , http .StatusGatewayTimeout :
999+ return pcerrors .CodeTimeout
1000+ case http .StatusTooManyRequests :
1001+ return pcerrors .CodeRateLimited
1002+ default :
1003+ return pcerrors .CodeInternal
1004+ }
9351005}
0 commit comments