niko_trust/pkg/transport/view.go
Niko Marmeladkov 3bf13fa488 Public SDK packages, proxy-aware rate limits, service login recipe
- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
  external Go projects can import the verified core; invariant tests
  updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
  sits behind a reverse proxy (off by default, header never trusted
  otherwise)
- examples/service + examples/approve: complete passwordless login round
  trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
2026-08-26 12:49:54 +03:00

232 lines
6 KiB
Go

package transport
import (
"encoding/hex"
"encoding/json"
"git.n1ko.dev/Niko/niko_trust/pkg/identity"
"git.n1ko.dev/Niko/niko_trust/pkg/protocol"
"git.n1ko.dev/Niko/niko_trust/pkg/tce"
)
func hexStr(b []byte) string { return hex.EncodeToString(b) }
// AddrOf renders a public key as its trust address text.
func AddrOf(pub []byte) string {
id, err := identity.FromPubKey(pub)
if err != nil {
return ""
}
return id.Address().String()
}
func addrOf(pub []byte) string { return AddrOf(pub) }
// valueView renders a TCE value for the JSON `object` convenience view.
func valueView(v tce.Value) any {
switch v.Tag() {
case tce.ValNull:
return nil
case tce.ValTrue:
return true
case tce.ValFalse:
return false
case tce.ValString:
s, _ := v.Str()
return s
case tce.ValNumber:
tok, _ := v.NumberToken()
return json.Number(tok)
}
return nil
}
func objectTypeOf(obj any) string {
switch obj.(type) {
case *protocol.Identity:
return "identity"
case *protocol.Claim:
return "claim"
case *protocol.Revocation:
return "revocation"
case *protocol.ApprovalRequest:
return "request"
case *protocol.ApprovalResponse:
return "response"
case *protocol.AuthAssertion:
return "auth"
case *protocol.DelegationClaim:
return "delegation"
case *protocol.KeyRotationRequest:
return "key_rotation_request"
case *protocol.KeyRotationConfirm:
return "key_rotation_confirm"
}
return ""
}
// ObjectTypeName returns the type name of a decoded protocol object.
func ObjectTypeName(obj any) string { return objectTypeOf(obj) }
// DecodeObject strict-decodes TCE bytes into the typed protocol object and
// returns its type name.
func DecodeObject(b []byte) (string, any, error) {
d := tce.NewDecoder(b)
tag, err := d.Header()
if err != nil {
return "", nil, err
}
switch tag {
case tce.TagIdentity:
o, e := protocol.DecodeIdentity(b)
return "identity", o, e
case tce.TagClaim:
o, e := protocol.DecodeClaim(b)
return "claim", o, e
case tce.TagRevocation:
o, e := protocol.DecodeRevocation(b)
return "revocation", o, e
case tce.TagApprovalRequest:
o, e := protocol.DecodeApprovalRequest(b)
return "request", o, e
case tce.TagApprovalResponse:
o, e := protocol.DecodeApprovalResponse(b)
return "response", o, e
case tce.TagAuthAssertion:
o, e := protocol.DecodeAuthAssertion(b)
return "auth", o, e
case tce.TagDelegation:
o, e := protocol.DecodeDelegationClaim(b)
return "delegation", o, e
case tce.TagKeyRotation:
o, e := protocol.DecodeKeyRotationRequest(b)
return "key_rotation_request", o, e
case tce.TagKeyRotationConf:
o, e := protocol.DecodeKeyRotationConfirm(b)
return "key_rotation_confirm", o, e
default:
return "", nil, tce.ErrObjectTag
}
}
// BuildView decodes the TCE bytes and produces the `object` convenience view of
// PROTOCOL.md section 10. It fails if the bytes do not strict-decode.
func BuildView(b []byte) (objectType string, view json.RawMessage, err error) {
typ, obj, decErr := DecodeObject(b)
if decErr != nil {
return "", nil, decErr
}
var v any
switch o := obj.(type) {
case *protocol.Identity:
v = map[string]any{
"type": "identity",
"version": 1,
"identity": addrOf(o.PubKey),
"alias": o.Alias,
"created_at": o.CreatedAt,
}
case *protocol.Claim:
claims := make(map[string]any, len(o.Claims))
for k, val := range o.Claims {
claims[k] = valueView(val)
}
v = map[string]any{
"type": "claim",
"version": 1,
"issuer": addrOf(o.Issuer),
"subject": addrOf(o.Subject),
"claims": claims,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"serial": o.Serial,
"nonce": hexStr(o.Nonce),
}
case *protocol.Revocation:
v = map[string]any{
"type": "revocation",
"version": 1,
"issuer": addrOf(o.Issuer),
"claim_id": o.ClaimID.String(),
"reason": o.Reason,
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalRequest:
payload := make(map[string]any, len(o.Payload))
for k, val := range o.Payload {
payload[k] = valueView(val)
}
v = map[string]any{
"type": "request",
"version": 1,
"sender": addrOf(o.Sender),
"recipient": addrOf(o.Recipient),
"action": o.Action,
"payload": payload,
"message": o.Message,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalResponse:
v = map[string]any{
"type": "response",
"version": 1,
"request_hash": o.RequestHash.String(),
"responder": addrOf(o.Responder),
"decision": o.Decision.String(),
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.AuthAssertion:
v = map[string]any{
"type": "auth",
"version": 1,
"identity": addrOf(o.PubKey),
"challenge": hexStr(o.Challenge),
"scope": o.Scope,
"audience": o.Audience,
"created_at": o.CreatedAt,
}
case *protocol.DelegationClaim:
predicates := make(map[string]any, len(o.Predicates))
for k, val := range o.Predicates {
predicates[k] = valueView(val)
}
v = map[string]any{
"type": "delegation",
"version": 1,
"granter": addrOf(o.Granter),
"grantee": addrOf(o.Grantee),
"predicates": predicates,
"max_depth": o.MaxDepth,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"serial": o.Serial,
"nonce": hexStr(o.Nonce),
}
case *protocol.KeyRotationRequest:
v = map[string]any{
"type": "key_rotation_request",
"version": 1,
"successor": addrOf(o.Successor),
"predecessor": addrOf(o.Predecessor),
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
}
case *protocol.KeyRotationConfirm:
v = map[string]any{
"type": "key_rotation_confirm",
"version": 1,
"rotation_hash": o.RotationHash.String(),
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
}
raw, err := json.Marshal(v)
if err != nil {
return "", nil, err
}
return typ, raw, nil
}