forked from algorandecosystem/caddy-x402avm
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcaddyfile.go
More file actions
179 lines (160 loc) · 4.67 KB
/
Copy pathcaddyfile.go
File metadata and controls
179 lines (160 loc) · 4.67 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package x402avm
import (
"strconv"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
httpcaddyfile.RegisterHandlerDirective("x402", parseCaddyfile)
}
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
x := &X402{FacilitatorURL: defaultFacilitatorURL}
if err := x.UnmarshalCaddyfile(h.Dispenser); err != nil {
return nil, err
}
return x, nil
}
// UnmarshalCaddyfile parses the x402 directive block.
//
// Syntax:
//
// x402 {
// accept {
// pay_to <address>
// price <amount> # e.g. 0.01 or $0.01
// network <network> # e.g. algorand-mainnet, base, solana-mainnet
// scheme <scheme> # optional, default: exact
// }
// accept { ... } # repeat for each additional accepted network
//
// description <text> # optional
// mime_type <mime> # optional, e.g. application/json
// facilitator_url <url> # default: https://facilitator.goplausible.xyz
// dry_run [true|false] # default: false
// quote_url <url> # optional dynamic quote endpoint
// quote_auth_header <value> # optional static auth header for quote endpoint
// quote_timeout_ms <int> # optional timeout in ms for quote endpoint
// settlement_gate_path_regex <regex> # optional, default ^/sessions/[^/]+/paid-action$
// proof_shared_secret <value> # required to sign settlement proof header
//
// # except: skip paywall for paths matching a regexp
// except ^/robots\.txt$
// except ^/\.well-known/
// except ^/favicon\.ico$
// except ^/health$
//
// # ua_match: only apply paywall when User-Agent matches (regexp)
// ua_match GPTBot|ChatGPT-User|OAI-SearchBot|Google-Extended|Googlebot-Extended
// ua_match anthropic-ai|Claude-Web|ClaudeBot|FacebookBot|Meta-ExternalAgent|meta-externalagent
// ua_match Applebot-Extended|CCBot|PerplexityBot|Amazonbot|cohere-ai|Ai2Bot|Bytespider
// ua_match SemrushBot|AhrefsBot|DataForSeoBot|PetalBot|YouBot|Diffbot|Timpibot
// ua_match ImagesiftBot|Kangaroo.Bot|Sidetrade.indexer.bot|Webz\.io|img2dataset
// }
//
// Multiple x402 blocks can appear in one Caddyfile, each attached to a
// different Caddy route; path scoping is handled by Caddy's route/matcher
// system, not by this directive.
func (x *X402) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name "x402"
for d.NextBlock(0) {
switch d.Val() {
case "accept":
opt := PaymentOption{Scheme: "exact"}
for d.NextBlock(1) {
switch d.Val() {
case "pay_to":
if !d.NextArg() {
return d.ArgErr()
}
opt.PayTo = d.Val()
case "price":
if !d.NextArg() {
return d.ArgErr()
}
opt.Price = d.Val()
case "network":
if !d.NextArg() {
return d.ArgErr()
}
opt.Network = d.Val()
case "scheme":
if !d.NextArg() {
return d.ArgErr()
}
opt.Scheme = d.Val()
default:
return d.Errf("unknown accept option: %q", d.Val())
}
}
x.Accepts = append(x.Accepts, opt)
case "description":
if !d.NextArg() {
return d.ArgErr()
}
x.Description = d.Val()
case "mime_type":
if !d.NextArg() {
return d.ArgErr()
}
x.MimeType = d.Val()
case "facilitator_url":
if !d.NextArg() {
return d.ArgErr()
}
x.FacilitatorURL = d.Val()
case "except":
if !d.NextArg() {
return d.ArgErr()
}
x.Except = append(x.Except, d.Val())
case "ua_match":
if !d.NextArg() {
return d.ArgErr()
}
x.UAMatch = append(x.UAMatch, d.Val())
case "dry_run":
if !d.NextArg() {
x.DryRun = true
continue
}
b, err := strconv.ParseBool(d.Val())
if err != nil {
return d.Errf("dry_run must be a boolean, got %q", d.Val())
}
x.DryRun = b
case "quote_url":
if !d.NextArg() {
return d.ArgErr()
}
x.QuoteURL = d.Val()
case "quote_auth_header":
if !d.NextArg() {
return d.ArgErr()
}
x.QuoteAuthHeader = d.Val()
case "quote_timeout_ms":
if !d.NextArg() {
return d.ArgErr()
}
timeout, err := strconv.Atoi(d.Val())
if err != nil || timeout < 0 {
return d.Errf("quote_timeout_ms must be a non-negative integer, got %q", d.Val())
}
x.QuoteTimeoutMS = timeout
case "settlement_gate_path_regex":
if !d.NextArg() {
return d.ArgErr()
}
x.SettlementGatePathRegex = d.Val()
case "proof_shared_secret":
if !d.NextArg() {
return d.ArgErr()
}
x.ProofSharedSecret = d.Val()
default:
return d.Errf("unknown x402 option: %q", d.Val())
}
}
return nil
}