-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcredential.go
More file actions
149 lines (135 loc) · 4.94 KB
/
credential.go
File metadata and controls
149 lines (135 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package mppx
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
)
// Credential is a payment credential sent by a client in an Authorization header.
type Credential struct {
// Challenge is the challenge this credential responds to.
Challenge Challenge `json:"challenge"`
// Payload contains method-specific payment proof (e.g. transaction hash, signature).
// The concrete type depends on the payment method.
Payload any `json:"payload"`
// Source is an optional payer identifier as a DID (e.g. "did:pkh:eip155:1:0x...").
Source string `json:"source,omitempty"`
}
// credentialWire is the on-wire JSON representation.
// Challenge.Request is serialized as a base64url string rather than an object.
type credentialWire struct {
Challenge challengeWire `json:"challenge"`
Payload any `json:"payload"`
Source string `json:"source,omitempty"`
}
type challengeWire struct {
ID string `json:"id"`
Realm string `json:"realm"`
Method string `json:"method"`
Intent string `json:"intent"`
Request string `json:"request"` // base64url-encoded JSON
Digest string `json:"digest,omitempty"`
Expires string `json:"expires,omitempty"`
Opaque map[string]string `json:"opaque,omitempty"`
Description string `json:"description,omitempty"`
}
// SerializeCredential serializes a Credential to the Authorization header value format.
//
// Format: Payment <base64url(JSON)>
func SerializeCredential(cred Credential) string { //nolint:gocritic // Public API keeps value params for backward compatibility.
wire := credentialWire{
Challenge: challengeWire{
ID: cred.Challenge.ID,
Realm: cred.Challenge.Realm,
Method: cred.Challenge.Method,
Intent: cred.Challenge.Intent,
Request: SerializePaymentRequest(cred.Challenge.Request),
Digest: cred.Challenge.Digest,
Expires: cred.Challenge.Expires,
Opaque: cred.Challenge.Opaque,
Description: cred.Challenge.Description,
},
Payload: cred.Payload,
Source: cred.Source,
}
data, err := json.Marshal(wire)
if err != nil {
return ""
}
return AuthSchemePaymentPrefix + base64.RawURLEncoding.EncodeToString(data)
}
var paymentSchemeRe = regexp.MustCompile(`(?i)^` + AuthSchemePayment + `\s+(.+)$`)
// DeserializeCredential parses an Authorization header value into a Credential.
func DeserializeCredential(header string) (Credential, error) {
m := paymentSchemeRe.FindStringSubmatch(strings.TrimSpace(header))
if len(m) < 2 {
return Credential{}, errors.New("mpp: missing Payment scheme in Authorization header")
}
data, err := base64.RawURLEncoding.DecodeString(m[1])
if err != nil {
return Credential{}, fmt.Errorf("mpp: invalid base64url in credential: %w", err)
}
var wire credentialWire
if err := json.Unmarshal(data, &wire); err != nil {
return Credential{}, fmt.Errorf("mpp: invalid JSON in credential: %w", err)
}
req, err := DeserializePaymentRequest(wire.Challenge.Request)
if err != nil {
return Credential{}, fmt.Errorf("mpp: invalid request in credential challenge: %w", err)
}
return Credential{
Challenge: Challenge{
ID: wire.Challenge.ID,
Realm: wire.Challenge.Realm,
Method: wire.Challenge.Method,
Intent: wire.Challenge.Intent,
Request: req,
Digest: wire.Challenge.Digest,
Expires: wire.Challenge.Expires,
Opaque: wire.Challenge.Opaque,
Description: wire.Challenge.Description,
},
Payload: wire.Payload,
Source: wire.Source,
}, nil
}
// ExtractCredential extracts a Credential from an HTTP request's Authorization header.
// Returns (cred, true, nil) if a valid Payment credential is present.
// Returns (Credential{}, false, nil) if no Payment scheme is present.
// Returns (Credential{}, true, err) if a Payment scheme is present but malformed.
func ExtractCredential(r *http.Request) (Credential, bool, error) {
auth := r.Header.Get(AuthorizationHeader)
if auth == "" {
return Credential{}, false, nil
}
scheme := findPaymentScheme(auth)
if scheme == "" {
return Credential{}, false, nil
}
cred, err := DeserializeCredential(scheme)
if err != nil {
return Credential{}, true, err
}
return cred, true, nil
}
// findPaymentScheme extracts the "Payment ..." scheme from an Authorization header value
// that may contain multiple comma-separated auth schemes.
func findPaymentScheme(header string) string {
// Split on comma, but respect quoted strings
schemes := splitAuthSchemes(header)
for _, s := range schemes {
s = strings.TrimSpace(s)
if regexp.MustCompile(`(?i)^` + AuthSchemePayment + `\s+`).MatchString(s) {
return s
}
}
return ""
}
// splitAuthSchemes naively splits an Authorization header on commas.
// This is sufficient because Payment credentials don't contain unescaped commas.
func splitAuthSchemes(header string) []string {
return strings.Split(header, ",")
}