+279
AUTH.md
+279
AUTH.md
···
1
+
# PDS Gatekeeper Authentication Middleware
2
+
3
+
This document describes the authentication middleware system for pds-gatekeeper, which provides flexible authorization rules based on DIDs, handles, and OAuth scopes.
4
+
5
+
## Overview
6
+
7
+
The auth middleware validates incoming requests by:
8
+
9
+
1. **Extracting** the DID and scopes from a JWT Bearer token
10
+
2. **Resolving** the DID to a handle using jacquard-identity
11
+
3. **Validating** against configured authorization rules
12
+
4. **Returning** appropriate HTTP errors (401/403) on failure
13
+
14
+
## Quick Start
15
+
16
+
```rust
17
+
use axum::middleware::from_fn_with_state;
18
+
use crate::auth::{auth_middleware, handle_ends_with, scope_equals, with_rules, AuthRules};
19
+
20
+
let app = Router::new()
21
+
// Simple: require handle from specific domain
22
+
.route("/xrpc/community.blacksky.feed.get",
23
+
get(handler).layer(from_fn_with_state(
24
+
handle_ends_with(".blacksky.team", &state),
25
+
auth_middleware
26
+
)))
27
+
28
+
// Simple: require specific OAuth scope
29
+
.route("/xrpc/com.atproto.repo.createRecord",
30
+
post(handler).layer(from_fn_with_state(
31
+
scope_equals("repo:app.bsky.feed.post", &state),
32
+
auth_middleware
33
+
)))
34
+
35
+
.with_state(state);
36
+
```
37
+
38
+
## ATProto OAuth Scopes Reference
39
+
40
+
| Scope | Description |
41
+
|-------|-------------|
42
+
| `atproto` | Base scope, required for all OAuth clients |
43
+
| `transition:generic` | Full repository access (equivalent to app passwords) |
44
+
| `repo:<collection>` | Access to specific collection (e.g., `repo:app.bsky.feed.post`) |
45
+
| `identity:handle` | Permits handle changes |
46
+
| `identity:*` | Full DID document control |
47
+
| `account:email` | Read email addresses |
48
+
| `account:repo?action=manage` | Import repository data |
49
+
| `blob:*/*` | Upload any blob type |
50
+
| `blob?accept=image/*` | Upload only images |
51
+
52
+
See [Marvin's Guide to OAuth Scopes](https://marvins-guide.leaflet.pub/3mbfvey7sok26) for complete details.
53
+
54
+
## Helper Functions
55
+
56
+
### Identity Helpers
57
+
58
+
| Function | Description |
59
+
|----------|-------------|
60
+
| `handle_ends_with(suffix, state)` | Handle must end with suffix |
61
+
| `handle_ends_with_any(suffixes, state)` | Handle must end with any suffix (OR) |
62
+
| `did_equals(did, state)` | DID must match exactly |
63
+
| `did_equals_any(dids, state)` | DID must match any value (OR) |
64
+
65
+
### Scope Helpers
66
+
67
+
| Function | Description |
68
+
|----------|-------------|
69
+
| `scope_equals(scope, state)` | Must have specific scope |
70
+
| `scope_any(scopes, state)` | Must have any of the scopes (OR) |
71
+
| `scope_all(scopes, state)` | Must have all scopes (AND) |
72
+
73
+
### Combined Helpers (Identity + Scope)
74
+
75
+
| Function | Description |
76
+
|----------|-------------|
77
+
| `handle_ends_with_and_scope(suffix, scope, state)` | Handle suffix AND scope |
78
+
| `handle_ends_with_and_scopes(suffix, scopes, state)` | Handle suffix AND all scopes |
79
+
| `did_with_scope(did, scope, state)` | DID match AND scope |
80
+
| `did_with_scopes(did, scopes, state)` | DID match AND all scopes |
81
+
82
+
### Custom Rules
83
+
84
+
For complex authorization logic, use `with_rules()`:
85
+
86
+
```rust
87
+
with_rules(AuthRules::Any(vec![
88
+
AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()),
89
+
AuthRules::All(vec![
90
+
AuthRules::HandleEndsWith(".mod.team".into()),
91
+
AuthRules::ScopeEquals("account:email".into()),
92
+
]),
93
+
]), &state)
94
+
```
95
+
96
+
## Realistic PDS Endpoint Examples
97
+
98
+
### Admin Endpoints
99
+
100
+
Based on `com.atproto.admin.*` endpoints from the ATProto PDS:
101
+
102
+
```rust
103
+
// com.atproto.admin.deleteAccount
104
+
// Admin-only: specific DID with full access scope
105
+
.route("/xrpc/com.atproto.admin.deleteAccount",
106
+
post(delete_account).layer(from_fn_with_state(
107
+
did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "transition:generic", &state),
108
+
auth_middleware
109
+
)))
110
+
111
+
// com.atproto.admin.getAccountInfo
112
+
// Either admin DID OR (moderator handle + account scope)
113
+
.route("/xrpc/com.atproto.admin.getAccountInfo",
114
+
get(get_account_info).layer(from_fn_with_state(
115
+
with_rules(AuthRules::Any(vec![
116
+
AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()),
117
+
AuthRules::All(vec![
118
+
AuthRules::HandleEndsWith(".mod.team".into()),
119
+
AuthRules::ScopeEquals("account:email".into()),
120
+
]),
121
+
]), &state),
122
+
auth_middleware
123
+
)))
124
+
125
+
// com.atproto.admin.updateAccountEmail
126
+
// Admin DID with account management scope
127
+
.route("/xrpc/com.atproto.admin.updateAccountEmail",
128
+
post(update_email).layer(from_fn_with_state(
129
+
did_with_scopes(
130
+
"did:plc:rnpkyqnmsw4ipey6eotbdnnf",
131
+
["account:email", "account:repo?action=manage"],
132
+
&state
133
+
),
134
+
auth_middleware
135
+
)))
136
+
137
+
// com.atproto.admin.updateAccountHandle
138
+
// Admin with identity control
139
+
.route("/xrpc/com.atproto.admin.updateAccountHandle",
140
+
post(update_handle).layer(from_fn_with_state(
141
+
did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "identity:*", &state),
142
+
auth_middleware
143
+
)))
144
+
```
145
+
146
+
### Repository Endpoints
147
+
148
+
```rust
149
+
// com.atproto.repo.createRecord
150
+
// Scoped write access to specific collection
151
+
.route("/xrpc/com.atproto.repo.createRecord",
152
+
post(create_record).layer(from_fn_with_state(
153
+
scope_equals("repo:app.bsky.feed.post", &state),
154
+
auth_middleware
155
+
)))
156
+
157
+
// com.atproto.repo.putRecord
158
+
// Either specific collection scope OR full access
159
+
.route("/xrpc/com.atproto.repo.putRecord",
160
+
post(put_record).layer(from_fn_with_state(
161
+
scope_any(["repo:app.bsky.feed.post", "transition:generic"], &state),
162
+
auth_middleware
163
+
)))
164
+
165
+
// com.atproto.repo.uploadBlob
166
+
// Blob upload with media type restriction (scope-based)
167
+
.route("/xrpc/com.atproto.repo.uploadBlob",
168
+
post(upload_blob).layer(from_fn_with_state(
169
+
scope_any(["blob:*/*", "blob?accept=image/*", "transition:generic"], &state),
170
+
auth_middleware
171
+
)))
172
+
```
173
+
174
+
### Community/Custom Endpoints
175
+
176
+
```rust
177
+
// Community feed generator - restricted to team members with full access
178
+
.route("/xrpc/community.blacksky.feed.generator",
179
+
post(generator).layer(from_fn_with_state(
180
+
handle_ends_with_and_scope(".blacksky.team", "transition:generic", &state),
181
+
auth_middleware
182
+
)))
183
+
184
+
// Multi-community endpoint
185
+
.route("/xrpc/community.shared.moderation.report",
186
+
post(report).layer(from_fn_with_state(
187
+
with_rules(AuthRules::All(vec![
188
+
AuthRules::HandleEndsWithAny(vec![
189
+
".blacksky.team".into(),
190
+
".bsky.team".into(),
191
+
".mod.social".into(),
192
+
]),
193
+
AuthRules::ScopeEquals("atproto".into()),
194
+
]), &state),
195
+
auth_middleware
196
+
)))
197
+
198
+
// VIP access - specific DIDs only
199
+
.route("/xrpc/community.blacksky.vip.access",
200
+
get(vip_handler).layer(from_fn_with_state(
201
+
did_equals_any([
202
+
"did:plc:rnpkyqnmsw4ipey6eotbdnnf",
203
+
"did:plc:abc123def456ghi789jklmno",
204
+
"did:plc:xyz987uvw654rst321qponml",
205
+
], &state),
206
+
auth_middleware
207
+
)))
208
+
```
209
+
210
+
## Building Complex Authorization Rules
211
+
212
+
The `AuthRules` enum supports arbitrary nesting:
213
+
214
+
```rust
215
+
// Complex: Admin OR (Team member with write scope) OR (Moderator with read-only)
216
+
let rules = AuthRules::Any(vec![
217
+
// Admin bypass
218
+
AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()),
219
+
220
+
// Team member with write access
221
+
AuthRules::All(vec![
222
+
AuthRules::HandleEndsWith(".blacksky.team".into()),
223
+
AuthRules::ScopeEquals("transition:generic".into()),
224
+
]),
225
+
226
+
// Moderator with limited scope
227
+
AuthRules::All(vec![
228
+
AuthRules::HandleEndsWith(".mod.team".into()),
229
+
AuthRules::ScopeEqualsAny(vec![
230
+
"account:email".into(),
231
+
"atproto".into(),
232
+
]),
233
+
]),
234
+
]);
235
+
```
236
+
237
+
## Error Responses
238
+
239
+
| Status | Error Code | Description |
240
+
|--------|------------|-------------|
241
+
| `401` | `AuthRequired` | No Authorization header provided |
242
+
| `401` | `InvalidToken` | JWT validation failed (expired, invalid signature, malformed) |
243
+
| `403` | `AccessDenied` | Valid authentication but authorization rules rejected |
244
+
| `500` | `ResolutionError` | Failed to resolve DID to handle |
245
+
246
+
Response format:
247
+
```json
248
+
{
249
+
"error": "AccessDenied",
250
+
"message": "Access denied by authorization rules"
251
+
}
252
+
```
253
+
254
+
## JWT Token Format
255
+
256
+
The middleware expects JWT tokens with these claims:
257
+
258
+
```json
259
+
{
260
+
"sub": "did:plc:rnpkyqnmsw4ipey6eotbdnnf",
261
+
"scope": "atproto transition:generic repo:app.bsky.feed.post",
262
+
"iat": 1704067200,
263
+
"exp": 1704153600
264
+
}
265
+
```
266
+
267
+
- `sub` (required): The user's DID
268
+
- `scope` (optional): Space-separated OAuth scopes per [RFC 6749](https://tools.ietf.org/html/rfc6749)
269
+
270
+
## Handle Resolution
271
+
272
+
DIDs are resolved to handles using the jacquard-identity `PublicResolver`:
273
+
274
+
1. Check the `HandleCache` for a cached result
275
+
2. If miss, resolve the DID document via PLC directory
276
+
3. Extract handle from `alsoKnownAs` field (format: `at://handle.example.com`)
277
+
4. Cache the result (1 hour TTL default)
278
+
279
+
This allows rules like `HandleEndsWith(".blacksky.team")` to work even though the JWT only contains the DID.
+1989
-521
Cargo.lock
+1989
-521
Cargo.lock
···
3
3
version = 4
4
4
5
5
[[package]]
6
-
name = "addr2line"
7
-
version = "0.24.2"
6
+
name = "abnf"
7
+
version = "0.13.0"
8
8
source = "registry+https://github.com/rust-lang/crates.io-index"
9
-
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
9
+
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
10
dependencies = [
11
-
"gimli",
11
+
"abnf-core",
12
+
"nom 7.1.3",
13
+
]
14
+
15
+
[[package]]
16
+
name = "abnf-core"
17
+
version = "0.5.0"
18
+
source = "registry+https://github.com/rust-lang/crates.io-index"
19
+
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
20
+
dependencies = [
21
+
"nom 7.1.3",
12
22
]
13
23
14
24
[[package]]
···
31
41
32
42
[[package]]
33
43
name = "aho-corasick"
34
-
version = "1.1.3"
44
+
version = "1.1.4"
35
45
source = "registry+https://github.com/rust-lang/crates.io-index"
36
-
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
46
+
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
37
47
dependencies = [
38
48
"memchr",
39
49
]
40
50
41
51
[[package]]
42
-
name = "allocator-api2"
43
-
version = "0.2.21"
52
+
name = "aliasable"
53
+
version = "0.1.3"
44
54
source = "registry+https://github.com/rust-lang/crates.io-index"
45
-
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
55
+
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
46
56
47
57
[[package]]
48
-
name = "android-tzdata"
49
-
version = "0.1.1"
58
+
name = "allocator-api2"
59
+
version = "0.2.21"
50
60
source = "registry+https://github.com/rust-lang/crates.io-index"
51
-
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
61
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
52
62
53
63
[[package]]
54
64
name = "android_system_properties"
···
61
71
62
72
[[package]]
63
73
name = "anyhow"
64
-
version = "1.0.99"
74
+
version = "1.0.100"
75
+
source = "registry+https://github.com/rust-lang/crates.io-index"
76
+
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
77
+
78
+
[[package]]
79
+
name = "ar_archive_writer"
80
+
version = "0.2.0"
65
81
source = "registry+https://github.com/rust-lang/crates.io-index"
66
-
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
82
+
checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a"
83
+
dependencies = [
84
+
"object",
85
+
]
67
86
68
87
[[package]]
69
88
name = "async-compression"
70
-
version = "0.4.27"
89
+
version = "0.4.36"
71
90
source = "registry+https://github.com/rust-lang/crates.io-index"
72
-
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
91
+
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
73
92
dependencies = [
93
+
"compression-codecs",
94
+
"compression-core",
74
95
"futures-core",
75
-
"memchr",
76
96
"pin-project-lite",
77
97
"tokio",
78
-
"zstd",
79
-
"zstd-safe",
80
98
]
81
99
82
100
[[package]]
···
87
105
dependencies = [
88
106
"proc-macro2",
89
107
"quote",
90
-
"syn",
108
+
"syn 2.0.112",
91
109
]
92
110
93
111
[[package]]
···
100
118
]
101
119
102
120
[[package]]
121
+
name = "atomic-polyfill"
122
+
version = "1.0.3"
123
+
source = "registry+https://github.com/rust-lang/crates.io-index"
124
+
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
125
+
dependencies = [
126
+
"critical-section",
127
+
]
128
+
129
+
[[package]]
103
130
name = "atomic-waker"
104
131
version = "1.1.2"
105
132
source = "registry+https://github.com/rust-lang/crates.io-index"
···
113
140
114
141
[[package]]
115
142
name = "aws-lc-rs"
116
-
version = "1.13.3"
143
+
version = "1.15.2"
117
144
source = "registry+https://github.com/rust-lang/crates.io-index"
118
-
checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
145
+
checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288"
119
146
dependencies = [
120
147
"aws-lc-sys",
121
148
"untrusted 0.7.1",
···
124
151
125
152
[[package]]
126
153
name = "aws-lc-sys"
127
-
version = "0.30.0"
154
+
version = "0.35.0"
128
155
source = "registry+https://github.com/rust-lang/crates.io-index"
129
-
checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
156
+
checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1"
130
157
dependencies = [
131
-
"bindgen",
132
158
"cc",
133
159
"cmake",
134
160
"dunce",
···
137
163
138
164
[[package]]
139
165
name = "axum"
140
-
version = "0.8.4"
166
+
version = "0.8.8"
141
167
source = "registry+https://github.com/rust-lang/crates.io-index"
142
-
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
168
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
143
169
dependencies = [
144
170
"axum-core",
145
171
"axum-macros",
···
157
183
"mime",
158
184
"percent-encoding",
159
185
"pin-project-lite",
160
-
"rustversion",
161
-
"serde",
186
+
"serde_core",
162
187
"serde_json",
163
188
"serde_path_to_error",
164
189
"serde_urlencoded",
···
172
197
173
198
[[package]]
174
199
name = "axum-core"
175
-
version = "0.5.2"
200
+
version = "0.5.6"
176
201
source = "registry+https://github.com/rust-lang/crates.io-index"
177
-
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
202
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
178
203
dependencies = [
179
204
"bytes",
180
205
"futures-core",
···
183
208
"http-body-util",
184
209
"mime",
185
210
"pin-project-lite",
186
-
"rustversion",
187
211
"sync_wrapper",
188
212
"tower-layer",
189
213
"tower-service",
···
198
222
dependencies = [
199
223
"proc-macro2",
200
224
"quote",
201
-
"syn",
225
+
"syn 2.0.112",
202
226
]
203
227
204
228
[[package]]
···
210
234
"axum",
211
235
"handlebars",
212
236
"serde",
213
-
"thiserror 2.0.14",
237
+
"thiserror 2.0.17",
214
238
]
215
239
216
240
[[package]]
217
-
name = "backtrace"
218
-
version = "0.3.75"
241
+
name = "base-x"
242
+
version = "0.2.11"
243
+
source = "registry+https://github.com/rust-lang/crates.io-index"
244
+
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
245
+
246
+
[[package]]
247
+
name = "base16ct"
248
+
version = "0.2.0"
219
249
source = "registry+https://github.com/rust-lang/crates.io-index"
220
-
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
250
+
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
251
+
252
+
[[package]]
253
+
name = "base256emoji"
254
+
version = "1.0.2"
255
+
source = "registry+https://github.com/rust-lang/crates.io-index"
256
+
checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c"
221
257
dependencies = [
222
-
"addr2line",
223
-
"cfg-if",
224
-
"libc",
225
-
"miniz_oxide",
226
-
"object",
227
-
"rustc-demangle",
228
-
"windows-targets 0.52.6",
258
+
"const-str",
259
+
"match-lookup",
229
260
]
230
261
231
262
[[package]]
···
236
267
237
268
[[package]]
238
269
name = "base64ct"
239
-
version = "1.8.0"
270
+
version = "1.8.1"
271
+
source = "registry+https://github.com/rust-lang/crates.io-index"
272
+
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
273
+
274
+
[[package]]
275
+
name = "bitflags"
276
+
version = "2.10.0"
240
277
source = "registry+https://github.com/rust-lang/crates.io-index"
241
-
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
278
+
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
279
+
dependencies = [
280
+
"serde_core",
281
+
]
282
+
283
+
[[package]]
284
+
name = "block-buffer"
285
+
version = "0.10.4"
286
+
source = "registry+https://github.com/rust-lang/crates.io-index"
287
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
288
+
dependencies = [
289
+
"generic-array",
290
+
]
242
291
243
292
[[package]]
244
-
name = "bindgen"
245
-
version = "0.69.5"
293
+
name = "bon"
294
+
version = "3.8.1"
246
295
source = "registry+https://github.com/rust-lang/crates.io-index"
247
-
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
296
+
checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1"
248
297
dependencies = [
249
-
"bitflags",
250
-
"cexpr",
251
-
"clang-sys",
252
-
"itertools",
253
-
"lazy_static",
254
-
"lazycell",
255
-
"log",
298
+
"bon-macros",
299
+
"rustversion",
300
+
]
301
+
302
+
[[package]]
303
+
name = "bon-macros"
304
+
version = "3.8.1"
305
+
source = "registry+https://github.com/rust-lang/crates.io-index"
306
+
checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645"
307
+
dependencies = [
308
+
"darling 0.21.3",
309
+
"ident_case",
256
310
"prettyplease",
257
311
"proc-macro2",
258
312
"quote",
259
-
"regex",
260
-
"rustc-hash",
261
-
"shlex",
262
-
"syn",
263
-
"which",
313
+
"rustversion",
314
+
"syn 2.0.112",
264
315
]
265
316
266
317
[[package]]
267
-
name = "bitflags"
268
-
version = "2.9.1"
318
+
name = "borsh"
319
+
version = "1.6.0"
269
320
source = "registry+https://github.com/rust-lang/crates.io-index"
270
-
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
321
+
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
271
322
dependencies = [
323
+
"cfg_aliases",
324
+
]
325
+
326
+
[[package]]
327
+
name = "bstr"
328
+
version = "1.12.1"
329
+
source = "registry+https://github.com/rust-lang/crates.io-index"
330
+
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
331
+
dependencies = [
332
+
"memchr",
272
333
"serde",
273
334
]
274
335
275
336
[[package]]
276
-
name = "block-buffer"
277
-
version = "0.10.4"
337
+
name = "btree-range-map"
338
+
version = "0.7.2"
278
339
source = "registry+https://github.com/rust-lang/crates.io-index"
279
-
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
340
+
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
280
341
dependencies = [
281
-
"generic-array",
342
+
"btree-slab",
343
+
"cc-traits",
344
+
"range-traits",
345
+
"serde",
346
+
"slab",
282
347
]
283
348
284
349
[[package]]
285
-
name = "bstr"
286
-
version = "1.12.0"
350
+
name = "btree-slab"
351
+
version = "0.6.1"
287
352
source = "registry+https://github.com/rust-lang/crates.io-index"
288
-
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
353
+
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
289
354
dependencies = [
290
-
"memchr",
291
-
"serde",
355
+
"cc-traits",
356
+
"slab",
357
+
"smallvec",
292
358
]
293
359
294
360
[[package]]
295
361
name = "bumpalo"
296
-
version = "3.19.0"
362
+
version = "3.19.1"
297
363
source = "registry+https://github.com/rust-lang/crates.io-index"
298
-
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
364
+
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
299
365
300
366
[[package]]
301
367
name = "byteorder"
···
305
371
306
372
[[package]]
307
373
name = "bytes"
308
-
version = "1.10.1"
374
+
version = "1.11.0"
375
+
source = "registry+https://github.com/rust-lang/crates.io-index"
376
+
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
377
+
dependencies = [
378
+
"serde",
379
+
]
380
+
381
+
[[package]]
382
+
name = "cbor4ii"
383
+
version = "0.2.14"
309
384
source = "registry+https://github.com/rust-lang/crates.io-index"
310
-
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
385
+
checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4"
386
+
dependencies = [
387
+
"serde",
388
+
]
311
389
312
390
[[package]]
313
391
name = "cc"
314
-
version = "1.2.32"
392
+
version = "1.2.51"
315
393
source = "registry+https://github.com/rust-lang/crates.io-index"
316
-
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
394
+
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
317
395
dependencies = [
396
+
"find-msvc-tools",
318
397
"jobserver",
319
398
"libc",
320
399
"shlex",
321
400
]
322
401
323
402
[[package]]
324
-
name = "cexpr"
325
-
version = "0.6.0"
403
+
name = "cc-traits"
404
+
version = "2.0.0"
326
405
source = "registry+https://github.com/rust-lang/crates.io-index"
327
-
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
406
+
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
328
407
dependencies = [
329
-
"nom 7.1.3",
408
+
"slab",
330
409
]
331
410
332
411
[[package]]
333
412
name = "cfg-if"
334
-
version = "1.0.1"
413
+
version = "1.0.4"
335
414
source = "registry+https://github.com/rust-lang/crates.io-index"
336
-
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
415
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
416
+
417
+
[[package]]
418
+
name = "cfg_aliases"
419
+
version = "0.2.1"
420
+
source = "registry+https://github.com/rust-lang/crates.io-index"
421
+
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
337
422
338
423
[[package]]
339
424
name = "chrono"
340
-
version = "0.4.41"
425
+
version = "0.4.42"
341
426
source = "registry+https://github.com/rust-lang/crates.io-index"
342
-
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
427
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
343
428
dependencies = [
344
-
"android-tzdata",
345
429
"iana-time-zone",
346
430
"js-sys",
347
431
"num-traits",
432
+
"serde",
348
433
"wasm-bindgen",
349
434
"windows-link",
350
435
]
···
384
469
dependencies = [
385
470
"ciborium-io",
386
471
"half",
472
+
]
473
+
474
+
[[package]]
475
+
name = "cid"
476
+
version = "0.11.1"
477
+
source = "registry+https://github.com/rust-lang/crates.io-index"
478
+
checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a"
479
+
dependencies = [
480
+
"core2",
481
+
"multibase",
482
+
"multihash",
483
+
"serde",
484
+
"serde_bytes",
485
+
"unsigned-varint",
387
486
]
388
487
389
488
[[package]]
···
397
496
]
398
497
399
498
[[package]]
400
-
name = "clang-sys"
401
-
version = "1.8.1"
499
+
name = "cmake"
500
+
version = "0.1.57"
402
501
source = "registry+https://github.com/rust-lang/crates.io-index"
403
-
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
502
+
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
404
503
dependencies = [
405
-
"glob",
406
-
"libc",
407
-
"libloading",
504
+
"cc",
408
505
]
409
506
410
507
[[package]]
411
-
name = "cmake"
412
-
version = "0.1.54"
508
+
name = "cobs"
509
+
version = "0.3.0"
510
+
source = "registry+https://github.com/rust-lang/crates.io-index"
511
+
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
512
+
dependencies = [
513
+
"thiserror 2.0.17",
514
+
]
515
+
516
+
[[package]]
517
+
name = "compression-codecs"
518
+
version = "0.4.35"
413
519
source = "registry+https://github.com/rust-lang/crates.io-index"
414
-
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
520
+
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
415
521
dependencies = [
416
-
"cc",
522
+
"compression-core",
523
+
"flate2",
524
+
"memchr",
525
+
"zstd",
526
+
"zstd-safe",
417
527
]
528
+
529
+
[[package]]
530
+
name = "compression-core"
531
+
version = "0.4.31"
532
+
source = "registry+https://github.com/rust-lang/crates.io-index"
533
+
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
418
534
419
535
[[package]]
420
536
name = "concurrent-queue"
···
432
548
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
433
549
434
550
[[package]]
551
+
name = "const-str"
552
+
version = "0.4.3"
553
+
source = "registry+https://github.com/rust-lang/crates.io-index"
554
+
checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3"
555
+
556
+
[[package]]
557
+
name = "cordyceps"
558
+
version = "0.3.4"
559
+
source = "registry+https://github.com/rust-lang/crates.io-index"
560
+
checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"
561
+
dependencies = [
562
+
"loom",
563
+
"tracing",
564
+
]
565
+
566
+
[[package]]
567
+
name = "core-foundation"
568
+
version = "0.9.4"
569
+
source = "registry+https://github.com/rust-lang/crates.io-index"
570
+
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
571
+
dependencies = [
572
+
"core-foundation-sys",
573
+
"libc",
574
+
]
575
+
576
+
[[package]]
435
577
name = "core-foundation-sys"
436
578
version = "0.8.7"
437
579
source = "registry+https://github.com/rust-lang/crates.io-index"
438
580
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
439
581
440
582
[[package]]
583
+
name = "core2"
584
+
version = "0.4.0"
585
+
source = "registry+https://github.com/rust-lang/crates.io-index"
586
+
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
587
+
dependencies = [
588
+
"memchr",
589
+
]
590
+
591
+
[[package]]
441
592
name = "cpufeatures"
442
593
version = "0.2.17"
443
594
source = "registry+https://github.com/rust-lang/crates.io-index"
···
448
599
449
600
[[package]]
450
601
name = "crc"
451
-
version = "3.3.0"
602
+
version = "3.4.0"
452
603
source = "registry+https://github.com/rust-lang/crates.io-index"
453
-
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
604
+
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
454
605
dependencies = [
455
606
"crc-catalog",
456
607
]
···
462
613
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
463
614
464
615
[[package]]
616
+
name = "crc32fast"
617
+
version = "1.5.0"
618
+
source = "registry+https://github.com/rust-lang/crates.io-index"
619
+
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
620
+
dependencies = [
621
+
"cfg-if",
622
+
]
623
+
624
+
[[package]]
625
+
name = "critical-section"
626
+
version = "1.2.0"
627
+
source = "registry+https://github.com/rust-lang/crates.io-index"
628
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
629
+
630
+
[[package]]
465
631
name = "crossbeam-queue"
466
632
version = "0.3.12"
467
633
source = "registry+https://github.com/rust-lang/crates.io-index"
···
483
649
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
484
650
485
651
[[package]]
652
+
name = "crypto-bigint"
653
+
version = "0.5.5"
654
+
source = "registry+https://github.com/rust-lang/crates.io-index"
655
+
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
656
+
dependencies = [
657
+
"generic-array",
658
+
"rand_core 0.6.4",
659
+
"subtle",
660
+
"zeroize",
661
+
]
662
+
663
+
[[package]]
486
664
name = "crypto-common"
487
-
version = "0.1.6"
665
+
version = "0.1.7"
488
666
source = "registry+https://github.com/rust-lang/crates.io-index"
489
-
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
667
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
490
668
dependencies = [
491
669
"generic-array",
492
670
"typenum",
···
498
676
source = "registry+https://github.com/rust-lang/crates.io-index"
499
677
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
500
678
dependencies = [
501
-
"darling_core",
502
-
"darling_macro",
679
+
"darling_core 0.20.11",
680
+
"darling_macro 0.20.11",
681
+
]
682
+
683
+
[[package]]
684
+
name = "darling"
685
+
version = "0.21.3"
686
+
source = "registry+https://github.com/rust-lang/crates.io-index"
687
+
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
688
+
dependencies = [
689
+
"darling_core 0.21.3",
690
+
"darling_macro 0.21.3",
503
691
]
504
692
505
693
[[package]]
···
513
701
"proc-macro2",
514
702
"quote",
515
703
"strsim",
516
-
"syn",
704
+
"syn 2.0.112",
705
+
]
706
+
707
+
[[package]]
708
+
name = "darling_core"
709
+
version = "0.21.3"
710
+
source = "registry+https://github.com/rust-lang/crates.io-index"
711
+
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
712
+
dependencies = [
713
+
"fnv",
714
+
"ident_case",
715
+
"proc-macro2",
716
+
"quote",
717
+
"strsim",
718
+
"syn 2.0.112",
517
719
]
518
720
519
721
[[package]]
···
522
724
source = "registry+https://github.com/rust-lang/crates.io-index"
523
725
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
524
726
dependencies = [
525
-
"darling_core",
727
+
"darling_core 0.20.11",
728
+
"quote",
729
+
"syn 2.0.112",
730
+
]
731
+
732
+
[[package]]
733
+
name = "darling_macro"
734
+
version = "0.21.3"
735
+
source = "registry+https://github.com/rust-lang/crates.io-index"
736
+
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
737
+
dependencies = [
738
+
"darling_core 0.21.3",
526
739
"quote",
527
-
"syn",
740
+
"syn 2.0.112",
528
741
]
529
742
530
743
[[package]]
···
542
755
]
543
756
544
757
[[package]]
758
+
name = "data-encoding"
759
+
version = "2.9.0"
760
+
source = "registry+https://github.com/rust-lang/crates.io-index"
761
+
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
762
+
763
+
[[package]]
764
+
name = "data-encoding-macro"
765
+
version = "0.1.18"
766
+
source = "registry+https://github.com/rust-lang/crates.io-index"
767
+
checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d"
768
+
dependencies = [
769
+
"data-encoding",
770
+
"data-encoding-macro-internal",
771
+
]
772
+
773
+
[[package]]
774
+
name = "data-encoding-macro-internal"
775
+
version = "0.1.16"
776
+
source = "registry+https://github.com/rust-lang/crates.io-index"
777
+
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
778
+
dependencies = [
779
+
"data-encoding",
780
+
"syn 2.0.112",
781
+
]
782
+
783
+
[[package]]
545
784
name = "der"
546
785
version = "0.7.10"
547
786
source = "registry+https://github.com/rust-lang/crates.io-index"
···
553
792
]
554
793
555
794
[[package]]
795
+
name = "deranged"
796
+
version = "0.5.5"
797
+
source = "registry+https://github.com/rust-lang/crates.io-index"
798
+
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
799
+
dependencies = [
800
+
"powerfmt",
801
+
"serde_core",
802
+
]
803
+
804
+
[[package]]
556
805
name = "derive_builder"
557
806
version = "0.20.2"
558
807
source = "registry+https://github.com/rust-lang/crates.io-index"
···
567
816
source = "registry+https://github.com/rust-lang/crates.io-index"
568
817
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
569
818
dependencies = [
570
-
"darling",
819
+
"darling 0.20.11",
571
820
"proc-macro2",
572
821
"quote",
573
-
"syn",
822
+
"syn 2.0.112",
574
823
]
575
824
576
825
[[package]]
···
580
829
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
581
830
dependencies = [
582
831
"derive_builder_core",
583
-
"syn",
832
+
"syn 2.0.112",
584
833
]
585
834
586
835
[[package]]
836
+
name = "derive_more"
837
+
version = "1.0.0"
838
+
source = "registry+https://github.com/rust-lang/crates.io-index"
839
+
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
840
+
dependencies = [
841
+
"derive_more-impl",
842
+
]
843
+
844
+
[[package]]
845
+
name = "derive_more-impl"
846
+
version = "1.0.0"
847
+
source = "registry+https://github.com/rust-lang/crates.io-index"
848
+
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
849
+
dependencies = [
850
+
"proc-macro2",
851
+
"quote",
852
+
"syn 2.0.112",
853
+
"unicode-xid",
854
+
]
855
+
856
+
[[package]]
857
+
name = "diatomic-waker"
858
+
version = "0.2.3"
859
+
source = "registry+https://github.com/rust-lang/crates.io-index"
860
+
checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c"
861
+
862
+
[[package]]
587
863
name = "digest"
588
864
version = "0.10.7"
589
865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
603
879
dependencies = [
604
880
"proc-macro2",
605
881
"quote",
606
-
"syn",
882
+
"syn 2.0.112",
607
883
]
608
884
609
885
[[package]]
···
619
895
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
620
896
621
897
[[package]]
898
+
name = "dyn-clone"
899
+
version = "1.0.20"
900
+
source = "registry+https://github.com/rust-lang/crates.io-index"
901
+
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
902
+
903
+
[[package]]
904
+
name = "ecdsa"
905
+
version = "0.16.9"
906
+
source = "registry+https://github.com/rust-lang/crates.io-index"
907
+
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
908
+
dependencies = [
909
+
"der",
910
+
"digest",
911
+
"elliptic-curve",
912
+
"rfc6979",
913
+
"signature",
914
+
"spki",
915
+
]
916
+
917
+
[[package]]
622
918
name = "either"
623
919
version = "1.15.0"
624
920
source = "registry+https://github.com/rust-lang/crates.io-index"
···
628
924
]
629
925
630
926
[[package]]
927
+
name = "elliptic-curve"
928
+
version = "0.13.8"
929
+
source = "registry+https://github.com/rust-lang/crates.io-index"
930
+
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
931
+
dependencies = [
932
+
"base16ct",
933
+
"crypto-bigint",
934
+
"digest",
935
+
"ff",
936
+
"generic-array",
937
+
"group",
938
+
"pem-rfc7468",
939
+
"pkcs8",
940
+
"rand_core 0.6.4",
941
+
"sec1",
942
+
"subtle",
943
+
"zeroize",
944
+
]
945
+
946
+
[[package]]
631
947
name = "email-encoding"
632
948
version = "0.4.1"
633
949
source = "registry+https://github.com/rust-lang/crates.io-index"
···
644
960
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
645
961
646
962
[[package]]
963
+
name = "embedded-io"
964
+
version = "0.4.0"
965
+
source = "registry+https://github.com/rust-lang/crates.io-index"
966
+
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
967
+
968
+
[[package]]
969
+
name = "embedded-io"
970
+
version = "0.6.1"
971
+
source = "registry+https://github.com/rust-lang/crates.io-index"
972
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
973
+
974
+
[[package]]
975
+
name = "encoding_rs"
976
+
version = "0.8.35"
977
+
source = "registry+https://github.com/rust-lang/crates.io-index"
978
+
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
979
+
dependencies = [
980
+
"cfg-if",
981
+
]
982
+
983
+
[[package]]
647
984
name = "equivalent"
648
985
version = "1.0.2"
649
986
source = "registry+https://github.com/rust-lang/crates.io-index"
···
651
988
652
989
[[package]]
653
990
name = "errno"
654
-
version = "0.3.13"
991
+
version = "0.3.14"
655
992
source = "registry+https://github.com/rust-lang/crates.io-index"
656
-
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
993
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
657
994
dependencies = [
658
995
"libc",
659
996
"windows-sys 0.59.0",
···
686
1023
version = "2.3.0"
687
1024
source = "registry+https://github.com/rust-lang/crates.io-index"
688
1025
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
1026
+
1027
+
[[package]]
1028
+
name = "ff"
1029
+
version = "0.13.1"
1030
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1031
+
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
1032
+
dependencies = [
1033
+
"rand_core 0.6.4",
1034
+
"subtle",
1035
+
]
1036
+
1037
+
[[package]]
1038
+
name = "find-msvc-tools"
1039
+
version = "0.1.6"
1040
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1041
+
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
1042
+
1043
+
[[package]]
1044
+
name = "flate2"
1045
+
version = "1.1.5"
1046
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1047
+
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
1048
+
dependencies = [
1049
+
"crc32fast",
1050
+
"miniz_oxide",
1051
+
]
689
1052
690
1053
[[package]]
691
1054
name = "flume"
···
695
1058
dependencies = [
696
1059
"futures-core",
697
1060
"futures-sink",
698
-
"spin",
1061
+
"spin 0.9.8",
699
1062
]
700
1063
701
1064
[[package]]
···
711
1074
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
712
1075
713
1076
[[package]]
1077
+
name = "foldhash"
1078
+
version = "0.2.0"
1079
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1080
+
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
1081
+
1082
+
[[package]]
1083
+
name = "foreign-types"
1084
+
version = "0.3.2"
1085
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1086
+
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
1087
+
dependencies = [
1088
+
"foreign-types-shared",
1089
+
]
1090
+
1091
+
[[package]]
1092
+
name = "foreign-types-shared"
1093
+
version = "0.1.1"
1094
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1095
+
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
1096
+
1097
+
[[package]]
714
1098
name = "form_urlencoded"
715
-
version = "1.2.1"
1099
+
version = "1.2.2"
716
1100
source = "registry+https://github.com/rust-lang/crates.io-index"
717
-
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
1101
+
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
718
1102
dependencies = [
719
1103
"percent-encoding",
720
1104
]
···
736
1120
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
737
1121
738
1122
[[package]]
1123
+
name = "futures-buffered"
1124
+
version = "0.2.12"
1125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1126
+
checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd"
1127
+
dependencies = [
1128
+
"cordyceps",
1129
+
"diatomic-waker",
1130
+
"futures-core",
1131
+
"pin-project-lite",
1132
+
"spin 0.10.0",
1133
+
]
1134
+
1135
+
[[package]]
739
1136
name = "futures-channel"
740
1137
version = "0.3.31"
741
1138
source = "registry+https://github.com/rust-lang/crates.io-index"
···
780
1177
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
781
1178
782
1179
[[package]]
1180
+
name = "futures-lite"
1181
+
version = "2.6.1"
1182
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1183
+
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
1184
+
dependencies = [
1185
+
"fastrand",
1186
+
"futures-core",
1187
+
"futures-io",
1188
+
"parking",
1189
+
"pin-project-lite",
1190
+
]
1191
+
1192
+
[[package]]
1193
+
name = "futures-macro"
1194
+
version = "0.3.31"
1195
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1196
+
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
1197
+
dependencies = [
1198
+
"proc-macro2",
1199
+
"quote",
1200
+
"syn 2.0.112",
1201
+
]
1202
+
1203
+
[[package]]
783
1204
name = "futures-sink"
784
1205
version = "0.3.31"
785
1206
source = "registry+https://github.com/rust-lang/crates.io-index"
···
805
1226
dependencies = [
806
1227
"futures-core",
807
1228
"futures-io",
1229
+
"futures-macro",
808
1230
"futures-sink",
809
1231
"futures-task",
810
1232
"memchr",
···
814
1236
]
815
1237
816
1238
[[package]]
1239
+
name = "generator"
1240
+
version = "0.8.8"
1241
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1242
+
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
1243
+
dependencies = [
1244
+
"cc",
1245
+
"cfg-if",
1246
+
"libc",
1247
+
"log",
1248
+
"rustversion",
1249
+
"windows-link",
1250
+
"windows-result",
1251
+
]
1252
+
1253
+
[[package]]
817
1254
name = "generic-array"
818
1255
version = "0.14.7"
819
1256
source = "registry+https://github.com/rust-lang/crates.io-index"
···
821
1258
dependencies = [
822
1259
"typenum",
823
1260
"version_check",
1261
+
"zeroize",
824
1262
]
825
1263
826
1264
[[package]]
···
830
1268
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
831
1269
dependencies = [
832
1270
"cfg-if",
1271
+
"js-sys",
833
1272
"libc",
834
-
"wasi 0.11.1+wasi-snapshot-preview1",
1273
+
"wasi",
1274
+
"wasm-bindgen",
835
1275
]
836
1276
837
1277
[[package]]
838
1278
name = "getrandom"
839
-
version = "0.3.3"
1279
+
version = "0.3.4"
840
1280
source = "registry+https://github.com/rust-lang/crates.io-index"
841
-
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1281
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
842
1282
dependencies = [
843
1283
"cfg-if",
844
1284
"js-sys",
845
1285
"libc",
846
1286
"r-efi",
847
-
"wasi 0.14.2+wasi-0.2.4",
1287
+
"wasip2",
848
1288
"wasm-bindgen",
849
1289
]
850
1290
851
1291
[[package]]
852
-
name = "gimli"
853
-
version = "0.31.1"
854
-
source = "registry+https://github.com/rust-lang/crates.io-index"
855
-
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
856
-
857
-
[[package]]
858
-
name = "glob"
859
-
version = "0.3.3"
860
-
source = "registry+https://github.com/rust-lang/crates.io-index"
861
-
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
862
-
863
-
[[package]]
864
1292
name = "globset"
865
-
version = "0.4.16"
1293
+
version = "0.4.18"
866
1294
source = "registry+https://github.com/rust-lang/crates.io-index"
867
-
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
1295
+
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
868
1296
dependencies = [
869
1297
"aho-corasick",
870
1298
"bstr",
871
1299
"log",
872
-
"regex-automata 0.4.9",
873
-
"regex-syntax 0.8.5",
1300
+
"regex-automata",
1301
+
"regex-syntax",
874
1302
]
875
1303
876
1304
[[package]]
877
1305
name = "governor"
878
-
version = "0.10.1"
1306
+
version = "0.10.4"
879
1307
source = "registry+https://github.com/rust-lang/crates.io-index"
880
-
checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19"
1308
+
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
881
1309
dependencies = [
882
1310
"cfg-if",
883
1311
"dashmap",
884
1312
"futures-sink",
885
1313
"futures-timer",
886
1314
"futures-util",
887
-
"getrandom 0.3.3",
888
-
"hashbrown 0.15.5",
1315
+
"getrandom 0.3.4",
1316
+
"hashbrown 0.16.1",
889
1317
"nonzero_ext",
890
1318
"parking_lot",
891
1319
"portable-atomic",
···
897
1325
]
898
1326
899
1327
[[package]]
1328
+
name = "group"
1329
+
version = "0.13.0"
1330
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1331
+
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
1332
+
dependencies = [
1333
+
"ff",
1334
+
"rand_core 0.6.4",
1335
+
"subtle",
1336
+
]
1337
+
1338
+
[[package]]
900
1339
name = "h2"
901
1340
version = "0.4.12"
902
1341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
908
1347
"futures-core",
909
1348
"futures-sink",
910
1349
"http",
911
-
"indexmap",
1350
+
"indexmap 2.12.1",
912
1351
"slab",
913
1352
"tokio",
914
1353
"tokio-util",
···
917
1356
918
1357
[[package]]
919
1358
name = "half"
920
-
version = "2.6.0"
1359
+
version = "2.7.1"
921
1360
source = "registry+https://github.com/rust-lang/crates.io-index"
922
-
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
1361
+
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
923
1362
dependencies = [
924
1363
"cfg-if",
925
1364
"crunchy",
1365
+
"zerocopy",
926
1366
]
927
1367
928
1368
[[package]]
929
1369
name = "handlebars"
930
-
version = "6.3.2"
1370
+
version = "6.4.0"
931
1371
source = "registry+https://github.com/rust-lang/crates.io-index"
932
-
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
1372
+
checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e"
933
1373
dependencies = [
934
1374
"derive_builder",
935
1375
"log",
···
939
1379
"rust-embed",
940
1380
"serde",
941
1381
"serde_json",
942
-
"thiserror 2.0.14",
1382
+
"thiserror 2.0.17",
1383
+
]
1384
+
1385
+
[[package]]
1386
+
name = "hash32"
1387
+
version = "0.2.1"
1388
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1389
+
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
1390
+
dependencies = [
1391
+
"byteorder",
943
1392
]
944
1393
945
1394
[[package]]
946
1395
name = "hashbrown"
1396
+
version = "0.12.3"
1397
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1398
+
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1399
+
1400
+
[[package]]
1401
+
name = "hashbrown"
947
1402
version = "0.14.5"
948
1403
source = "registry+https://github.com/rust-lang/crates.io-index"
949
1404
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
···
960
1415
dependencies = [
961
1416
"allocator-api2",
962
1417
"equivalent",
963
-
"foldhash",
1418
+
"foldhash 0.1.5",
1419
+
]
1420
+
1421
+
[[package]]
1422
+
name = "hashbrown"
1423
+
version = "0.16.1"
1424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1425
+
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1426
+
dependencies = [
1427
+
"allocator-api2",
1428
+
"equivalent",
1429
+
"foldhash 0.2.0",
964
1430
]
965
1431
966
1432
[[package]]
···
973
1439
]
974
1440
975
1441
[[package]]
1442
+
name = "heapless"
1443
+
version = "0.7.17"
1444
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1445
+
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
1446
+
dependencies = [
1447
+
"atomic-polyfill",
1448
+
"hash32",
1449
+
"rustc_version",
1450
+
"serde",
1451
+
"spin 0.9.8",
1452
+
"stable_deref_trait",
1453
+
]
1454
+
1455
+
[[package]]
1456
+
name = "heck"
1457
+
version = "0.4.1"
1458
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1459
+
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
1460
+
1461
+
[[package]]
976
1462
name = "heck"
977
1463
version = "0.5.0"
978
1464
source = "registry+https://github.com/rust-lang/crates.io-index"
···
985
1471
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
986
1472
987
1473
[[package]]
1474
+
name = "hex_fmt"
1475
+
version = "0.3.0"
1476
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1477
+
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
1478
+
1479
+
[[package]]
988
1480
name = "hkdf"
989
1481
version = "0.12.4"
990
1482
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1004
1496
1005
1497
[[package]]
1006
1498
name = "home"
1007
-
version = "0.5.11"
1499
+
version = "0.5.12"
1008
1500
source = "registry+https://github.com/rust-lang/crates.io-index"
1009
-
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
1501
+
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
1010
1502
dependencies = [
1011
-
"windows-sys 0.59.0",
1503
+
"windows-sys 0.61.2",
1504
+
]
1505
+
1506
+
[[package]]
1507
+
name = "html-escape"
1508
+
version = "0.2.13"
1509
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1510
+
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
1511
+
dependencies = [
1512
+
"utf8-width",
1012
1513
]
1013
1514
1014
1515
[[package]]
1015
1516
name = "http"
1016
-
version = "1.3.1"
1517
+
version = "1.4.0"
1017
1518
source = "registry+https://github.com/rust-lang/crates.io-index"
1018
-
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
1519
+
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
1019
1520
dependencies = [
1020
1521
"bytes",
1021
-
"fnv",
1022
1522
"itoa",
1023
1523
]
1024
1524
···
1059
1559
1060
1560
[[package]]
1061
1561
name = "hyper"
1062
-
version = "1.6.0"
1562
+
version = "1.8.1"
1063
1563
source = "registry+https://github.com/rust-lang/crates.io-index"
1064
-
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
1564
+
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
1065
1565
dependencies = [
1566
+
"atomic-waker",
1066
1567
"bytes",
1067
1568
"futures-channel",
1068
-
"futures-util",
1569
+
"futures-core",
1069
1570
"h2",
1070
1571
"http",
1071
1572
"http-body",
···
1073
1574
"httpdate",
1074
1575
"itoa",
1075
1576
"pin-project-lite",
1577
+
"pin-utils",
1076
1578
"smallvec",
1077
1579
"tokio",
1078
1580
"want",
1079
1581
]
1080
1582
1081
1583
[[package]]
1584
+
name = "hyper-rustls"
1585
+
version = "0.27.7"
1586
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1587
+
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
1588
+
dependencies = [
1589
+
"http",
1590
+
"hyper",
1591
+
"hyper-util",
1592
+
"rustls",
1593
+
"rustls-pki-types",
1594
+
"tokio",
1595
+
"tokio-rustls",
1596
+
"tower-service",
1597
+
"webpki-roots 1.0.5",
1598
+
]
1599
+
1600
+
[[package]]
1082
1601
name = "hyper-timeout"
1083
1602
version = "0.5.2"
1084
1603
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1093
1612
1094
1613
[[package]]
1095
1614
name = "hyper-util"
1096
-
version = "0.1.16"
1615
+
version = "0.1.19"
1097
1616
source = "registry+https://github.com/rust-lang/crates.io-index"
1098
-
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1617
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
1099
1618
dependencies = [
1619
+
"base64",
1100
1620
"bytes",
1101
1621
"futures-channel",
1102
1622
"futures-core",
···
1104
1624
"http",
1105
1625
"http-body",
1106
1626
"hyper",
1627
+
"ipnet",
1107
1628
"libc",
1629
+
"percent-encoding",
1108
1630
"pin-project-lite",
1109
1631
"socket2",
1632
+
"system-configuration",
1110
1633
"tokio",
1111
1634
"tower-service",
1112
1635
"tracing",
1636
+
"windows-registry",
1113
1637
]
1114
1638
1115
1639
[[package]]
1116
1640
name = "iana-time-zone"
1117
-
version = "0.1.63"
1641
+
version = "0.1.64"
1118
1642
source = "registry+https://github.com/rust-lang/crates.io-index"
1119
-
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
1643
+
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
1120
1644
dependencies = [
1121
1645
"android_system_properties",
1122
1646
"core-foundation-sys",
···
1138
1662
1139
1663
[[package]]
1140
1664
name = "icu_collections"
1141
-
version = "2.0.0"
1665
+
version = "2.1.1"
1142
1666
source = "registry+https://github.com/rust-lang/crates.io-index"
1143
-
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
1667
+
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
1144
1668
dependencies = [
1145
1669
"displaydoc",
1146
1670
"potential_utf",
···
1151
1675
1152
1676
[[package]]
1153
1677
name = "icu_locale_core"
1154
-
version = "2.0.0"
1678
+
version = "2.1.1"
1155
1679
source = "registry+https://github.com/rust-lang/crates.io-index"
1156
-
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
1680
+
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
1157
1681
dependencies = [
1158
1682
"displaydoc",
1159
1683
"litemap",
···
1164
1688
1165
1689
[[package]]
1166
1690
name = "icu_normalizer"
1167
-
version = "2.0.0"
1691
+
version = "2.1.1"
1168
1692
source = "registry+https://github.com/rust-lang/crates.io-index"
1169
-
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
1693
+
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
1170
1694
dependencies = [
1171
-
"displaydoc",
1172
1695
"icu_collections",
1173
1696
"icu_normalizer_data",
1174
1697
"icu_properties",
···
1179
1702
1180
1703
[[package]]
1181
1704
name = "icu_normalizer_data"
1182
-
version = "2.0.0"
1705
+
version = "2.1.1"
1183
1706
source = "registry+https://github.com/rust-lang/crates.io-index"
1184
-
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
1707
+
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
1185
1708
1186
1709
[[package]]
1187
1710
name = "icu_properties"
1188
-
version = "2.0.1"
1711
+
version = "2.1.2"
1189
1712
source = "registry+https://github.com/rust-lang/crates.io-index"
1190
-
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
1713
+
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1191
1714
dependencies = [
1192
-
"displaydoc",
1193
1715
"icu_collections",
1194
1716
"icu_locale_core",
1195
1717
"icu_properties_data",
1196
1718
"icu_provider",
1197
-
"potential_utf",
1198
1719
"zerotrie",
1199
1720
"zerovec",
1200
1721
]
1201
1722
1202
1723
[[package]]
1203
1724
name = "icu_properties_data"
1204
-
version = "2.0.1"
1725
+
version = "2.1.2"
1205
1726
source = "registry+https://github.com/rust-lang/crates.io-index"
1206
-
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
1727
+
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1207
1728
1208
1729
[[package]]
1209
1730
name = "icu_provider"
1210
-
version = "2.0.0"
1731
+
version = "2.1.1"
1211
1732
source = "registry+https://github.com/rust-lang/crates.io-index"
1212
-
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
1733
+
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
1213
1734
dependencies = [
1214
1735
"displaydoc",
1215
1736
"icu_locale_core",
1216
-
"stable_deref_trait",
1217
-
"tinystr",
1218
1737
"writeable",
1219
1738
"yoke",
1220
1739
"zerofrom",
···
1230
1749
1231
1750
[[package]]
1232
1751
name = "idna"
1233
-
version = "1.0.3"
1752
+
version = "1.1.0"
1234
1753
source = "registry+https://github.com/rust-lang/crates.io-index"
1235
-
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
1754
+
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
1236
1755
dependencies = [
1237
1756
"idna_adapter",
1238
1757
"smallvec",
···
1251
1770
1252
1771
[[package]]
1253
1772
name = "indexmap"
1254
-
version = "2.10.0"
1773
+
version = "1.9.3"
1774
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1775
+
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1776
+
dependencies = [
1777
+
"autocfg",
1778
+
"hashbrown 0.12.3",
1779
+
"serde",
1780
+
]
1781
+
1782
+
[[package]]
1783
+
name = "indexmap"
1784
+
version = "2.12.1"
1255
1785
source = "registry+https://github.com/rust-lang/crates.io-index"
1256
-
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
1786
+
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
1257
1787
dependencies = [
1258
1788
"equivalent",
1259
-
"hashbrown 0.15.5",
1789
+
"hashbrown 0.16.1",
1790
+
"serde",
1791
+
"serde_core",
1792
+
]
1793
+
1794
+
[[package]]
1795
+
name = "indoc"
1796
+
version = "2.0.7"
1797
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1798
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1799
+
dependencies = [
1800
+
"rustversion",
1260
1801
]
1261
1802
1262
1803
[[package]]
···
1269
1810
]
1270
1811
1271
1812
[[package]]
1272
-
name = "io-uring"
1273
-
version = "0.7.9"
1813
+
name = "inventory"
1814
+
version = "0.3.21"
1274
1815
source = "registry+https://github.com/rust-lang/crates.io-index"
1275
-
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
1816
+
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
1817
+
dependencies = [
1818
+
"rustversion",
1819
+
]
1820
+
1821
+
[[package]]
1822
+
name = "ipld-core"
1823
+
version = "0.4.2"
1824
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1825
+
checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db"
1276
1826
dependencies = [
1277
-
"bitflags",
1278
-
"cfg-if",
1279
-
"libc",
1827
+
"cid",
1828
+
"serde",
1829
+
"serde_bytes",
1280
1830
]
1281
1831
1282
1832
[[package]]
1283
-
name = "itertools"
1284
-
version = "0.12.1"
1833
+
name = "ipnet"
1834
+
version = "2.11.0"
1835
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1836
+
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
1837
+
1838
+
[[package]]
1839
+
name = "iri-string"
1840
+
version = "0.7.10"
1285
1841
source = "registry+https://github.com/rust-lang/crates.io-index"
1286
-
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
1842
+
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
1287
1843
dependencies = [
1288
-
"either",
1844
+
"memchr",
1845
+
"serde",
1289
1846
]
1290
1847
1291
1848
[[package]]
1292
1849
name = "itoa"
1293
-
version = "1.0.15"
1850
+
version = "1.0.17"
1851
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1852
+
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1853
+
1854
+
[[package]]
1855
+
name = "jacquard-api"
1856
+
version = "0.9.5"
1857
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1858
+
checksum = "4979fb1848c1dd7ac8fd12745bc71f56f6da61374407d5f9b06005467a954e5a"
1859
+
dependencies = [
1860
+
"bon",
1861
+
"bytes",
1862
+
"jacquard-common",
1863
+
"jacquard-derive",
1864
+
"jacquard-lexicon",
1865
+
"miette",
1866
+
"rustversion",
1867
+
"serde",
1868
+
"serde_bytes",
1869
+
"serde_ipld_dagcbor",
1870
+
"thiserror 2.0.17",
1871
+
"unicode-segmentation",
1872
+
]
1873
+
1874
+
[[package]]
1875
+
name = "jacquard-common"
1876
+
version = "0.9.5"
1877
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1878
+
checksum = "1751921e0bdae5e0077afade6161545e9ef7698306c868f800916e99ecbcaae9"
1879
+
dependencies = [
1880
+
"base64",
1881
+
"bon",
1882
+
"bytes",
1883
+
"chrono",
1884
+
"cid",
1885
+
"getrandom 0.2.16",
1886
+
"getrandom 0.3.4",
1887
+
"http",
1888
+
"ipld-core",
1889
+
"k256",
1890
+
"langtag",
1891
+
"miette",
1892
+
"multibase",
1893
+
"multihash",
1894
+
"ouroboros",
1895
+
"p256",
1896
+
"postcard",
1897
+
"rand 0.9.2",
1898
+
"regex",
1899
+
"regex-lite",
1900
+
"reqwest",
1901
+
"serde",
1902
+
"serde_bytes",
1903
+
"serde_html_form",
1904
+
"serde_ipld_dagcbor",
1905
+
"serde_json",
1906
+
"signature",
1907
+
"smol_str",
1908
+
"thiserror 2.0.17",
1909
+
"tokio",
1910
+
"tokio-util",
1911
+
"trait-variant",
1912
+
"url",
1913
+
]
1914
+
1915
+
[[package]]
1916
+
name = "jacquard-derive"
1917
+
version = "0.9.5"
1918
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1919
+
checksum = "9c8d73dfee07943fdab93569ed1c28b06c6921ed891c08b415c4a323ff67e593"
1920
+
dependencies = [
1921
+
"heck 0.5.0",
1922
+
"jacquard-lexicon",
1923
+
"proc-macro2",
1924
+
"quote",
1925
+
"syn 2.0.112",
1926
+
]
1927
+
1928
+
[[package]]
1929
+
name = "jacquard-identity"
1930
+
version = "0.9.5"
1294
1931
source = "registry+https://github.com/rust-lang/crates.io-index"
1295
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1932
+
checksum = "e7aaefa819fa4213cf59f180dba932f018a7cd0599582fd38474ee2a38c16cf2"
1933
+
dependencies = [
1934
+
"bon",
1935
+
"bytes",
1936
+
"http",
1937
+
"jacquard-api",
1938
+
"jacquard-common",
1939
+
"jacquard-lexicon",
1940
+
"miette",
1941
+
"n0-future",
1942
+
"percent-encoding",
1943
+
"reqwest",
1944
+
"serde",
1945
+
"serde_html_form",
1946
+
"serde_json",
1947
+
"thiserror 2.0.17",
1948
+
"tokio",
1949
+
"trait-variant",
1950
+
"url",
1951
+
"urlencoding",
1952
+
]
1953
+
1954
+
[[package]]
1955
+
name = "jacquard-lexicon"
1956
+
version = "0.9.5"
1957
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1958
+
checksum = "8411aff546569b0a1e0ef669bed2380cec1c00d48f02f3fcd57a71545321b3d8"
1959
+
dependencies = [
1960
+
"cid",
1961
+
"dashmap",
1962
+
"heck 0.5.0",
1963
+
"inventory",
1964
+
"jacquard-common",
1965
+
"miette",
1966
+
"multihash",
1967
+
"prettyplease",
1968
+
"proc-macro2",
1969
+
"quote",
1970
+
"serde",
1971
+
"serde_ipld_dagcbor",
1972
+
"serde_json",
1973
+
"serde_repr",
1974
+
"serde_with",
1975
+
"sha2",
1976
+
"syn 2.0.112",
1977
+
"thiserror 2.0.17",
1978
+
"unicode-segmentation",
1979
+
]
1296
1980
1297
1981
[[package]]
1298
1982
name = "jobserver"
1299
-
version = "0.1.33"
1983
+
version = "0.1.34"
1300
1984
source = "registry+https://github.com/rust-lang/crates.io-index"
1301
-
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
1985
+
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
1302
1986
dependencies = [
1303
-
"getrandom 0.3.3",
1987
+
"getrandom 0.3.4",
1304
1988
"libc",
1989
+
]
1990
+
1991
+
[[package]]
1992
+
name = "josekit"
1993
+
version = "0.10.3"
1994
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1995
+
checksum = "a808e078330e6af222eb0044b71d4b1ff981bfef43e7bc8133a88234e0c86a0c"
1996
+
dependencies = [
1997
+
"anyhow",
1998
+
"base64",
1999
+
"flate2",
2000
+
"openssl",
2001
+
"regex",
2002
+
"serde",
2003
+
"serde_json",
2004
+
"thiserror 2.0.17",
2005
+
"time",
1305
2006
]
1306
2007
1307
2008
[[package]]
1308
2009
name = "js-sys"
1309
-
version = "0.3.77"
2010
+
version = "0.3.83"
1310
2011
source = "registry+https://github.com/rust-lang/crates.io-index"
1311
-
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
2012
+
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
1312
2013
dependencies = [
1313
2014
"once_cell",
1314
2015
"wasm-bindgen",
···
1337
2038
]
1338
2039
1339
2040
[[package]]
2041
+
name = "k256"
2042
+
version = "0.13.4"
2043
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2044
+
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
2045
+
dependencies = [
2046
+
"cfg-if",
2047
+
"ecdsa",
2048
+
"elliptic-curve",
2049
+
"sha2",
2050
+
]
2051
+
2052
+
[[package]]
2053
+
name = "langtag"
2054
+
version = "0.4.0"
2055
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2056
+
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
2057
+
dependencies = [
2058
+
"serde",
2059
+
"static-regular-grammar",
2060
+
"thiserror 1.0.69",
2061
+
]
2062
+
2063
+
[[package]]
1340
2064
name = "lazy_static"
1341
2065
version = "1.5.0"
1342
2066
source = "registry+https://github.com/rust-lang/crates.io-index"
1343
2067
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1344
2068
dependencies = [
1345
-
"spin",
2069
+
"spin 0.9.8",
1346
2070
]
1347
2071
1348
2072
[[package]]
1349
-
name = "lazycell"
1350
-
version = "1.3.0"
1351
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1352
-
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
1353
-
1354
-
[[package]]
1355
2073
name = "lettre"
1356
-
version = "0.11.18"
2074
+
version = "0.11.19"
1357
2075
source = "registry+https://github.com/rust-lang/crates.io-index"
1358
-
checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56"
2076
+
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
1359
2077
dependencies = [
1360
2078
"async-trait",
1361
2079
"base64",
···
1376
2094
"tokio",
1377
2095
"tokio-rustls",
1378
2096
"url",
1379
-
"webpki-roots 1.0.2",
2097
+
"webpki-roots 1.0.5",
1380
2098
]
1381
2099
1382
2100
[[package]]
1383
2101
name = "libc"
1384
-
version = "0.2.175"
1385
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1386
-
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
1387
-
1388
-
[[package]]
1389
-
name = "libloading"
1390
-
version = "0.8.8"
2102
+
version = "0.2.178"
1391
2103
source = "registry+https://github.com/rust-lang/crates.io-index"
1392
-
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
1393
-
dependencies = [
1394
-
"cfg-if",
1395
-
"windows-targets 0.52.6",
1396
-
]
2104
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
1397
2105
1398
2106
[[package]]
1399
2107
name = "libm"
···
1403
2111
1404
2112
[[package]]
1405
2113
name = "libredox"
1406
-
version = "0.1.9"
2114
+
version = "0.1.12"
1407
2115
source = "registry+https://github.com/rust-lang/crates.io-index"
1408
-
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
2116
+
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
1409
2117
dependencies = [
1410
2118
"bitflags",
1411
2119
"libc",
1412
-
"redox_syscall",
2120
+
"redox_syscall 0.7.0",
1413
2121
]
1414
2122
1415
2123
[[package]]
···
1424
2132
]
1425
2133
1426
2134
[[package]]
1427
-
name = "linux-raw-sys"
1428
-
version = "0.4.15"
1429
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1430
-
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
1431
-
1432
-
[[package]]
1433
2135
name = "litemap"
1434
-
version = "0.8.0"
2136
+
version = "0.8.1"
1435
2137
source = "registry+https://github.com/rust-lang/crates.io-index"
1436
-
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
2138
+
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
1437
2139
1438
2140
[[package]]
1439
2141
name = "lock_api"
1440
-
version = "0.4.13"
2142
+
version = "0.4.14"
1441
2143
source = "registry+https://github.com/rust-lang/crates.io-index"
1442
-
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
2144
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1443
2145
dependencies = [
1444
-
"autocfg",
1445
2146
"scopeguard",
1446
2147
]
1447
2148
1448
2149
[[package]]
1449
2150
name = "log"
1450
-
version = "0.4.27"
2151
+
version = "0.4.29"
1451
2152
source = "registry+https://github.com/rust-lang/crates.io-index"
1452
-
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
2153
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
2154
+
2155
+
[[package]]
2156
+
name = "loom"
2157
+
version = "0.7.2"
2158
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2159
+
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
2160
+
dependencies = [
2161
+
"cfg-if",
2162
+
"generator",
2163
+
"scoped-tls",
2164
+
"tracing",
2165
+
"tracing-subscriber",
2166
+
]
2167
+
2168
+
[[package]]
2169
+
name = "lru-slab"
2170
+
version = "0.1.2"
2171
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2172
+
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
2173
+
2174
+
[[package]]
2175
+
name = "match-lookup"
2176
+
version = "0.1.1"
2177
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2178
+
checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e"
2179
+
dependencies = [
2180
+
"proc-macro2",
2181
+
"quote",
2182
+
"syn 1.0.109",
2183
+
]
1453
2184
1454
2185
[[package]]
1455
2186
name = "matchers"
1456
-
version = "0.1.0"
2187
+
version = "0.2.0"
1457
2188
source = "registry+https://github.com/rust-lang/crates.io-index"
1458
-
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
2189
+
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
1459
2190
dependencies = [
1460
-
"regex-automata 0.1.10",
2191
+
"regex-automata",
1461
2192
]
1462
2193
1463
2194
[[package]]
···
1478
2209
1479
2210
[[package]]
1480
2211
name = "memchr"
1481
-
version = "2.7.5"
2212
+
version = "2.7.6"
1482
2213
source = "registry+https://github.com/rust-lang/crates.io-index"
1483
-
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
2214
+
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
2215
+
2216
+
[[package]]
2217
+
name = "miette"
2218
+
version = "7.6.0"
2219
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2220
+
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
2221
+
dependencies = [
2222
+
"cfg-if",
2223
+
"miette-derive",
2224
+
"unicode-width",
2225
+
]
2226
+
2227
+
[[package]]
2228
+
name = "miette-derive"
2229
+
version = "7.6.0"
2230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2231
+
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
2232
+
dependencies = [
2233
+
"proc-macro2",
2234
+
"quote",
2235
+
"syn 2.0.112",
2236
+
]
1484
2237
1485
2238
[[package]]
1486
2239
name = "mime"
···
1501
2254
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1502
2255
dependencies = [
1503
2256
"adler2",
2257
+
"simd-adler32",
1504
2258
]
1505
2259
1506
2260
[[package]]
1507
2261
name = "mio"
1508
-
version = "1.0.4"
2262
+
version = "1.1.1"
1509
2263
source = "registry+https://github.com/rust-lang/crates.io-index"
1510
-
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
2264
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
1511
2265
dependencies = [
1512
2266
"libc",
1513
-
"wasi 0.11.1+wasi-snapshot-preview1",
1514
-
"windows-sys 0.59.0",
2267
+
"wasi",
2268
+
"windows-sys 0.61.2",
2269
+
]
2270
+
2271
+
[[package]]
2272
+
name = "multibase"
2273
+
version = "0.9.2"
2274
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2275
+
checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77"
2276
+
dependencies = [
2277
+
"base-x",
2278
+
"base256emoji",
2279
+
"data-encoding",
2280
+
"data-encoding-macro",
2281
+
]
2282
+
2283
+
[[package]]
2284
+
name = "multihash"
2285
+
version = "0.19.3"
2286
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2287
+
checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d"
2288
+
dependencies = [
2289
+
"core2",
2290
+
"serde",
2291
+
"unsigned-varint",
2292
+
]
2293
+
2294
+
[[package]]
2295
+
name = "n0-future"
2296
+
version = "0.1.3"
2297
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2298
+
checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794"
2299
+
dependencies = [
2300
+
"cfg_aliases",
2301
+
"derive_more",
2302
+
"futures-buffered",
2303
+
"futures-lite",
2304
+
"futures-util",
2305
+
"js-sys",
2306
+
"pin-project",
2307
+
"send_wrapper",
2308
+
"tokio",
2309
+
"tokio-util",
2310
+
"wasm-bindgen",
2311
+
"wasm-bindgen-futures",
2312
+
"web-time",
1515
2313
]
1516
2314
1517
2315
[[package]]
···
1547
2345
1548
2346
[[package]]
1549
2347
name = "nu-ansi-term"
1550
-
version = "0.46.0"
2348
+
version = "0.50.3"
1551
2349
source = "registry+https://github.com/rust-lang/crates.io-index"
1552
-
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
2350
+
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
1553
2351
dependencies = [
1554
-
"overload",
1555
-
"winapi",
2352
+
"windows-sys 0.59.0",
1556
2353
]
1557
2354
1558
2355
[[package]]
1559
2356
name = "num-bigint-dig"
1560
-
version = "0.8.4"
2357
+
version = "0.8.6"
1561
2358
source = "registry+https://github.com/rust-lang/crates.io-index"
1562
-
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
2359
+
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
1563
2360
dependencies = [
1564
-
"byteorder",
1565
2361
"lazy_static",
1566
2362
"libm",
1567
2363
"num-integer",
···
1571
2367
"smallvec",
1572
2368
"zeroize",
1573
2369
]
2370
+
2371
+
[[package]]
2372
+
name = "num-conv"
2373
+
version = "0.1.0"
2374
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2375
+
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
1574
2376
1575
2377
[[package]]
1576
2378
name = "num-integer"
···
1619
2421
1620
2422
[[package]]
1621
2423
name = "object"
1622
-
version = "0.36.7"
2424
+
version = "0.32.2"
1623
2425
source = "registry+https://github.com/rust-lang/crates.io-index"
1624
-
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
2426
+
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
1625
2427
dependencies = [
1626
2428
"memchr",
1627
2429
]
···
1633
2435
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1634
2436
1635
2437
[[package]]
1636
-
name = "overload"
2438
+
name = "openssl"
2439
+
version = "0.10.75"
2440
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2441
+
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
2442
+
dependencies = [
2443
+
"bitflags",
2444
+
"cfg-if",
2445
+
"foreign-types",
2446
+
"libc",
2447
+
"once_cell",
2448
+
"openssl-macros",
2449
+
"openssl-sys",
2450
+
]
2451
+
2452
+
[[package]]
2453
+
name = "openssl-macros"
1637
2454
version = "0.1.1"
1638
2455
source = "registry+https://github.com/rust-lang/crates.io-index"
1639
-
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
2456
+
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
2457
+
dependencies = [
2458
+
"proc-macro2",
2459
+
"quote",
2460
+
"syn 2.0.112",
2461
+
]
2462
+
2463
+
[[package]]
2464
+
name = "openssl-sys"
2465
+
version = "0.9.111"
2466
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2467
+
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
2468
+
dependencies = [
2469
+
"cc",
2470
+
"libc",
2471
+
"pkg-config",
2472
+
"vcpkg",
2473
+
]
2474
+
2475
+
[[package]]
2476
+
name = "ouroboros"
2477
+
version = "0.18.5"
2478
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2479
+
checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
2480
+
dependencies = [
2481
+
"aliasable",
2482
+
"ouroboros_macro",
2483
+
"static_assertions",
2484
+
]
2485
+
2486
+
[[package]]
2487
+
name = "ouroboros_macro"
2488
+
version = "0.18.5"
2489
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2490
+
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
2491
+
dependencies = [
2492
+
"heck 0.4.1",
2493
+
"proc-macro2",
2494
+
"proc-macro2-diagnostics",
2495
+
"quote",
2496
+
"syn 2.0.112",
2497
+
]
2498
+
2499
+
[[package]]
2500
+
name = "p256"
2501
+
version = "0.13.2"
2502
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2503
+
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
2504
+
dependencies = [
2505
+
"ecdsa",
2506
+
"elliptic-curve",
2507
+
"primeorder",
2508
+
"sha2",
2509
+
]
1640
2510
1641
2511
[[package]]
1642
2512
name = "parking"
···
1646
2516
1647
2517
[[package]]
1648
2518
name = "parking_lot"
1649
-
version = "0.12.4"
2519
+
version = "0.12.5"
1650
2520
source = "registry+https://github.com/rust-lang/crates.io-index"
1651
-
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
2521
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1652
2522
dependencies = [
1653
2523
"lock_api",
1654
2524
"parking_lot_core",
···
1656
2526
1657
2527
[[package]]
1658
2528
name = "parking_lot_core"
1659
-
version = "0.9.11"
2529
+
version = "0.9.12"
1660
2530
source = "registry+https://github.com/rust-lang/crates.io-index"
1661
-
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
2531
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1662
2532
dependencies = [
1663
2533
"cfg-if",
1664
2534
"libc",
1665
-
"redox_syscall",
2535
+
"redox_syscall 0.5.18",
1666
2536
"smallvec",
1667
-
"windows-targets 0.52.6",
2537
+
"windows-link",
1668
2538
]
1669
2539
1670
2540
[[package]]
···
1697
2567
"axum",
1698
2568
"axum-template",
1699
2569
"chrono",
2570
+
"dashmap",
1700
2571
"dotenvy",
1701
2572
"handlebars",
1702
2573
"hex",
2574
+
"html-escape",
1703
2575
"hyper-util",
2576
+
"jacquard-common",
2577
+
"jacquard-identity",
2578
+
"josekit",
1704
2579
"jwt-compact",
1705
2580
"lettre",
2581
+
"multibase",
1706
2582
"rand 0.9.2",
2583
+
"reqwest",
1707
2584
"rust-embed",
1708
2585
"rustls",
1709
2586
"scrypt",
···
1712
2589
"sha2",
1713
2590
"sqlx",
1714
2591
"tokio",
2592
+
"tower",
1715
2593
"tower-http",
1716
2594
"tower_governor",
1717
2595
"tracing",
1718
2596
"tracing-subscriber",
2597
+
"urlencoding",
1719
2598
]
1720
2599
1721
2600
[[package]]
···
1729
2608
1730
2609
[[package]]
1731
2610
name = "percent-encoding"
1732
-
version = "2.3.1"
2611
+
version = "2.3.2"
1733
2612
source = "registry+https://github.com/rust-lang/crates.io-index"
1734
-
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
2613
+
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1735
2614
1736
2615
[[package]]
1737
2616
name = "pest"
1738
-
version = "2.8.1"
2617
+
version = "2.8.4"
1739
2618
source = "registry+https://github.com/rust-lang/crates.io-index"
1740
-
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
2619
+
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
1741
2620
dependencies = [
1742
2621
"memchr",
1743
-
"thiserror 2.0.14",
1744
2622
"ucd-trie",
1745
2623
]
1746
2624
1747
2625
[[package]]
1748
2626
name = "pest_derive"
1749
-
version = "2.8.1"
2627
+
version = "2.8.4"
1750
2628
source = "registry+https://github.com/rust-lang/crates.io-index"
1751
-
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
2629
+
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
1752
2630
dependencies = [
1753
2631
"pest",
1754
2632
"pest_generator",
···
1756
2634
1757
2635
[[package]]
1758
2636
name = "pest_generator"
1759
-
version = "2.8.1"
2637
+
version = "2.8.4"
1760
2638
source = "registry+https://github.com/rust-lang/crates.io-index"
1761
-
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
2639
+
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
1762
2640
dependencies = [
1763
2641
"pest",
1764
2642
"pest_meta",
1765
2643
"proc-macro2",
1766
2644
"quote",
1767
-
"syn",
2645
+
"syn 2.0.112",
1768
2646
]
1769
2647
1770
2648
[[package]]
1771
2649
name = "pest_meta"
1772
-
version = "2.8.1"
2650
+
version = "2.8.4"
1773
2651
source = "registry+https://github.com/rust-lang/crates.io-index"
1774
-
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
2652
+
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
1775
2653
dependencies = [
1776
2654
"pest",
1777
2655
"sha2",
···
1794
2672
dependencies = [
1795
2673
"proc-macro2",
1796
2674
"quote",
1797
-
"syn",
2675
+
"syn 2.0.112",
1798
2676
]
1799
2677
1800
2678
[[package]]
···
1838
2716
1839
2717
[[package]]
1840
2718
name = "portable-atomic"
1841
-
version = "1.11.1"
2719
+
version = "1.13.0"
1842
2720
source = "registry+https://github.com/rust-lang/crates.io-index"
1843
-
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2721
+
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
2722
+
2723
+
[[package]]
2724
+
name = "postcard"
2725
+
version = "1.1.3"
2726
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2727
+
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
2728
+
dependencies = [
2729
+
"cobs",
2730
+
"embedded-io 0.4.0",
2731
+
"embedded-io 0.6.1",
2732
+
"heapless",
2733
+
"serde",
2734
+
]
1844
2735
1845
2736
[[package]]
1846
2737
name = "potential_utf"
1847
-
version = "0.1.2"
2738
+
version = "0.1.4"
1848
2739
source = "registry+https://github.com/rust-lang/crates.io-index"
1849
-
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
2740
+
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
1850
2741
dependencies = [
1851
2742
"zerovec",
1852
2743
]
2744
+
2745
+
[[package]]
2746
+
name = "powerfmt"
2747
+
version = "0.2.0"
2748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2749
+
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
1853
2750
1854
2751
[[package]]
1855
2752
name = "ppv-lite86"
···
1862
2759
1863
2760
[[package]]
1864
2761
name = "prettyplease"
1865
-
version = "0.2.35"
2762
+
version = "0.2.37"
2763
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2764
+
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2765
+
dependencies = [
2766
+
"proc-macro2",
2767
+
"syn 2.0.112",
2768
+
]
2769
+
2770
+
[[package]]
2771
+
name = "primeorder"
2772
+
version = "0.13.6"
2773
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2774
+
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2775
+
dependencies = [
2776
+
"elliptic-curve",
2777
+
]
2778
+
2779
+
[[package]]
2780
+
name = "proc-macro-error"
2781
+
version = "1.0.4"
2782
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2783
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2784
+
dependencies = [
2785
+
"proc-macro-error-attr",
2786
+
"proc-macro2",
2787
+
"quote",
2788
+
"syn 1.0.109",
2789
+
"version_check",
2790
+
]
2791
+
2792
+
[[package]]
2793
+
name = "proc-macro-error-attr"
2794
+
version = "1.0.4"
1866
2795
source = "registry+https://github.com/rust-lang/crates.io-index"
1867
-
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
2796
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
1868
2797
dependencies = [
1869
2798
"proc-macro2",
1870
-
"syn",
2799
+
"quote",
2800
+
"version_check",
1871
2801
]
1872
2802
1873
2803
[[package]]
1874
2804
name = "proc-macro2"
1875
-
version = "1.0.97"
2805
+
version = "1.0.104"
1876
2806
source = "registry+https://github.com/rust-lang/crates.io-index"
1877
-
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
2807
+
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
1878
2808
dependencies = [
1879
2809
"unicode-ident",
1880
2810
]
1881
2811
1882
2812
[[package]]
2813
+
name = "proc-macro2-diagnostics"
2814
+
version = "0.10.1"
2815
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2816
+
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
2817
+
dependencies = [
2818
+
"proc-macro2",
2819
+
"quote",
2820
+
"syn 2.0.112",
2821
+
"version_check",
2822
+
"yansi",
2823
+
]
2824
+
2825
+
[[package]]
1883
2826
name = "psm"
1884
-
version = "0.1.26"
2827
+
version = "0.1.28"
1885
2828
source = "registry+https://github.com/rust-lang/crates.io-index"
1886
-
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
2829
+
checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01"
1887
2830
dependencies = [
2831
+
"ar_archive_writer",
1888
2832
"cc",
1889
2833
]
1890
2834
···
1898
2842
"libc",
1899
2843
"once_cell",
1900
2844
"raw-cpuid",
1901
-
"wasi 0.11.1+wasi-snapshot-preview1",
2845
+
"wasi",
1902
2846
"web-sys",
1903
2847
"winapi",
1904
2848
]
1905
2849
1906
2850
[[package]]
2851
+
name = "quinn"
2852
+
version = "0.11.9"
2853
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2854
+
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
2855
+
dependencies = [
2856
+
"bytes",
2857
+
"cfg_aliases",
2858
+
"pin-project-lite",
2859
+
"quinn-proto",
2860
+
"quinn-udp",
2861
+
"rustc-hash",
2862
+
"rustls",
2863
+
"socket2",
2864
+
"thiserror 2.0.17",
2865
+
"tokio",
2866
+
"tracing",
2867
+
"web-time",
2868
+
]
2869
+
2870
+
[[package]]
2871
+
name = "quinn-proto"
2872
+
version = "0.11.13"
2873
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2874
+
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
2875
+
dependencies = [
2876
+
"bytes",
2877
+
"getrandom 0.3.4",
2878
+
"lru-slab",
2879
+
"rand 0.9.2",
2880
+
"ring",
2881
+
"rustc-hash",
2882
+
"rustls",
2883
+
"rustls-pki-types",
2884
+
"slab",
2885
+
"thiserror 2.0.17",
2886
+
"tinyvec",
2887
+
"tracing",
2888
+
"web-time",
2889
+
]
2890
+
2891
+
[[package]]
2892
+
name = "quinn-udp"
2893
+
version = "0.5.14"
2894
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2895
+
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
2896
+
dependencies = [
2897
+
"cfg_aliases",
2898
+
"libc",
2899
+
"once_cell",
2900
+
"socket2",
2901
+
"tracing",
2902
+
"windows-sys 0.59.0",
2903
+
]
2904
+
2905
+
[[package]]
1907
2906
name = "quote"
1908
-
version = "1.0.40"
2907
+
version = "1.0.42"
1909
2908
source = "registry+https://github.com/rust-lang/crates.io-index"
1910
-
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
2909
+
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
1911
2910
dependencies = [
1912
2911
"proc-macro2",
1913
2912
]
···
1980
2979
source = "registry+https://github.com/rust-lang/crates.io-index"
1981
2980
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
1982
2981
dependencies = [
1983
-
"getrandom 0.3.3",
2982
+
"getrandom 0.3.4",
1984
2983
]
1985
2984
1986
2985
[[package]]
2986
+
name = "range-traits"
2987
+
version = "0.3.2"
2988
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2989
+
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
2990
+
2991
+
[[package]]
1987
2992
name = "raw-cpuid"
1988
-
version = "11.5.0"
2993
+
version = "11.6.0"
1989
2994
source = "registry+https://github.com/rust-lang/crates.io-index"
1990
-
checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146"
2995
+
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
1991
2996
dependencies = [
1992
2997
"bitflags",
1993
2998
]
1994
2999
1995
3000
[[package]]
1996
3001
name = "redox_syscall"
1997
-
version = "0.5.17"
3002
+
version = "0.5.18"
1998
3003
source = "registry+https://github.com/rust-lang/crates.io-index"
1999
-
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
3004
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3005
+
dependencies = [
3006
+
"bitflags",
3007
+
]
3008
+
3009
+
[[package]]
3010
+
name = "redox_syscall"
3011
+
version = "0.7.0"
3012
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3013
+
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
2000
3014
dependencies = [
2001
3015
"bitflags",
2002
3016
]
2003
3017
2004
3018
[[package]]
2005
-
name = "regex"
2006
-
version = "1.11.1"
3019
+
name = "ref-cast"
3020
+
version = "1.0.25"
2007
3021
source = "registry+https://github.com/rust-lang/crates.io-index"
2008
-
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
3022
+
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
2009
3023
dependencies = [
2010
-
"aho-corasick",
2011
-
"memchr",
2012
-
"regex-automata 0.4.9",
2013
-
"regex-syntax 0.8.5",
3024
+
"ref-cast-impl",
3025
+
]
3026
+
3027
+
[[package]]
3028
+
name = "ref-cast-impl"
3029
+
version = "1.0.25"
3030
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3031
+
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
3032
+
dependencies = [
3033
+
"proc-macro2",
3034
+
"quote",
3035
+
"syn 2.0.112",
2014
3036
]
2015
3037
2016
3038
[[package]]
2017
-
name = "regex-automata"
2018
-
version = "0.1.10"
3039
+
name = "regex"
3040
+
version = "1.12.2"
2019
3041
source = "registry+https://github.com/rust-lang/crates.io-index"
2020
-
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
3042
+
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
2021
3043
dependencies = [
2022
-
"regex-syntax 0.6.29",
3044
+
"aho-corasick",
3045
+
"memchr",
3046
+
"regex-automata",
3047
+
"regex-syntax",
2023
3048
]
2024
3049
2025
3050
[[package]]
2026
3051
name = "regex-automata"
2027
-
version = "0.4.9"
3052
+
version = "0.4.13"
2028
3053
source = "registry+https://github.com/rust-lang/crates.io-index"
2029
-
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
3054
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
2030
3055
dependencies = [
2031
3056
"aho-corasick",
2032
3057
"memchr",
2033
-
"regex-syntax 0.8.5",
3058
+
"regex-syntax",
2034
3059
]
2035
3060
2036
3061
[[package]]
2037
-
name = "regex-syntax"
2038
-
version = "0.6.29"
3062
+
name = "regex-lite"
3063
+
version = "0.1.8"
2039
3064
source = "registry+https://github.com/rust-lang/crates.io-index"
2040
-
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
3065
+
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
2041
3066
2042
3067
[[package]]
2043
3068
name = "regex-syntax"
2044
-
version = "0.8.5"
3069
+
version = "0.8.8"
2045
3070
source = "registry+https://github.com/rust-lang/crates.io-index"
2046
-
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
3071
+
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
3072
+
3073
+
[[package]]
3074
+
name = "reqwest"
3075
+
version = "0.12.28"
3076
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3077
+
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
3078
+
dependencies = [
3079
+
"base64",
3080
+
"bytes",
3081
+
"encoding_rs",
3082
+
"futures-core",
3083
+
"h2",
3084
+
"http",
3085
+
"http-body",
3086
+
"http-body-util",
3087
+
"hyper",
3088
+
"hyper-rustls",
3089
+
"hyper-util",
3090
+
"js-sys",
3091
+
"log",
3092
+
"mime",
3093
+
"percent-encoding",
3094
+
"pin-project-lite",
3095
+
"quinn",
3096
+
"rustls",
3097
+
"rustls-pki-types",
3098
+
"serde",
3099
+
"serde_json",
3100
+
"serde_urlencoded",
3101
+
"sync_wrapper",
3102
+
"tokio",
3103
+
"tokio-rustls",
3104
+
"tower",
3105
+
"tower-http",
3106
+
"tower-service",
3107
+
"url",
3108
+
"wasm-bindgen",
3109
+
"wasm-bindgen-futures",
3110
+
"web-sys",
3111
+
"webpki-roots 1.0.5",
3112
+
]
3113
+
3114
+
[[package]]
3115
+
name = "rfc6979"
3116
+
version = "0.4.0"
3117
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3118
+
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
3119
+
dependencies = [
3120
+
"hmac",
3121
+
"subtle",
3122
+
]
2047
3123
2048
3124
[[package]]
2049
3125
name = "ring"
···
2061
3137
2062
3138
[[package]]
2063
3139
name = "rsa"
2064
-
version = "0.9.8"
3140
+
version = "0.9.9"
2065
3141
source = "registry+https://github.com/rust-lang/crates.io-index"
2066
-
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
3142
+
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
2067
3143
dependencies = [
2068
3144
"const-oid",
2069
3145
"digest",
···
2081
3157
2082
3158
[[package]]
2083
3159
name = "rust-embed"
2084
-
version = "8.7.2"
3160
+
version = "8.9.0"
2085
3161
source = "registry+https://github.com/rust-lang/crates.io-index"
2086
-
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
3162
+
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
2087
3163
dependencies = [
2088
3164
"rust-embed-impl",
2089
3165
"rust-embed-utils",
···
2092
3168
2093
3169
[[package]]
2094
3170
name = "rust-embed-impl"
2095
-
version = "8.7.2"
3171
+
version = "8.9.0"
2096
3172
source = "registry+https://github.com/rust-lang/crates.io-index"
2097
-
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
3173
+
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
2098
3174
dependencies = [
2099
3175
"proc-macro2",
2100
3176
"quote",
2101
3177
"rust-embed-utils",
2102
-
"syn",
3178
+
"syn 2.0.112",
2103
3179
"walkdir",
2104
3180
]
2105
3181
2106
3182
[[package]]
2107
3183
name = "rust-embed-utils"
2108
-
version = "8.7.2"
3184
+
version = "8.9.0"
2109
3185
source = "registry+https://github.com/rust-lang/crates.io-index"
2110
-
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
3186
+
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
2111
3187
dependencies = [
2112
3188
"globset",
2113
3189
"sha2",
···
2115
3191
]
2116
3192
2117
3193
[[package]]
2118
-
name = "rustc-demangle"
2119
-
version = "0.1.26"
2120
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2121
-
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
2122
-
2123
-
[[package]]
2124
3194
name = "rustc-hash"
2125
-
version = "1.1.0"
3195
+
version = "2.1.1"
2126
3196
source = "registry+https://github.com/rust-lang/crates.io-index"
2127
-
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
3197
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
2128
3198
2129
3199
[[package]]
2130
-
name = "rustix"
2131
-
version = "0.38.44"
3200
+
name = "rustc_version"
3201
+
version = "0.4.1"
2132
3202
source = "registry+https://github.com/rust-lang/crates.io-index"
2133
-
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
3203
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2134
3204
dependencies = [
2135
-
"bitflags",
2136
-
"errno",
2137
-
"libc",
2138
-
"linux-raw-sys",
2139
-
"windows-sys 0.59.0",
3205
+
"semver",
2140
3206
]
2141
3207
2142
3208
[[package]]
2143
3209
name = "rustls"
2144
-
version = "0.23.31"
3210
+
version = "0.23.35"
2145
3211
source = "registry+https://github.com/rust-lang/crates.io-index"
2146
-
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
3212
+
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
2147
3213
dependencies = [
2148
3214
"aws-lc-rs",
2149
3215
"log",
···
2157
3223
2158
3224
[[package]]
2159
3225
name = "rustls-pki-types"
2160
-
version = "1.12.0"
3226
+
version = "1.13.2"
2161
3227
source = "registry+https://github.com/rust-lang/crates.io-index"
2162
-
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
3228
+
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
2163
3229
dependencies = [
3230
+
"web-time",
2164
3231
"zeroize",
2165
3232
]
2166
3233
2167
3234
[[package]]
2168
3235
name = "rustls-webpki"
2169
-
version = "0.103.4"
3236
+
version = "0.103.8"
2170
3237
source = "registry+https://github.com/rust-lang/crates.io-index"
2171
-
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
3238
+
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
2172
3239
dependencies = [
2173
3240
"aws-lc-rs",
2174
3241
"ring",
···
2184
3251
2185
3252
[[package]]
2186
3253
name = "ryu"
2187
-
version = "1.0.20"
3254
+
version = "1.0.22"
2188
3255
source = "registry+https://github.com/rust-lang/crates.io-index"
2189
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
3256
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
2190
3257
2191
3258
[[package]]
2192
3259
name = "salsa20"
···
2207
3274
]
2208
3275
2209
3276
[[package]]
3277
+
name = "schemars"
3278
+
version = "0.9.0"
3279
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3280
+
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
3281
+
dependencies = [
3282
+
"dyn-clone",
3283
+
"ref-cast",
3284
+
"serde",
3285
+
"serde_json",
3286
+
]
3287
+
3288
+
[[package]]
3289
+
name = "schemars"
3290
+
version = "1.2.0"
3291
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3292
+
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
3293
+
dependencies = [
3294
+
"dyn-clone",
3295
+
"ref-cast",
3296
+
"serde",
3297
+
"serde_json",
3298
+
]
3299
+
3300
+
[[package]]
3301
+
name = "scoped-tls"
3302
+
version = "1.0.1"
3303
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3304
+
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
3305
+
3306
+
[[package]]
2210
3307
name = "scopeguard"
2211
3308
version = "1.2.0"
2212
3309
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2225
3322
]
2226
3323
2227
3324
[[package]]
3325
+
name = "sec1"
3326
+
version = "0.7.3"
3327
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3328
+
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
3329
+
dependencies = [
3330
+
"base16ct",
3331
+
"der",
3332
+
"generic-array",
3333
+
"pkcs8",
3334
+
"subtle",
3335
+
"zeroize",
3336
+
]
3337
+
3338
+
[[package]]
2228
3339
name = "secp256k1"
2229
3340
version = "0.28.2"
2230
3341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2243
3354
]
2244
3355
2245
3356
[[package]]
3357
+
name = "semver"
3358
+
version = "1.0.27"
3359
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3360
+
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
3361
+
3362
+
[[package]]
3363
+
name = "send_wrapper"
3364
+
version = "0.6.0"
3365
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3366
+
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
3367
+
3368
+
[[package]]
2246
3369
name = "serde"
2247
-
version = "1.0.219"
3370
+
version = "1.0.228"
2248
3371
source = "registry+https://github.com/rust-lang/crates.io-index"
2249
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3372
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
3373
+
dependencies = [
3374
+
"serde_core",
3375
+
"serde_derive",
3376
+
]
3377
+
3378
+
[[package]]
3379
+
name = "serde_bytes"
3380
+
version = "0.11.19"
3381
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3382
+
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
3383
+
dependencies = [
3384
+
"serde",
3385
+
"serde_core",
3386
+
]
3387
+
3388
+
[[package]]
3389
+
name = "serde_core"
3390
+
version = "1.0.228"
3391
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3392
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2250
3393
dependencies = [
2251
3394
"serde_derive",
2252
3395
]
2253
3396
2254
3397
[[package]]
2255
3398
name = "serde_derive"
2256
-
version = "1.0.219"
3399
+
version = "1.0.228"
2257
3400
source = "registry+https://github.com/rust-lang/crates.io-index"
2258
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3401
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
2259
3402
dependencies = [
2260
3403
"proc-macro2",
2261
3404
"quote",
2262
-
"syn",
3405
+
"syn 2.0.112",
3406
+
]
3407
+
3408
+
[[package]]
3409
+
name = "serde_html_form"
3410
+
version = "0.2.8"
3411
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3412
+
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
3413
+
dependencies = [
3414
+
"form_urlencoded",
3415
+
"indexmap 2.12.1",
3416
+
"itoa",
3417
+
"ryu",
3418
+
"serde_core",
3419
+
]
3420
+
3421
+
[[package]]
3422
+
name = "serde_ipld_dagcbor"
3423
+
version = "0.6.4"
3424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3425
+
checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778"
3426
+
dependencies = [
3427
+
"cbor4ii",
3428
+
"ipld-core",
3429
+
"scopeguard",
3430
+
"serde",
2263
3431
]
2264
3432
2265
3433
[[package]]
2266
3434
name = "serde_json"
2267
-
version = "1.0.142"
3435
+
version = "1.0.148"
2268
3436
source = "registry+https://github.com/rust-lang/crates.io-index"
2269
-
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
3437
+
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
2270
3438
dependencies = [
3439
+
"indexmap 2.12.1",
2271
3440
"itoa",
2272
3441
"memchr",
2273
-
"ryu",
2274
3442
"serde",
3443
+
"serde_core",
3444
+
"zmij",
2275
3445
]
2276
3446
2277
3447
[[package]]
2278
3448
name = "serde_path_to_error"
2279
-
version = "0.1.17"
3449
+
version = "0.1.20"
2280
3450
source = "registry+https://github.com/rust-lang/crates.io-index"
2281
-
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
3451
+
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
2282
3452
dependencies = [
2283
3453
"itoa",
2284
3454
"serde",
3455
+
"serde_core",
3456
+
]
3457
+
3458
+
[[package]]
3459
+
name = "serde_repr"
3460
+
version = "0.1.20"
3461
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3462
+
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
3463
+
dependencies = [
3464
+
"proc-macro2",
3465
+
"quote",
3466
+
"syn 2.0.112",
2285
3467
]
2286
3468
2287
3469
[[package]]
···
2297
3479
]
2298
3480
2299
3481
[[package]]
3482
+
name = "serde_with"
3483
+
version = "3.16.1"
3484
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3485
+
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
3486
+
dependencies = [
3487
+
"base64",
3488
+
"chrono",
3489
+
"hex",
3490
+
"indexmap 1.9.3",
3491
+
"indexmap 2.12.1",
3492
+
"schemars 0.9.0",
3493
+
"schemars 1.2.0",
3494
+
"serde_core",
3495
+
"serde_json",
3496
+
"serde_with_macros",
3497
+
"time",
3498
+
]
3499
+
3500
+
[[package]]
3501
+
name = "serde_with_macros"
3502
+
version = "3.16.1"
3503
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3504
+
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
3505
+
dependencies = [
3506
+
"darling 0.21.3",
3507
+
"proc-macro2",
3508
+
"quote",
3509
+
"syn 2.0.112",
3510
+
]
3511
+
3512
+
[[package]]
2300
3513
name = "sha1"
2301
3514
version = "0.10.6"
2302
3515
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2335
3548
2336
3549
[[package]]
2337
3550
name = "signal-hook-registry"
2338
-
version = "1.4.6"
3551
+
version = "1.4.8"
2339
3552
source = "registry+https://github.com/rust-lang/crates.io-index"
2340
-
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
3553
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
2341
3554
dependencies = [
3555
+
"errno",
2342
3556
"libc",
2343
3557
]
2344
3558
···
2353
3567
]
2354
3568
2355
3569
[[package]]
3570
+
name = "simd-adler32"
3571
+
version = "0.3.8"
3572
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3573
+
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
3574
+
3575
+
[[package]]
2356
3576
name = "slab"
2357
3577
version = "0.4.11"
2358
3578
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2368
3588
]
2369
3589
2370
3590
[[package]]
3591
+
name = "smol_str"
3592
+
version = "0.3.4"
3593
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3594
+
checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5"
3595
+
dependencies = [
3596
+
"borsh",
3597
+
"serde_core",
3598
+
]
3599
+
3600
+
[[package]]
2371
3601
name = "socket2"
2372
-
version = "0.6.0"
3602
+
version = "0.6.1"
2373
3603
source = "registry+https://github.com/rust-lang/crates.io-index"
2374
-
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
3604
+
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
2375
3605
dependencies = [
2376
3606
"libc",
2377
-
"windows-sys 0.59.0",
3607
+
"windows-sys 0.60.2",
2378
3608
]
2379
3609
2380
3610
[[package]]
···
2387
3617
]
2388
3618
2389
3619
[[package]]
3620
+
name = "spin"
3621
+
version = "0.10.0"
3622
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3623
+
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
3624
+
3625
+
[[package]]
2390
3626
name = "spinning_top"
2391
3627
version = "0.3.0"
2392
3628
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2437
3673
"futures-util",
2438
3674
"hashbrown 0.15.5",
2439
3675
"hashlink",
2440
-
"indexmap",
3676
+
"indexmap 2.12.1",
2441
3677
"log",
2442
3678
"memchr",
2443
3679
"once_cell",
···
2447
3683
"serde_json",
2448
3684
"sha2",
2449
3685
"smallvec",
2450
-
"thiserror 2.0.14",
3686
+
"thiserror 2.0.17",
2451
3687
"tokio",
2452
3688
"tokio-stream",
2453
3689
"tracing",
···
2465
3701
"quote",
2466
3702
"sqlx-core",
2467
3703
"sqlx-macros-core",
2468
-
"syn",
3704
+
"syn 2.0.112",
2469
3705
]
2470
3706
2471
3707
[[package]]
···
2476
3712
dependencies = [
2477
3713
"dotenvy",
2478
3714
"either",
2479
-
"heck",
3715
+
"heck 0.5.0",
2480
3716
"hex",
2481
3717
"once_cell",
2482
3718
"proc-macro2",
···
2488
3724
"sqlx-mysql",
2489
3725
"sqlx-postgres",
2490
3726
"sqlx-sqlite",
2491
-
"syn",
3727
+
"syn 2.0.112",
2492
3728
"tokio",
2493
3729
"url",
2494
3730
]
···
2531
3767
"smallvec",
2532
3768
"sqlx-core",
2533
3769
"stringprep",
2534
-
"thiserror 2.0.14",
3770
+
"thiserror 2.0.17",
2535
3771
"tracing",
2536
3772
"whoami",
2537
3773
]
···
2569
3805
"smallvec",
2570
3806
"sqlx-core",
2571
3807
"stringprep",
2572
-
"thiserror 2.0.14",
3808
+
"thiserror 2.0.17",
2573
3809
"tracing",
2574
3810
"whoami",
2575
3811
]
···
2594
3830
"serde",
2595
3831
"serde_urlencoded",
2596
3832
"sqlx-core",
2597
-
"thiserror 2.0.14",
3833
+
"thiserror 2.0.17",
2598
3834
"tracing",
2599
3835
"url",
2600
3836
]
2601
3837
2602
3838
[[package]]
2603
3839
name = "stable_deref_trait"
2604
-
version = "1.2.0"
3840
+
version = "1.2.1"
2605
3841
source = "registry+https://github.com/rust-lang/crates.io-index"
2606
-
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
3842
+
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
2607
3843
2608
3844
[[package]]
2609
3845
name = "stacker"
2610
-
version = "0.1.21"
3846
+
version = "0.1.22"
2611
3847
source = "registry+https://github.com/rust-lang/crates.io-index"
2612
-
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
3848
+
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
2613
3849
dependencies = [
2614
3850
"cc",
2615
3851
"cfg-if",
···
2619
3855
]
2620
3856
2621
3857
[[package]]
3858
+
name = "static-regular-grammar"
3859
+
version = "2.0.2"
3860
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3861
+
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
3862
+
dependencies = [
3863
+
"abnf",
3864
+
"btree-range-map",
3865
+
"ciborium",
3866
+
"hex_fmt",
3867
+
"indoc",
3868
+
"proc-macro-error",
3869
+
"proc-macro2",
3870
+
"quote",
3871
+
"serde",
3872
+
"sha2",
3873
+
"syn 2.0.112",
3874
+
"thiserror 1.0.69",
3875
+
]
3876
+
3877
+
[[package]]
3878
+
name = "static_assertions"
3879
+
version = "1.1.0"
3880
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3881
+
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
3882
+
3883
+
[[package]]
2622
3884
name = "stringprep"
2623
3885
version = "0.1.5"
2624
3886
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2643
3905
2644
3906
[[package]]
2645
3907
name = "syn"
2646
-
version = "2.0.105"
3908
+
version = "1.0.109"
3909
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3910
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
3911
+
dependencies = [
3912
+
"proc-macro2",
3913
+
"quote",
3914
+
"unicode-ident",
3915
+
]
3916
+
3917
+
[[package]]
3918
+
name = "syn"
3919
+
version = "2.0.112"
2647
3920
source = "registry+https://github.com/rust-lang/crates.io-index"
2648
-
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
3921
+
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
2649
3922
dependencies = [
2650
3923
"proc-macro2",
2651
3924
"quote",
···
2657
3930
version = "1.0.2"
2658
3931
source = "registry+https://github.com/rust-lang/crates.io-index"
2659
3932
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3933
+
dependencies = [
3934
+
"futures-core",
3935
+
]
2660
3936
2661
3937
[[package]]
2662
3938
name = "synstructure"
···
2666
3942
dependencies = [
2667
3943
"proc-macro2",
2668
3944
"quote",
2669
-
"syn",
3945
+
"syn 2.0.112",
3946
+
]
3947
+
3948
+
[[package]]
3949
+
name = "system-configuration"
3950
+
version = "0.6.1"
3951
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3952
+
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
3953
+
dependencies = [
3954
+
"bitflags",
3955
+
"core-foundation",
3956
+
"system-configuration-sys",
3957
+
]
3958
+
3959
+
[[package]]
3960
+
name = "system-configuration-sys"
3961
+
version = "0.6.0"
3962
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3963
+
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
3964
+
dependencies = [
3965
+
"core-foundation-sys",
3966
+
"libc",
2670
3967
]
2671
3968
2672
3969
[[package]]
···
2680
3977
2681
3978
[[package]]
2682
3979
name = "thiserror"
2683
-
version = "2.0.14"
3980
+
version = "2.0.17"
2684
3981
source = "registry+https://github.com/rust-lang/crates.io-index"
2685
-
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
3982
+
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
2686
3983
dependencies = [
2687
-
"thiserror-impl 2.0.14",
3984
+
"thiserror-impl 2.0.17",
2688
3985
]
2689
3986
2690
3987
[[package]]
···
2695
3992
dependencies = [
2696
3993
"proc-macro2",
2697
3994
"quote",
2698
-
"syn",
3995
+
"syn 2.0.112",
2699
3996
]
2700
3997
2701
3998
[[package]]
2702
3999
name = "thiserror-impl"
2703
-
version = "2.0.14"
4000
+
version = "2.0.17"
2704
4001
source = "registry+https://github.com/rust-lang/crates.io-index"
2705
-
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
4002
+
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
2706
4003
dependencies = [
2707
4004
"proc-macro2",
2708
4005
"quote",
2709
-
"syn",
4006
+
"syn 2.0.112",
2710
4007
]
2711
4008
2712
4009
[[package]]
···
2719
4016
]
2720
4017
2721
4018
[[package]]
4019
+
name = "time"
4020
+
version = "0.3.44"
4021
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4022
+
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
4023
+
dependencies = [
4024
+
"deranged",
4025
+
"itoa",
4026
+
"num-conv",
4027
+
"powerfmt",
4028
+
"serde",
4029
+
"time-core",
4030
+
"time-macros",
4031
+
]
4032
+
4033
+
[[package]]
4034
+
name = "time-core"
4035
+
version = "0.1.6"
4036
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4037
+
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
4038
+
4039
+
[[package]]
4040
+
name = "time-macros"
4041
+
version = "0.2.24"
4042
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4043
+
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
4044
+
dependencies = [
4045
+
"num-conv",
4046
+
"time-core",
4047
+
]
4048
+
4049
+
[[package]]
2722
4050
name = "tinystr"
2723
-
version = "0.8.1"
4051
+
version = "0.8.2"
2724
4052
source = "registry+https://github.com/rust-lang/crates.io-index"
2725
-
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
4053
+
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
2726
4054
dependencies = [
2727
4055
"displaydoc",
2728
4056
"zerovec",
···
2730
4058
2731
4059
[[package]]
2732
4060
name = "tinyvec"
2733
-
version = "1.9.0"
4061
+
version = "1.10.0"
2734
4062
source = "registry+https://github.com/rust-lang/crates.io-index"
2735
-
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
4063
+
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
2736
4064
dependencies = [
2737
4065
"tinyvec_macros",
2738
4066
]
···
2745
4073
2746
4074
[[package]]
2747
4075
name = "tokio"
2748
-
version = "1.47.1"
4076
+
version = "1.48.0"
2749
4077
source = "registry+https://github.com/rust-lang/crates.io-index"
2750
-
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
4078
+
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
2751
4079
dependencies = [
2752
-
"backtrace",
2753
4080
"bytes",
2754
-
"io-uring",
2755
4081
"libc",
2756
4082
"mio",
2757
4083
"pin-project-lite",
2758
4084
"signal-hook-registry",
2759
-
"slab",
2760
4085
"socket2",
2761
4086
"tokio-macros",
2762
-
"windows-sys 0.59.0",
4087
+
"windows-sys 0.61.2",
2763
4088
]
2764
4089
2765
4090
[[package]]
2766
4091
name = "tokio-macros"
2767
-
version = "2.5.0"
4092
+
version = "2.6.0"
2768
4093
source = "registry+https://github.com/rust-lang/crates.io-index"
2769
-
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
4094
+
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
2770
4095
dependencies = [
2771
4096
"proc-macro2",
2772
4097
"quote",
2773
-
"syn",
4098
+
"syn 2.0.112",
2774
4099
]
2775
4100
2776
4101
[[package]]
2777
4102
name = "tokio-rustls"
2778
-
version = "0.26.2"
4103
+
version = "0.26.4"
2779
4104
source = "registry+https://github.com/rust-lang/crates.io-index"
2780
-
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
4105
+
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
2781
4106
dependencies = [
2782
4107
"rustls",
2783
4108
"tokio",
···
2796
4121
2797
4122
[[package]]
2798
4123
name = "tokio-util"
2799
-
version = "0.7.15"
4124
+
version = "0.7.17"
2800
4125
source = "registry+https://github.com/rust-lang/crates.io-index"
2801
-
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
4126
+
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
2802
4127
dependencies = [
2803
4128
"bytes",
2804
4129
"futures-core",
2805
4130
"futures-sink",
4131
+
"futures-util",
2806
4132
"pin-project-lite",
2807
4133
"tokio",
2808
4134
]
2809
4135
2810
4136
[[package]]
2811
4137
name = "tonic"
2812
-
version = "0.14.1"
4138
+
version = "0.14.2"
2813
4139
source = "registry+https://github.com/rust-lang/crates.io-index"
2814
-
checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40"
4140
+
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
2815
4141
dependencies = [
2816
4142
"async-trait",
2817
4143
"axum",
···
2844
4170
dependencies = [
2845
4171
"futures-core",
2846
4172
"futures-util",
2847
-
"indexmap",
4173
+
"indexmap 2.12.1",
2848
4174
"pin-project-lite",
2849
4175
"slab",
2850
4176
"sync_wrapper",
···
2857
4183
2858
4184
[[package]]
2859
4185
name = "tower-http"
2860
-
version = "0.6.6"
4186
+
version = "0.6.8"
2861
4187
source = "registry+https://github.com/rust-lang/crates.io-index"
2862
-
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
4188
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
2863
4189
dependencies = [
2864
4190
"async-compression",
2865
4191
"bitflags",
2866
4192
"bytes",
2867
4193
"futures-core",
4194
+
"futures-util",
2868
4195
"http",
2869
4196
"http-body",
4197
+
"http-body-util",
4198
+
"iri-string",
2870
4199
"pin-project-lite",
2871
4200
"tokio",
2872
4201
"tokio-util",
4202
+
"tower",
2873
4203
"tower-layer",
2874
4204
"tower-service",
2875
4205
]
···
2897
4227
"governor",
2898
4228
"http",
2899
4229
"pin-project",
2900
-
"thiserror 2.0.14",
4230
+
"thiserror 2.0.17",
2901
4231
"tonic",
2902
4232
"tower",
2903
4233
"tracing",
···
2905
4235
2906
4236
[[package]]
2907
4237
name = "tracing"
2908
-
version = "0.1.41"
4238
+
version = "0.1.44"
2909
4239
source = "registry+https://github.com/rust-lang/crates.io-index"
2910
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
4240
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
2911
4241
dependencies = [
2912
4242
"log",
2913
4243
"pin-project-lite",
···
2917
4247
2918
4248
[[package]]
2919
4249
name = "tracing-attributes"
2920
-
version = "0.1.30"
4250
+
version = "0.1.31"
2921
4251
source = "registry+https://github.com/rust-lang/crates.io-index"
2922
-
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
4252
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
2923
4253
dependencies = [
2924
4254
"proc-macro2",
2925
4255
"quote",
2926
-
"syn",
4256
+
"syn 2.0.112",
2927
4257
]
2928
4258
2929
4259
[[package]]
2930
4260
name = "tracing-core"
2931
-
version = "0.1.34"
4261
+
version = "0.1.36"
2932
4262
source = "registry+https://github.com/rust-lang/crates.io-index"
2933
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
4263
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
2934
4264
dependencies = [
2935
4265
"once_cell",
2936
4266
"valuable",
···
2949
4279
2950
4280
[[package]]
2951
4281
name = "tracing-subscriber"
2952
-
version = "0.3.19"
4282
+
version = "0.3.22"
2953
4283
source = "registry+https://github.com/rust-lang/crates.io-index"
2954
-
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
4284
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
2955
4285
dependencies = [
2956
4286
"matchers",
2957
4287
"nu-ansi-term",
2958
4288
"once_cell",
2959
-
"regex",
4289
+
"regex-automata",
2960
4290
"sharded-slab",
2961
4291
"smallvec",
2962
4292
"thread_local",
···
2966
4296
]
2967
4297
2968
4298
[[package]]
4299
+
name = "trait-variant"
4300
+
version = "0.1.2"
4301
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4302
+
checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
4303
+
dependencies = [
4304
+
"proc-macro2",
4305
+
"quote",
4306
+
"syn 2.0.112",
4307
+
]
4308
+
4309
+
[[package]]
2969
4310
name = "try-lock"
2970
4311
version = "0.2.5"
2971
4312
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2973
4314
2974
4315
[[package]]
2975
4316
name = "typenum"
2976
-
version = "1.18.0"
4317
+
version = "1.19.0"
2977
4318
source = "registry+https://github.com/rust-lang/crates.io-index"
2978
-
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
4319
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
2979
4320
2980
4321
[[package]]
2981
4322
name = "ucd-trie"
···
2991
4332
2992
4333
[[package]]
2993
4334
name = "unicode-ident"
2994
-
version = "1.0.18"
4335
+
version = "1.0.22"
2995
4336
source = "registry+https://github.com/rust-lang/crates.io-index"
2996
-
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
4337
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
2997
4338
2998
4339
[[package]]
2999
4340
name = "unicode-normalization"
3000
-
version = "0.1.24"
4341
+
version = "0.1.25"
3001
4342
source = "registry+https://github.com/rust-lang/crates.io-index"
3002
-
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
4343
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
3003
4344
dependencies = [
3004
4345
"tinyvec",
3005
4346
]
3006
4347
3007
4348
[[package]]
3008
4349
name = "unicode-properties"
3009
-
version = "0.1.3"
4350
+
version = "0.1.4"
4351
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4352
+
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
4353
+
4354
+
[[package]]
4355
+
name = "unicode-segmentation"
4356
+
version = "1.12.0"
3010
4357
source = "registry+https://github.com/rust-lang/crates.io-index"
3011
-
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
4358
+
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
4359
+
4360
+
[[package]]
4361
+
name = "unicode-width"
4362
+
version = "0.1.14"
4363
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4364
+
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
4365
+
4366
+
[[package]]
4367
+
name = "unicode-xid"
4368
+
version = "0.2.6"
4369
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4370
+
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
4371
+
4372
+
[[package]]
4373
+
name = "unsigned-varint"
4374
+
version = "0.8.0"
4375
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4376
+
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
3012
4377
3013
4378
[[package]]
3014
4379
name = "untrusted"
···
3024
4389
3025
4390
[[package]]
3026
4391
name = "url"
3027
-
version = "2.5.4"
4392
+
version = "2.5.7"
3028
4393
source = "registry+https://github.com/rust-lang/crates.io-index"
3029
-
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
4394
+
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
3030
4395
dependencies = [
3031
4396
"form_urlencoded",
3032
4397
"idna",
3033
4398
"percent-encoding",
4399
+
"serde",
3034
4400
]
4401
+
4402
+
[[package]]
4403
+
name = "urlencoding"
4404
+
version = "2.1.3"
4405
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4406
+
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
4407
+
4408
+
[[package]]
4409
+
name = "utf8-width"
4410
+
version = "0.1.8"
4411
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4412
+
checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
3035
4413
3036
4414
[[package]]
3037
4415
name = "utf8_iter"
···
3083
4461
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3084
4462
3085
4463
[[package]]
3086
-
name = "wasi"
3087
-
version = "0.14.2+wasi-0.2.4"
4464
+
name = "wasip2"
4465
+
version = "1.0.1+wasi-0.2.4"
3088
4466
source = "registry+https://github.com/rust-lang/crates.io-index"
3089
-
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
4467
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
3090
4468
dependencies = [
3091
-
"wit-bindgen-rt",
4469
+
"wit-bindgen",
3092
4470
]
3093
4471
3094
4472
[[package]]
···
3099
4477
3100
4478
[[package]]
3101
4479
name = "wasm-bindgen"
3102
-
version = "0.2.100"
4480
+
version = "0.2.106"
3103
4481
source = "registry+https://github.com/rust-lang/crates.io-index"
3104
-
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
4482
+
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
3105
4483
dependencies = [
3106
4484
"cfg-if",
3107
4485
"once_cell",
3108
4486
"rustversion",
3109
4487
"wasm-bindgen-macro",
4488
+
"wasm-bindgen-shared",
3110
4489
]
3111
4490
3112
4491
[[package]]
3113
-
name = "wasm-bindgen-backend"
3114
-
version = "0.2.100"
4492
+
name = "wasm-bindgen-futures"
4493
+
version = "0.4.56"
3115
4494
source = "registry+https://github.com/rust-lang/crates.io-index"
3116
-
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
4495
+
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
3117
4496
dependencies = [
3118
-
"bumpalo",
3119
-
"log",
3120
-
"proc-macro2",
3121
-
"quote",
3122
-
"syn",
3123
-
"wasm-bindgen-shared",
4497
+
"cfg-if",
4498
+
"js-sys",
4499
+
"once_cell",
4500
+
"wasm-bindgen",
4501
+
"web-sys",
3124
4502
]
3125
4503
3126
4504
[[package]]
3127
4505
name = "wasm-bindgen-macro"
3128
-
version = "0.2.100"
4506
+
version = "0.2.106"
3129
4507
source = "registry+https://github.com/rust-lang/crates.io-index"
3130
-
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
4508
+
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
3131
4509
dependencies = [
3132
4510
"quote",
3133
4511
"wasm-bindgen-macro-support",
···
3135
4513
3136
4514
[[package]]
3137
4515
name = "wasm-bindgen-macro-support"
3138
-
version = "0.2.100"
4516
+
version = "0.2.106"
3139
4517
source = "registry+https://github.com/rust-lang/crates.io-index"
3140
-
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
4518
+
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
3141
4519
dependencies = [
4520
+
"bumpalo",
3142
4521
"proc-macro2",
3143
4522
"quote",
3144
-
"syn",
3145
-
"wasm-bindgen-backend",
4523
+
"syn 2.0.112",
3146
4524
"wasm-bindgen-shared",
3147
4525
]
3148
4526
3149
4527
[[package]]
3150
4528
name = "wasm-bindgen-shared"
3151
-
version = "0.2.100"
4529
+
version = "0.2.106"
3152
4530
source = "registry+https://github.com/rust-lang/crates.io-index"
3153
-
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
4531
+
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
3154
4532
dependencies = [
3155
4533
"unicode-ident",
3156
4534
]
3157
4535
3158
4536
[[package]]
3159
4537
name = "web-sys"
3160
-
version = "0.3.77"
4538
+
version = "0.3.83"
3161
4539
source = "registry+https://github.com/rust-lang/crates.io-index"
3162
-
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
4540
+
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
3163
4541
dependencies = [
3164
4542
"js-sys",
3165
4543
"wasm-bindgen",
···
3181
4559
source = "registry+https://github.com/rust-lang/crates.io-index"
3182
4560
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
3183
4561
dependencies = [
3184
-
"webpki-roots 1.0.2",
4562
+
"webpki-roots 1.0.5",
3185
4563
]
3186
4564
3187
4565
[[package]]
3188
4566
name = "webpki-roots"
3189
-
version = "1.0.2"
4567
+
version = "1.0.5"
3190
4568
source = "registry+https://github.com/rust-lang/crates.io-index"
3191
-
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
4569
+
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
3192
4570
dependencies = [
3193
4571
"rustls-pki-types",
3194
4572
]
3195
4573
3196
4574
[[package]]
3197
-
name = "which"
3198
-
version = "4.4.2"
3199
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3200
-
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
3201
-
dependencies = [
3202
-
"either",
3203
-
"home",
3204
-
"once_cell",
3205
-
"rustix",
3206
-
]
3207
-
3208
-
[[package]]
3209
4575
name = "whoami"
3210
4576
version = "1.6.1"
3211
4577
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3233
4599
3234
4600
[[package]]
3235
4601
name = "winapi-util"
3236
-
version = "0.1.9"
4602
+
version = "0.1.11"
3237
4603
source = "registry+https://github.com/rust-lang/crates.io-index"
3238
-
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
4604
+
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
3239
4605
dependencies = [
3240
-
"windows-sys 0.59.0",
4606
+
"windows-sys 0.48.0",
3241
4607
]
3242
4608
3243
4609
[[package]]
···
3248
4614
3249
4615
[[package]]
3250
4616
name = "windows-core"
3251
-
version = "0.61.2"
4617
+
version = "0.62.2"
3252
4618
source = "registry+https://github.com/rust-lang/crates.io-index"
3253
-
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
4619
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
3254
4620
dependencies = [
3255
4621
"windows-implement",
3256
4622
"windows-interface",
···
3261
4627
3262
4628
[[package]]
3263
4629
name = "windows-implement"
3264
-
version = "0.60.0"
4630
+
version = "0.60.2"
3265
4631
source = "registry+https://github.com/rust-lang/crates.io-index"
3266
-
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
4632
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
3267
4633
dependencies = [
3268
4634
"proc-macro2",
3269
4635
"quote",
3270
-
"syn",
4636
+
"syn 2.0.112",
3271
4637
]
3272
4638
3273
4639
[[package]]
3274
4640
name = "windows-interface"
3275
-
version = "0.59.1"
4641
+
version = "0.59.3"
3276
4642
source = "registry+https://github.com/rust-lang/crates.io-index"
3277
-
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
4643
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
3278
4644
dependencies = [
3279
4645
"proc-macro2",
3280
4646
"quote",
3281
-
"syn",
4647
+
"syn 2.0.112",
3282
4648
]
3283
4649
3284
4650
[[package]]
3285
4651
name = "windows-link"
3286
-
version = "0.1.3"
4652
+
version = "0.2.1"
3287
4653
source = "registry+https://github.com/rust-lang/crates.io-index"
3288
-
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
4654
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4655
+
4656
+
[[package]]
4657
+
name = "windows-registry"
4658
+
version = "0.6.1"
4659
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4660
+
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
4661
+
dependencies = [
4662
+
"windows-link",
4663
+
"windows-result",
4664
+
"windows-strings",
4665
+
]
3289
4666
3290
4667
[[package]]
3291
4668
name = "windows-result"
3292
-
version = "0.3.4"
4669
+
version = "0.4.1"
3293
4670
source = "registry+https://github.com/rust-lang/crates.io-index"
3294
-
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
4671
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
3295
4672
dependencies = [
3296
4673
"windows-link",
3297
4674
]
3298
4675
3299
4676
[[package]]
3300
4677
name = "windows-strings"
3301
-
version = "0.4.2"
4678
+
version = "0.5.1"
3302
4679
source = "registry+https://github.com/rust-lang/crates.io-index"
3303
-
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
4680
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
3304
4681
dependencies = [
3305
4682
"windows-link",
3306
4683
]
···
3333
4710
]
3334
4711
3335
4712
[[package]]
4713
+
name = "windows-sys"
4714
+
version = "0.60.2"
4715
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4716
+
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
4717
+
dependencies = [
4718
+
"windows-targets 0.53.5",
4719
+
]
4720
+
4721
+
[[package]]
4722
+
name = "windows-sys"
4723
+
version = "0.61.2"
4724
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4725
+
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
4726
+
dependencies = [
4727
+
"windows-link",
4728
+
]
4729
+
4730
+
[[package]]
3336
4731
name = "windows-targets"
3337
4732
version = "0.48.5"
3338
4733
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3356
4751
"windows_aarch64_gnullvm 0.52.6",
3357
4752
"windows_aarch64_msvc 0.52.6",
3358
4753
"windows_i686_gnu 0.52.6",
3359
-
"windows_i686_gnullvm",
4754
+
"windows_i686_gnullvm 0.52.6",
3360
4755
"windows_i686_msvc 0.52.6",
3361
4756
"windows_x86_64_gnu 0.52.6",
3362
4757
"windows_x86_64_gnullvm 0.52.6",
···
3364
4759
]
3365
4760
3366
4761
[[package]]
4762
+
name = "windows-targets"
4763
+
version = "0.53.5"
4764
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4765
+
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
4766
+
dependencies = [
4767
+
"windows-link",
4768
+
"windows_aarch64_gnullvm 0.53.1",
4769
+
"windows_aarch64_msvc 0.53.1",
4770
+
"windows_i686_gnu 0.53.1",
4771
+
"windows_i686_gnullvm 0.53.1",
4772
+
"windows_i686_msvc 0.53.1",
4773
+
"windows_x86_64_gnu 0.53.1",
4774
+
"windows_x86_64_gnullvm 0.53.1",
4775
+
"windows_x86_64_msvc 0.53.1",
4776
+
]
4777
+
4778
+
[[package]]
3367
4779
name = "windows_aarch64_gnullvm"
3368
4780
version = "0.48.5"
3369
4781
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3376
4788
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
3377
4789
3378
4790
[[package]]
4791
+
name = "windows_aarch64_gnullvm"
4792
+
version = "0.53.1"
4793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4794
+
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
4795
+
4796
+
[[package]]
3379
4797
name = "windows_aarch64_msvc"
3380
4798
version = "0.48.5"
3381
4799
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3388
4806
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
3389
4807
3390
4808
[[package]]
4809
+
name = "windows_aarch64_msvc"
4810
+
version = "0.53.1"
4811
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4812
+
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
4813
+
4814
+
[[package]]
3391
4815
name = "windows_i686_gnu"
3392
4816
version = "0.48.5"
3393
4817
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3400
4824
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
3401
4825
3402
4826
[[package]]
4827
+
name = "windows_i686_gnu"
4828
+
version = "0.53.1"
4829
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4830
+
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
4831
+
4832
+
[[package]]
3403
4833
name = "windows_i686_gnullvm"
3404
4834
version = "0.52.6"
3405
4835
source = "registry+https://github.com/rust-lang/crates.io-index"
3406
4836
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
4837
+
4838
+
[[package]]
4839
+
name = "windows_i686_gnullvm"
4840
+
version = "0.53.1"
4841
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4842
+
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
3407
4843
3408
4844
[[package]]
3409
4845
name = "windows_i686_msvc"
···
3418
4854
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
3419
4855
3420
4856
[[package]]
4857
+
name = "windows_i686_msvc"
4858
+
version = "0.53.1"
4859
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4860
+
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
4861
+
4862
+
[[package]]
3421
4863
name = "windows_x86_64_gnu"
3422
4864
version = "0.48.5"
3423
4865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3430
4872
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
3431
4873
3432
4874
[[package]]
4875
+
name = "windows_x86_64_gnu"
4876
+
version = "0.53.1"
4877
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4878
+
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
4879
+
4880
+
[[package]]
3433
4881
name = "windows_x86_64_gnullvm"
3434
4882
version = "0.48.5"
3435
4883
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3442
4890
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
3443
4891
3444
4892
[[package]]
4893
+
name = "windows_x86_64_gnullvm"
4894
+
version = "0.53.1"
4895
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4896
+
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
4897
+
4898
+
[[package]]
3445
4899
name = "windows_x86_64_msvc"
3446
4900
version = "0.48.5"
3447
4901
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3454
4908
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
3455
4909
3456
4910
[[package]]
3457
-
name = "wit-bindgen-rt"
3458
-
version = "0.39.0"
4911
+
name = "windows_x86_64_msvc"
4912
+
version = "0.53.1"
3459
4913
source = "registry+https://github.com/rust-lang/crates.io-index"
3460
-
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
3461
-
dependencies = [
3462
-
"bitflags",
3463
-
]
4914
+
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
4915
+
4916
+
[[package]]
4917
+
name = "wit-bindgen"
4918
+
version = "0.46.0"
4919
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4920
+
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
3464
4921
3465
4922
[[package]]
3466
4923
name = "writeable"
3467
-
version = "0.6.1"
4924
+
version = "0.6.2"
3468
4925
source = "registry+https://github.com/rust-lang/crates.io-index"
3469
-
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
4926
+
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
4927
+
4928
+
[[package]]
4929
+
name = "yansi"
4930
+
version = "1.0.1"
4931
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4932
+
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
3470
4933
3471
4934
[[package]]
3472
4935
name = "yoke"
3473
-
version = "0.8.0"
4936
+
version = "0.8.1"
3474
4937
source = "registry+https://github.com/rust-lang/crates.io-index"
3475
-
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
4938
+
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
3476
4939
dependencies = [
3477
-
"serde",
3478
4940
"stable_deref_trait",
3479
4941
"yoke-derive",
3480
4942
"zerofrom",
···
3482
4944
3483
4945
[[package]]
3484
4946
name = "yoke-derive"
3485
-
version = "0.8.0"
4947
+
version = "0.8.1"
3486
4948
source = "registry+https://github.com/rust-lang/crates.io-index"
3487
-
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
4949
+
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
3488
4950
dependencies = [
3489
4951
"proc-macro2",
3490
4952
"quote",
3491
-
"syn",
4953
+
"syn 2.0.112",
3492
4954
"synstructure",
3493
4955
]
3494
4956
3495
4957
[[package]]
3496
4958
name = "zerocopy"
3497
-
version = "0.8.26"
4959
+
version = "0.8.31"
3498
4960
source = "registry+https://github.com/rust-lang/crates.io-index"
3499
-
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
4961
+
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
3500
4962
dependencies = [
3501
4963
"zerocopy-derive",
3502
4964
]
3503
4965
3504
4966
[[package]]
3505
4967
name = "zerocopy-derive"
3506
-
version = "0.8.26"
4968
+
version = "0.8.31"
3507
4969
source = "registry+https://github.com/rust-lang/crates.io-index"
3508
-
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
4970
+
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
3509
4971
dependencies = [
3510
4972
"proc-macro2",
3511
4973
"quote",
3512
-
"syn",
4974
+
"syn 2.0.112",
3513
4975
]
3514
4976
3515
4977
[[package]]
···
3529
4991
dependencies = [
3530
4992
"proc-macro2",
3531
4993
"quote",
3532
-
"syn",
4994
+
"syn 2.0.112",
3533
4995
"synstructure",
3534
4996
]
3535
4997
3536
4998
[[package]]
3537
4999
name = "zeroize"
3538
-
version = "1.8.1"
5000
+
version = "1.8.2"
3539
5001
source = "registry+https://github.com/rust-lang/crates.io-index"
3540
-
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
5002
+
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
3541
5003
dependencies = [
3542
5004
"zeroize_derive",
3543
5005
]
3544
5006
3545
5007
[[package]]
3546
5008
name = "zeroize_derive"
3547
-
version = "1.4.2"
5009
+
version = "1.4.3"
3548
5010
source = "registry+https://github.com/rust-lang/crates.io-index"
3549
-
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
5011
+
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
3550
5012
dependencies = [
3551
5013
"proc-macro2",
3552
5014
"quote",
3553
-
"syn",
5015
+
"syn 2.0.112",
3554
5016
]
3555
5017
3556
5018
[[package]]
3557
5019
name = "zerotrie"
3558
-
version = "0.2.2"
5020
+
version = "0.2.3"
3559
5021
source = "registry+https://github.com/rust-lang/crates.io-index"
3560
-
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
5022
+
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
3561
5023
dependencies = [
3562
5024
"displaydoc",
3563
5025
"yoke",
···
3566
5028
3567
5029
[[package]]
3568
5030
name = "zerovec"
3569
-
version = "0.11.4"
5031
+
version = "0.11.5"
3570
5032
source = "registry+https://github.com/rust-lang/crates.io-index"
3571
-
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
5033
+
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
3572
5034
dependencies = [
3573
5035
"yoke",
3574
5036
"zerofrom",
···
3577
5039
3578
5040
[[package]]
3579
5041
name = "zerovec-derive"
3580
-
version = "0.11.1"
5042
+
version = "0.11.2"
3581
5043
source = "registry+https://github.com/rust-lang/crates.io-index"
3582
-
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
5044
+
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
3583
5045
dependencies = [
3584
5046
"proc-macro2",
3585
5047
"quote",
3586
-
"syn",
5048
+
"syn 2.0.112",
3587
5049
]
3588
5050
3589
5051
[[package]]
5052
+
name = "zmij"
5053
+
version = "1.0.8"
5054
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5055
+
checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd"
5056
+
5057
+
[[package]]
3590
5058
name = "zstd"
3591
5059
version = "0.13.3"
3592
5060
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3606
5074
3607
5075
[[package]]
3608
5076
name = "zstd-sys"
3609
-
version = "2.0.15+zstd.1.5.7"
5077
+
version = "2.0.16+zstd.1.5.7"
3610
5078
source = "registry+https://github.com/rust-lang/crates.io-index"
3611
-
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
5079
+
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
3612
5080
dependencies = [
3613
5081
"cc",
3614
5082
"pkg-config",
+18
-9
Cargo.toml
+18
-9
Cargo.toml
···
5
5
license = "MIT"
6
6
7
7
[dependencies]
8
-
axum = { version = "0.8.4", features = ["macros", "json"] }
9
-
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "signal"] }
8
+
axum = { version = "0.8.8", features = ["macros", "json"] }
9
+
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal"] }
10
10
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] }
11
11
dotenvy = "0.15.7"
12
12
serde = { version = "1.0", features = ["derive"] }
13
13
serde_json = "1.0"
14
14
tracing = "0.1"
15
15
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
16
-
hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] }
16
+
hyper-util = { version = "0.1.19", features = ["client", "client-legacy"] }
17
17
tower-http = { version = "0.6", features = ["cors", "compression-zstd"] }
18
-
tower_governor = "0.8.0"
18
+
tower_governor = { version = "0.8.0", features = ["axum", "tracing"] }
19
19
hex = "0.4"
20
20
jwt-compact = { version = "0.8.0", features = ["es256k"] }
21
21
scrypt = "0.11"
22
22
#Leaveing these two cause I think it is needed by the email crate for ssl
23
-
aws-lc-rs = "1.13.0"
23
+
aws-lc-rs = "1.15.2"
24
24
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
25
25
lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] }
26
-
handlebars = { version = "6.3.2", features = ["rust-embed"] }
27
-
rust-embed = "8.7.2"
26
+
handlebars = { version = "6.4.0", features = ["rust-embed"] }
27
+
rust-embed = "8.9.0"
28
28
axum-template = { version = "3.0.0", features = ["handlebars"] }
29
29
rand = "0.9.2"
30
-
anyhow = "1.0.99"
31
-
chrono = "0.4.41"
30
+
anyhow = "1.0.100"
31
+
chrono = { version = "0.4.42", features = ["default", "serde"] }
32
32
sha2 = "0.10"
33
+
jacquard-common = "0.9.5"
34
+
jacquard-identity = "0.9.5"
35
+
multibase = "0.9.2"
36
+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
37
+
urlencoding = "2.1"
38
+
html-escape = "0.2.13"
39
+
josekit = "0.10.3"
40
+
dashmap = "6.1"
41
+
tower = "0.5"
+108
-24
README.md
+108
-24
README.md
···
15
15
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
16
16
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17
17
18
-
## Captcha on Create Account
18
+
## Captcha on account creation
19
+
20
+
Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge
21
+
hosted on the
22
+
PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support
23
+
and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha
24
+
was successful.
25
+
26
+
- Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true.
27
+
- Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/
28
+
- Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to
29
+
PDS
30
+
Gatekeeper
31
+
- Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not
32
+
strictly needed unless you're scaling
33
+
- Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`.
34
+
- Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the
35
+
url showing the captcha. Defaults are:
36
+
- https://bsky.app
37
+
- https://pdsmoover.com
38
+
- https://blacksky.community
39
+
- https://tektite.cc
19
40
20
-
Future feature?
41
+
## Block account creation unless it's a migration
42
+
43
+
You can set `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` to block createAccount unless it's via a migration. This does not require
44
+
a change for migration tools, but social-apps create a new account will no longer work and to create a brand new account
45
+
users will need to do this via the Oauth account create screen on the PDS. We recommend setting `PDS_HCAPTCHA_SITE_KEY`
46
+
and `PDS_HCAPTCHA_SECRET_KEY` so the OAuth screen is protected by a captcha if you use this with invite codes turned
47
+
off.
21
48
22
49
# Setup
23
50
···
49
76
- pds
50
77
```
51
78
79
+
For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up
80
+
correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml).
81
+
82
+
```yml
83
+
gatekeeper:
84
+
container_name: gatekeeper
85
+
image: 'fatfingers23/pds_gatekeeper:latest'
86
+
restart: unless-stopped
87
+
volumes:
88
+
- '/pds:/pds'
89
+
environment:
90
+
- 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
91
+
- 'PDS_BASE_URL=http://pds:3000'
92
+
- GATEKEEPER_HOST=0.0.0.0
93
+
depends_on:
94
+
- pds
95
+
healthcheck:
96
+
test:
97
+
- CMD
98
+
- timeout
99
+
- '1'
100
+
- bash
101
+
- '-c'
102
+
- 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
103
+
interval: 10s
104
+
timeout: 5s
105
+
retries: 3
106
+
start_period: 10s
107
+
labels:
108
+
- traefik.enable=true
109
+
- 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))'
110
+
- traefik.http.routers.pds-gatekeeper.entrypoints=https
111
+
- traefik.http.routers.pds-gatekeeper.tls=true
112
+
- traefik.http.routers.pds-gatekeeper.priority=100
113
+
- traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
114
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
115
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
116
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
117
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
118
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
119
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
120
+
- traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
121
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
122
+
```
123
+
52
124
## Caddy setup
53
125
54
126
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
55
127
in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
56
128
This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
57
129
58
-
```caddyfile
130
+
```
59
131
@gatekeeper {
60
-
path /xrpc/com.atproto.server.getSession
61
-
path /xrpc/com.atproto.server.updateEmail
62
-
path /xrpc/com.atproto.server.createSession
63
-
path /xrpc/com.atproto.server.createAccount
64
-
path /@atproto/oauth-provider/~api/sign-in
132
+
path /xrpc/com.atproto.server.getSession
133
+
path /xrpc/com.atproto.server.describeServer
134
+
path /xrpc/com.atproto.server.updateEmail
135
+
path /xrpc/com.atproto.server.createSession
136
+
path /xrpc/com.atproto.server.createAccount
137
+
path /@atproto/oauth-provider/~api/sign-in
138
+
path /gate/*
65
139
}
66
140
67
141
handle @gatekeeper {
68
-
reverse_proxy http://localhost:8080
69
-
}
142
+
reverse_proxy http://localhost:8080
143
+
}
70
144
71
-
reverse_proxy http://localhost:3000
145
+
reverse_proxy http://localhost:3000
72
146
```
73
147
74
148
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
75
149
`localhost:8081` (or w/e port you want).
76
150
77
-
```caddyfile
151
+
```
78
152
http://*.localhost:8082, http://localhost:8082 {
79
-
@gatekeeper {
80
-
path /xrpc/com.atproto.server.getSession
81
-
path /xrpc/com.atproto.server.updateEmail
82
-
path /xrpc/com.atproto.server.createSession
83
-
path /@atproto/oauth-provider/~api/sign-in
84
-
}
85
-
86
-
handle @gatekeeper {
87
-
reverse_proxy http://localhost:8080
88
-
}
153
+
@gatekeeper {
154
+
path /xrpc/com.atproto.server.getSession
155
+
path /xrpc/com.atproto.server.describeServer
156
+
path /xrpc/com.atproto.server.updateEmail
157
+
path /xrpc/com.atproto.server.createSession
158
+
path /xrpc/com.atproto.server.createAccount
159
+
path /@atproto/oauth-provider/~api/sign-in
160
+
path /gate/*
161
+
}
89
162
90
-
reverse_proxy http://localhost:3000
163
+
handle @gatekeeper {
164
+
#This is the address for PDS gatekeeper, default is 8080
165
+
reverse_proxy http://localhost:8080
166
+
#Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper
167
+
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
168
+
}
169
+
reverse_proxy http://localhost:3000
91
170
}
92
171
93
172
```
···
120
199
121
200
`GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where
122
201
the 5 comes from. Example can set this to 10 to allow for 10 requests in a burst, and after 60 seconds it will drop one
123
-
off.
202
+
off.
203
+
204
+
`GATEKEEPER_ALLOW_ONLY_MIGRATIONS` - Defaults false. If set to true, will only allow the
205
+
`/xrpc/com.atproto.server.createAccount` endpoint to be used for migrations. Meaning it will check for the serviceAuth
206
+
token and verify it is valid.
207
+
+22
-22
examples/Caddyfile
+22
-22
examples/Caddyfile
···
1
1
{
2
-
email youremail@myemail.com
3
-
on_demand_tls {
4
-
ask http://localhost:3000/tls-check
5
-
}
2
+
email youremail@myemail.com
3
+
on_demand_tls {
4
+
ask http://localhost:3000/tls-check
5
+
}
6
6
}
7
7
8
8
*.yourpds.com, yourpds.com {
9
-
tls {
10
-
on_demand
11
-
}
12
-
# You'll most likely just want from here to....
13
-
@gatekeeper {
14
-
path /xrpc/com.atproto.server.getSession
15
-
path /xrpc/com.atproto.server.updateEmail
16
-
path /xrpc/com.atproto.server.createSession
17
-
path /xrpc/com.atproto.server.createAccount
18
-
path /@atproto/oauth-provider/~api/sign-in
9
+
tls {
10
+
on_demand
19
11
}
12
+
# You'll most likely just want from here to....
13
+
@gatekeeper {
14
+
path /xrpc/com.atproto.server.getSession
15
+
path /xrpc/com.atproto.server.describeServer
16
+
path /xrpc/com.atproto.server.updateEmail
17
+
path /xrpc/com.atproto.server.createSession
18
+
path /xrpc/com.atproto.server.createAccount
19
+
path /@atproto/oauth-provider/~api/sign-in
20
+
path /gate/*
21
+
}
20
22
21
-
handle @gatekeeper {
22
-
#This is the address for PDS gatekeeper, default is 8080
23
-
reverse_proxy http://localhost:8080
24
-
}
23
+
handle @gatekeeper {
24
+
#This is the address for PDS gatekeeper, default is 8080
25
+
reverse_proxy http://localhost:8080
26
+
}
25
27
26
-
reverse_proxy http://localhost:3000
27
-
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
28
+
reverse_proxy http://localhost:3000
29
+
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
28
30
}
29
-
30
-
+73
examples/coolify-compose.yml
+73
examples/coolify-compose.yml
···
1
+
services:
2
+
pds:
3
+
image: 'ghcr.io/bluesky-social/pds:0.4.182'
4
+
volumes:
5
+
- '/pds:/pds'
6
+
environment:
7
+
- SERVICE_URL_PDS_3000
8
+
- 'PDS_HOSTNAME=${SERVICE_FQDN_PDS_3000}'
9
+
- 'PDS_JWT_SECRET=${SERVICE_HEX_32_JWTSECRET}'
10
+
- 'PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}'
11
+
- 'PDS_ADMIN_EMAIL=${PDS_ADMIN_EMAIL}'
12
+
- 'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${SERVICE_HEX_32_ROTATIONKEY}'
13
+
- 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
14
+
- 'PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATA_DIRECTORY:-/pds}/blocks'
15
+
- 'PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-104857600}'
16
+
- 'PDS_DID_PLC_URL=${PDS_DID_PLC_URL:-https://plc.directory}'
17
+
- 'PDS_EMAIL_FROM_ADDRESS=${PDS_EMAIL_FROM_ADDRESS}'
18
+
- 'PDS_EMAIL_SMTP_URL=${PDS_EMAIL_SMTP_URL}'
19
+
- 'PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL:-https://api.bsky.app}'
20
+
- 'PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID:-did:web:api.bsky.app}'
21
+
- 'PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL:-https://mod.bsky.app/xrpc/com.atproto.moderation.createReport}'
22
+
- 'PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID:-did:plc:ar7c4by46qjdydhdevvrndac}'
23
+
- 'PDS_CRAWLERS=${PDS_CRAWLERS:-https://bsky.network}'
24
+
- 'LOG_ENABLED=${LOG_ENABLED:-true}'
25
+
command: "sh -c '\n set -euo pipefail\n echo \"Installing required packages and pdsadmin...\"\n apk add --no-cache openssl curl bash jq coreutils gnupg util-linux-misc >/dev/null\n curl -o /usr/local/bin/pdsadmin.sh https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh\n chmod 700 /usr/local/bin/pdsadmin.sh\n ln -sf /usr/local/bin/pdsadmin.sh /usr/local/bin/pdsadmin\n echo \"Creating an empty pds.env file so pdsadmin works...\"\n touch ${PDS_DATA_DIRECTORY}/pds.env\n echo \"Launching PDS, enjoy!...\"\n exec node --enable-source-maps index.js\n'\n"
26
+
healthcheck:
27
+
test:
28
+
- CMD
29
+
- wget
30
+
- '--spider'
31
+
- 'http://127.0.0.1:3000/xrpc/_health'
32
+
interval: 5s
33
+
timeout: 10s
34
+
retries: 10
35
+
gatekeeper:
36
+
container_name: gatekeeper
37
+
image: 'fatfingers23/pds_gatekeeper:latest'
38
+
restart: unless-stopped
39
+
volumes:
40
+
- '/pds:/pds'
41
+
environment:
42
+
- 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
43
+
- 'PDS_BASE_URL=http://pds:3000'
44
+
- GATEKEEPER_HOST=0.0.0.0
45
+
depends_on:
46
+
- pds
47
+
healthcheck:
48
+
test:
49
+
- CMD
50
+
- timeout
51
+
- '1'
52
+
- bash
53
+
- '-c'
54
+
- 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
55
+
interval: 10s
56
+
timeout: 5s
57
+
retries: 3
58
+
start_period: 10s
59
+
labels:
60
+
- traefik.enable=true
61
+
- 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.describeServer`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`) || Path(`/gate`))'
62
+
- traefik.http.routers.pds-gatekeeper.entrypoints=https
63
+
- traefik.http.routers.pds-gatekeeper.tls=true
64
+
- traefik.http.routers.pds-gatekeeper.priority=100
65
+
- traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
66
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
67
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
68
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
69
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
70
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
71
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
72
+
- traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
73
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
+166
html_templates/captcha.hbs
+166
html_templates/captcha.hbs
···
1
+
<html lang="en" class=" ">
2
+
<head>
3
+
<meta charset="utf-8"/>
4
+
<meta
5
+
name="viewport"
6
+
content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"
7
+
/>
8
+
<meta name="referrer" content="origin-when-cross-origin"/>
9
+
10
+
<title>
11
+
{{pds}} - Captcha
12
+
</title>
13
+
<style>
14
+
:root,
15
+
:root.light-mode {
16
+
--brand-color: rgb(16, 131, 254);
17
+
--primary-color: rgb(7, 10, 13);
18
+
--secondary-color: rgb(66, 86, 108);
19
+
--bg-primary-color: rgb(255, 255, 255);
20
+
--bg-secondary-color: rgb(240, 242, 245);
21
+
}
22
+
23
+
@media (prefers-color-scheme: dark) {
24
+
:root {
25
+
--brand-color: rgb(16, 131, 254);
26
+
--primary-color: rgb(255, 255, 255);
27
+
--secondary-color: rgb(133, 152, 173);
28
+
--bg-primary-color: rgb(7, 10, 13);
29
+
--bg-secondary-color: rgb(13, 18, 23);
30
+
}
31
+
}
32
+
33
+
:root.dark-mode {
34
+
--brand-color: rgb(16, 131, 254);
35
+
--primary-color: rgb(255, 255, 255);
36
+
--secondary-color: rgb(133, 152, 173);
37
+
--bg-primary-color: rgb(7, 10, 13);
38
+
--bg-secondary-color: rgb(13, 18, 23);
39
+
}
40
+
41
+
body {
42
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
43
+
Arial, sans-serif;
44
+
background: var(--bg-primary-color);
45
+
color: var(--primary-color);
46
+
text-rendering: optimizeLegibility;
47
+
-webkit-font-smoothing: antialiased;
48
+
}
49
+
50
+
.info {
51
+
border-radius: 8px;
52
+
padding: 12px 14px;
53
+
letter-spacing: 0.25px;
54
+
font-weight: 400;
55
+
background-color: var(--bg-secondary-color);
56
+
color: var(--secondary-color);
57
+
}
58
+
59
+
.gate-page,
60
+
.error-page {
61
+
margin: 0;
62
+
margin-top: 10px;
63
+
display: flex;
64
+
justify-content: center;
65
+
}
66
+
67
+
.gate-page .main,
68
+
.error-page .main {
69
+
max-width: 600px;
70
+
width: 100%;
71
+
display: flex;
72
+
flex-direction: column;
73
+
}
74
+
75
+
.gate-page .main {
76
+
padding: 0;
77
+
}
78
+
79
+
.error-page .main {
80
+
padding: 20px;
81
+
}
82
+
83
+
.gate-page .main > :not(:first-child),
84
+
.error-page .main > :not(:first-child) {
85
+
margin-top: 20px;
86
+
}
87
+
88
+
.gate-page #gate-form {
89
+
margin: 0;
90
+
}
91
+
92
+
.gate-page #hcaptcha {
93
+
display: flex;
94
+
justify-content: center;
95
+
}
96
+
97
+
.pds-title {
98
+
font-size: 2.25rem;
99
+
font-weight: 700;
100
+
line-height: 1.2;
101
+
text-align: center;
102
+
color: var(--primary-color);
103
+
margin: 10px 0 16px;
104
+
}
105
+
106
+
</style>
107
+
108
+
<script type="text/javascript">
109
+
function onCaptchaReady() {
110
+
const theme = document.documentElement.classList.contains('dark-mode') ?
111
+
'dark' :
112
+
document.documentElement.classList.contains('light-mode') ?
113
+
'light' :
114
+
window.matchMedia('(prefers-color-scheme: dark)').matches ?
115
+
'dark' :
116
+
'light'
117
+
hcaptcha.render('hcaptcha', {theme});
118
+
}
119
+
120
+
function onCaptchaComplete() {
121
+
setTimeout(function () {
122
+
document.getElementById('gate-form').submit();
123
+
}, 1000);
124
+
}
125
+
126
+
function onCaptchaError() {
127
+
const url = new URL(location.href);
128
+
url.searchParams.set('error', 'true');
129
+
location.assign(url.search);
130
+
}
131
+
132
+
function onCaptchaExpired() {
133
+
const url = new URL(location.href);
134
+
url.searchParams.set('error', 'expired');
135
+
location.assign(url.search);
136
+
}
137
+
</script>
138
+
<script
139
+
src="https://js.hcaptcha.com/1/api.js?render=explicit&onload=onCaptchaReady"
140
+
async
141
+
defer
142
+
></script>
143
+
<link rel="stylesheet" href="/gate/signup/assets/common.css"/>
144
+
</head>
145
+
146
+
<body class="gate-page">
147
+
<div class="main">
148
+
<div class="pds-title">{{pds}}</div>
149
+
<form id="gate-form" action="" method="POST">
150
+
<div
151
+
id="hcaptcha"
152
+
data-sitekey="{{captcha_site_key}}"
153
+
data-callback="onCaptchaComplete"
154
+
data-error-callback="onCaptchaError"
155
+
data-expired-callback="onCaptchaExpired"
156
+
data-chalexpired-callback="onCaptchaExpired"
157
+
></div>
158
+
<input type="hidden" name="redirect_url" value="{{redirect_url}}"/>
159
+
</form>
160
+
{{#if error_message }}
161
+
<div class="info">{{error_message}}</div>
162
+
{{/if}}
163
+
164
+
</div>
165
+
</body>
166
+
</html>
+1
-1
justfile
+1
-1
justfile
+10
migrations/20251126000000_gate_codes.sql
+10
migrations/20251126000000_gate_codes.sql
···
1
+
-- Add migration script here
2
+
CREATE TABLE IF NOT EXISTS gate_codes
3
+
(
4
+
code VARCHAR PRIMARY KEY,
5
+
handle VARCHAR NOT NULL,
6
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
7
+
);
8
+
9
+
-- Index on created_at for efficient cleanup of expired codes
10
+
CREATE INDEX IF NOT EXISTS idx_gate_codes_created_at ON gate_codes(created_at);
+101
src/auth/cache.rs
+101
src/auth/cache.rs
···
1
+
use dashmap::DashMap;
2
+
use std::sync::Arc;
3
+
use std::time::{Duration, Instant};
4
+
5
+
#[derive(Clone, Debug)]
6
+
struct CachedHandle {
7
+
handle: String,
8
+
cached_at: Instant,
9
+
}
10
+
11
+
/// A thread-safe cache for DID-to-handle resolutions with TTL expiration.
12
+
#[derive(Clone)]
13
+
pub struct HandleCache {
14
+
cache: Arc<DashMap<String, CachedHandle>>,
15
+
ttl: Duration,
16
+
}
17
+
18
+
impl Default for HandleCache {
19
+
fn default() -> Self {
20
+
Self::new()
21
+
}
22
+
}
23
+
24
+
impl HandleCache {
25
+
/// Creates a new HandleCache with a default TTL of 1 hour.
26
+
pub fn new() -> Self {
27
+
Self::with_ttl(Duration::from_secs(3600))
28
+
}
29
+
30
+
/// Creates a new HandleCache with a custom TTL.
31
+
pub fn with_ttl(ttl: Duration) -> Self {
32
+
Self {
33
+
cache: Arc::new(DashMap::new()),
34
+
ttl,
35
+
}
36
+
}
37
+
38
+
/// Gets a cached handle for the given DID, if it exists and hasn't expired.
39
+
pub fn get(&self, did: &str) -> Option<String> {
40
+
let entry = self.cache.get(did)?;
41
+
if entry.cached_at.elapsed() > self.ttl {
42
+
drop(entry);
43
+
self.cache.remove(did);
44
+
return None;
45
+
}
46
+
Some(entry.handle.clone())
47
+
}
48
+
49
+
/// Inserts a DID-to-handle mapping into the cache.
50
+
pub fn insert(&self, did: String, handle: String) {
51
+
self.cache.insert(
52
+
did,
53
+
CachedHandle {
54
+
handle,
55
+
cached_at: Instant::now(),
56
+
},
57
+
);
58
+
}
59
+
60
+
/// Removes expired entries from the cache.
61
+
/// Call this periodically to prevent memory growth.
62
+
pub fn cleanup(&self) {
63
+
self.cache.retain(|_, v| v.cached_at.elapsed() <= self.ttl);
64
+
}
65
+
66
+
/// Returns the number of entries in the cache.
67
+
pub fn len(&self) -> usize {
68
+
self.cache.len()
69
+
}
70
+
71
+
/// Returns true if the cache is empty.
72
+
pub fn is_empty(&self) -> bool {
73
+
self.cache.is_empty()
74
+
}
75
+
}
76
+
77
+
#[cfg(test)]
78
+
mod tests {
79
+
use super::*;
80
+
81
+
#[test]
82
+
fn test_cache_insert_and_get() {
83
+
let cache = HandleCache::new();
84
+
cache.insert("did:plc:test".into(), "test.handle.com".into());
85
+
assert_eq!(cache.get("did:plc:test"), Some("test.handle.com".into()));
86
+
}
87
+
88
+
#[test]
89
+
fn test_cache_miss() {
90
+
let cache = HandleCache::new();
91
+
assert_eq!(cache.get("did:plc:nonexistent"), None);
92
+
}
93
+
94
+
#[test]
95
+
fn test_cache_expiration() {
96
+
let cache = HandleCache::with_ttl(Duration::from_millis(1));
97
+
cache.insert("did:plc:test".into(), "test.handle.com".into());
98
+
std::thread::sleep(Duration::from_millis(10));
99
+
assert_eq!(cache.get("did:plc:test"), None);
100
+
}
101
+
}
+456
src/auth/middleware.rs
+456
src/auth/middleware.rs
···
1
+
use super::{AuthRules, HandleCache, SessionData};
2
+
use crate::helpers::json_error_response;
3
+
use crate::AppState;
4
+
use axum::extract::{Request, State};
5
+
use axum::http::{HeaderMap, StatusCode};
6
+
use axum::middleware::Next;
7
+
use axum::response::{IntoResponse, Response};
8
+
use jacquard_identity::resolver::IdentityResolver;
9
+
use jacquard_identity::PublicResolver;
10
+
use jwt_compact::alg::{Hs256, Hs256Key};
11
+
use jwt_compact::{AlgorithmExt, Claims, Token, UntrustedToken, ValidationError};
12
+
use serde::{Deserialize, Serialize};
13
+
use std::env;
14
+
use std::sync::Arc;
15
+
use tracing::log;
16
+
17
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18
+
pub enum AuthScheme {
19
+
Bearer,
20
+
DPoP,
21
+
}
22
+
23
+
#[derive(Serialize, Deserialize)]
24
+
pub struct TokenClaims {
25
+
pub sub: String,
26
+
/// OAuth scopes as space-separated string (per OAuth 2.0 spec)
27
+
#[serde(default)]
28
+
pub scope: Option<String>,
29
+
}
30
+
31
+
/// State passed to the auth middleware containing both AppState and auth rules.
32
+
#[derive(Clone)]
33
+
pub struct AuthMiddlewareState {
34
+
pub app_state: AppState,
35
+
pub rules: AuthRules,
36
+
}
37
+
38
+
/// Core middleware function that validates authentication and applies auth rules.
39
+
///
40
+
/// Use this with `axum::middleware::from_fn_with_state`:
41
+
/// ```ignore
42
+
/// use axum::middleware::from_fn_with_state;
43
+
///
44
+
/// let mw_state = AuthMiddlewareState {
45
+
/// app_state: state.clone(),
46
+
/// rules: AuthRules::HandleEndsWith(".blacksky.team".into()),
47
+
/// };
48
+
///
49
+
/// .route("/protected", get(handler).layer(from_fn_with_state(mw_state, auth_middleware)))
50
+
/// ```
51
+
pub async fn auth_middleware(
52
+
State(mw_state): State<AuthMiddlewareState>,
53
+
req: Request,
54
+
next: Next,
55
+
) -> Response {
56
+
let AuthMiddlewareState { app_state, rules } = mw_state;
57
+
58
+
// 1. Extract DID and scopes from JWT (Bearer token)
59
+
let extracted = match extract_auth_from_request(req.headers()) {
60
+
Ok(Some(auth)) => auth,
61
+
Ok(None) => {
62
+
return json_error_response(StatusCode::UNAUTHORIZED, "AuthRequired", "Authentication required")
63
+
.unwrap_or_else(|_| StatusCode::UNAUTHORIZED.into_response());
64
+
}
65
+
Err(e) => {
66
+
log::error!("Token extraction error: {}", e);
67
+
return json_error_response(StatusCode::UNAUTHORIZED, "InvalidToken", &e)
68
+
.unwrap_or_else(|_| StatusCode::UNAUTHORIZED.into_response());
69
+
}
70
+
};
71
+
72
+
// 2. Resolve DID to handle (check cache first)
73
+
let handle = match resolve_did_to_handle(&app_state.resolver, &app_state.handle_cache, &extracted.did).await {
74
+
Ok(handle) => handle,
75
+
Err(e) => {
76
+
log::error!("Failed to resolve DID {} to handle: {}", extracted.did, e);
77
+
return json_error_response(
78
+
StatusCode::INTERNAL_SERVER_ERROR,
79
+
"ResolutionError",
80
+
"Failed to resolve identity",
81
+
)
82
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
83
+
}
84
+
};
85
+
86
+
// 3. Build session data and validate rules
87
+
let session = SessionData {
88
+
did: extracted.did,
89
+
handle,
90
+
scopes: extracted.scopes,
91
+
};
92
+
93
+
if !rules.validate(&session) {
94
+
return json_error_response(StatusCode::FORBIDDEN, "AccessDenied", "Access denied by authorization rules")
95
+
.unwrap_or_else(|_| StatusCode::FORBIDDEN.into_response());
96
+
}
97
+
98
+
// 4. Pass through on success
99
+
next.run(req).await
100
+
}
101
+
102
+
/// Extracted authentication data from JWT
103
+
struct ExtractedAuth {
104
+
did: String,
105
+
scopes: Vec<String>,
106
+
}
107
+
108
+
/// Extracts the DID and scopes from the Authorization header (Bearer JWT).
109
+
fn extract_auth_from_request(headers: &HeaderMap) -> Result<Option<ExtractedAuth>, String> {
110
+
let auth = extract_auth(headers)?;
111
+
112
+
match auth {
113
+
None => Ok(None),
114
+
Some((scheme, token_str)) => {
115
+
match scheme {
116
+
AuthScheme::Bearer => {
117
+
let token = UntrustedToken::new(&token_str)
118
+
.map_err(|_| "Invalid token format".to_string())?;
119
+
120
+
let _claims: Claims<TokenClaims> = token
121
+
.deserialize_claims_unchecked()
122
+
.map_err(|_| "Failed to parse token claims".to_string())?;
123
+
124
+
let key = Hs256Key::new(
125
+
env::var("PDS_JWT_SECRET")
126
+
.map_err(|_| "PDS_JWT_SECRET not configured".to_string())?,
127
+
);
128
+
129
+
let validated: Token<TokenClaims> = Hs256
130
+
.validator(&key)
131
+
.validate(&token)
132
+
.map_err(|e: ValidationError| format!("Token validation failed: {:?}", e))?;
133
+
134
+
let custom = &validated.claims().custom;
135
+
136
+
// Parse scopes from space-separated string (OAuth 2.0 spec)
137
+
let scopes: Vec<String> = custom.scope
138
+
.as_ref()
139
+
.map(|s| s.split_whitespace().map(|s| s.to_string()).collect())
140
+
.unwrap_or_default();
141
+
142
+
Ok(Some(ExtractedAuth {
143
+
did: custom.sub.clone(),
144
+
scopes,
145
+
}))
146
+
}
147
+
AuthScheme::DPoP => {
148
+
// DPoP tokens are not validated here; pass through without auth data
149
+
Ok(None)
150
+
}
151
+
}
152
+
}
153
+
}
154
+
}
155
+
156
+
/// Extracts the authentication scheme and token from the Authorization header.
157
+
fn extract_auth(headers: &HeaderMap) -> Result<Option<(AuthScheme, String)>, String> {
158
+
match headers.get(axum::http::header::AUTHORIZATION) {
159
+
None => Ok(None),
160
+
Some(hv) => {
161
+
let s = hv
162
+
.to_str()
163
+
.map_err(|_| "Authorization header is not valid UTF-8".to_string())?;
164
+
165
+
let mut parts = s.splitn(2, ' ');
166
+
match (parts.next(), parts.next()) {
167
+
(Some("Bearer"), Some(tok)) if !tok.is_empty() => {
168
+
Ok(Some((AuthScheme::Bearer, tok.to_string())))
169
+
}
170
+
(Some("DPoP"), Some(tok)) if !tok.is_empty() => {
171
+
Ok(Some((AuthScheme::DPoP, tok.to_string())))
172
+
}
173
+
_ => Err(
174
+
"Authorization header must be in format 'Bearer <token>' or 'DPoP <token>'"
175
+
.to_string(),
176
+
),
177
+
}
178
+
}
179
+
}
180
+
}
181
+
182
+
/// Resolves a DID to its handle using the PublicResolver, with caching.
183
+
async fn resolve_did_to_handle(
184
+
resolver: &Arc<PublicResolver>,
185
+
cache: &HandleCache,
186
+
did: &str,
187
+
) -> Result<String, String> {
188
+
// Check cache first
189
+
if let Some(handle) = cache.get(did) {
190
+
return Ok(handle);
191
+
}
192
+
193
+
// Parse the DID
194
+
let did_parsed = jacquard_common::types::did::Did::new(did)
195
+
.map_err(|e| format!("Invalid DID: {:?}", e))?;
196
+
197
+
// Resolve the DID document
198
+
let did_doc_response = resolver
199
+
.resolve_did_doc(&did_parsed)
200
+
.await
201
+
.map_err(|e| format!("DID resolution failed: {:?}", e))?;
202
+
203
+
let doc = did_doc_response
204
+
.parse()
205
+
.map_err(|e| format!("Failed to parse DID document: {:?}", e))?;
206
+
207
+
// Extract handle from alsoKnownAs field
208
+
// Format is typically: ["at://handle.example.com"]
209
+
let handle: String = doc
210
+
.also_known_as
211
+
.as_ref()
212
+
.and_then(|aka| {
213
+
aka.iter()
214
+
.find(|uri| uri.starts_with("at://"))
215
+
.map(|uri| uri.strip_prefix("at://").unwrap_or(uri.as_ref()).to_string())
216
+
})
217
+
.ok_or_else(|| "No ATProto handle found in DID document".to_string())?;
218
+
219
+
// Cache the result
220
+
cache.insert(did.to_string(), handle.clone());
221
+
222
+
Ok(handle)
223
+
}
224
+
225
+
// ============================================================================
226
+
// Helper Functions for Creating Middleware State
227
+
// ============================================================================
228
+
229
+
/// Creates an `AuthMiddlewareState` for requiring the handle to end with a specific suffix.
230
+
///
231
+
/// # Example
232
+
/// ```ignore
233
+
/// use axum::middleware::from_fn_with_state;
234
+
/// use crate::auth::{auth_middleware, handle_ends_with};
235
+
///
236
+
/// .route("/protected", get(handler).layer(
237
+
/// from_fn_with_state(handle_ends_with(".blacksky.team", &state), auth_middleware)
238
+
/// ))
239
+
/// ```
240
+
pub fn handle_ends_with(suffix: impl Into<String>, state: &AppState) -> AuthMiddlewareState {
241
+
AuthMiddlewareState {
242
+
app_state: state.clone(),
243
+
rules: AuthRules::HandleEndsWith(suffix.into()),
244
+
}
245
+
}
246
+
247
+
/// Creates an `AuthMiddlewareState` for requiring the handle to end with any of the specified suffixes.
248
+
pub fn handle_ends_with_any<I, T>(suffixes: I, state: &AppState) -> AuthMiddlewareState
249
+
where
250
+
I: IntoIterator<Item = T>,
251
+
T: Into<String>,
252
+
{
253
+
AuthMiddlewareState {
254
+
app_state: state.clone(),
255
+
rules: AuthRules::HandleEndsWithAny(suffixes.into_iter().map(|s| s.into()).collect()),
256
+
}
257
+
}
258
+
259
+
/// Creates an `AuthMiddlewareState` for requiring the DID to equal a specific value.
260
+
pub fn did_equals(did: impl Into<String>, state: &AppState) -> AuthMiddlewareState {
261
+
AuthMiddlewareState {
262
+
app_state: state.clone(),
263
+
rules: AuthRules::DidEquals(did.into()),
264
+
}
265
+
}
266
+
267
+
/// Creates an `AuthMiddlewareState` for requiring the DID to be one of the specified values.
268
+
pub fn did_equals_any<I, T>(dids: I, state: &AppState) -> AuthMiddlewareState
269
+
where
270
+
I: IntoIterator<Item = T>,
271
+
T: Into<String>,
272
+
{
273
+
AuthMiddlewareState {
274
+
app_state: state.clone(),
275
+
rules: AuthRules::DidEqualsAny(dids.into_iter().map(|d| d.into()).collect()),
276
+
}
277
+
}
278
+
279
+
/// Creates an `AuthMiddlewareState` with custom auth rules.
280
+
pub fn with_rules(rules: AuthRules, state: &AppState) -> AuthMiddlewareState {
281
+
AuthMiddlewareState {
282
+
app_state: state.clone(),
283
+
rules,
284
+
}
285
+
}
286
+
287
+
// ============================================================================
288
+
// Scope Helper Functions
289
+
// ============================================================================
290
+
291
+
/// Creates an `AuthMiddlewareState` requiring a specific OAuth scope.
292
+
///
293
+
/// # Example
294
+
/// ```ignore
295
+
/// .route("/xrpc/com.atproto.repo.createRecord",
296
+
/// post(handler).layer(from_fn_with_state(
297
+
/// scope_equals("repo:app.bsky.feed.post", &state),
298
+
/// auth_middleware
299
+
/// )))
300
+
/// ```
301
+
pub fn scope_equals(scope: impl Into<String>, state: &AppState) -> AuthMiddlewareState {
302
+
AuthMiddlewareState {
303
+
app_state: state.clone(),
304
+
rules: AuthRules::ScopeEquals(scope.into()),
305
+
}
306
+
}
307
+
308
+
/// Creates an `AuthMiddlewareState` requiring ANY of the specified scopes (OR logic).
309
+
///
310
+
/// # Example
311
+
/// ```ignore
312
+
/// .route("/xrpc/com.atproto.repo.putRecord",
313
+
/// post(handler).layer(from_fn_with_state(
314
+
/// scope_any(["repo:app.bsky.feed.post", "transition:generic"], &state),
315
+
/// auth_middleware
316
+
/// )))
317
+
/// ```
318
+
pub fn scope_any<I, T>(scopes: I, state: &AppState) -> AuthMiddlewareState
319
+
where
320
+
I: IntoIterator<Item = T>,
321
+
T: Into<String>,
322
+
{
323
+
AuthMiddlewareState {
324
+
app_state: state.clone(),
325
+
rules: AuthRules::ScopeEqualsAny(scopes.into_iter().map(|s| s.into()).collect()),
326
+
}
327
+
}
328
+
329
+
/// Creates an `AuthMiddlewareState` requiring ALL of the specified scopes (AND logic).
330
+
///
331
+
/// # Example
332
+
/// ```ignore
333
+
/// .route("/xrpc/com.atproto.admin.updateAccount",
334
+
/// post(handler).layer(from_fn_with_state(
335
+
/// scope_all(["account:email", "account:repo?action=manage"], &state),
336
+
/// auth_middleware
337
+
/// )))
338
+
/// ```
339
+
pub fn scope_all<I, T>(scopes: I, state: &AppState) -> AuthMiddlewareState
340
+
where
341
+
I: IntoIterator<Item = T>,
342
+
T: Into<String>,
343
+
{
344
+
AuthMiddlewareState {
345
+
app_state: state.clone(),
346
+
rules: AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()),
347
+
}
348
+
}
349
+
350
+
// ============================================================================
351
+
// Combined Rule Helpers (Identity + Scope)
352
+
// ============================================================================
353
+
354
+
/// Creates an `AuthMiddlewareState` requiring handle to end with suffix AND have a specific scope.
355
+
///
356
+
/// # Example
357
+
/// ```ignore
358
+
/// .route("/xrpc/community.blacksky.feed.generator",
359
+
/// post(handler).layer(from_fn_with_state(
360
+
/// handle_ends_with_and_scope(".blacksky.team", "transition:generic", &state),
361
+
/// auth_middleware
362
+
/// )))
363
+
/// ```
364
+
pub fn handle_ends_with_and_scope(
365
+
suffix: impl Into<String>,
366
+
scope: impl Into<String>,
367
+
state: &AppState,
368
+
) -> AuthMiddlewareState {
369
+
AuthMiddlewareState {
370
+
app_state: state.clone(),
371
+
rules: AuthRules::All(vec![
372
+
AuthRules::HandleEndsWith(suffix.into()),
373
+
AuthRules::ScopeEquals(scope.into()),
374
+
]),
375
+
}
376
+
}
377
+
378
+
/// Creates an `AuthMiddlewareState` requiring handle to end with suffix AND have ALL specified scopes.
379
+
///
380
+
/// # Example
381
+
/// ```ignore
382
+
/// .route("/xrpc/community.blacksky.admin.manage",
383
+
/// post(handler).layer(from_fn_with_state(
384
+
/// handle_ends_with_and_scopes(".blacksky.team", ["transition:generic", "identity:*"], &state),
385
+
/// auth_middleware
386
+
/// )))
387
+
/// ```
388
+
pub fn handle_ends_with_and_scopes<I, T>(
389
+
suffix: impl Into<String>,
390
+
scopes: I,
391
+
state: &AppState,
392
+
) -> AuthMiddlewareState
393
+
where
394
+
I: IntoIterator<Item = T>,
395
+
T: Into<String>,
396
+
{
397
+
AuthMiddlewareState {
398
+
app_state: state.clone(),
399
+
rules: AuthRules::All(vec![
400
+
AuthRules::HandleEndsWith(suffix.into()),
401
+
AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()),
402
+
]),
403
+
}
404
+
}
405
+
406
+
/// Creates an `AuthMiddlewareState` requiring DID to equal value AND have a specific scope.
407
+
///
408
+
/// # Example
409
+
/// ```ignore
410
+
/// .route("/xrpc/com.atproto.admin.deleteAccount",
411
+
/// post(handler).layer(from_fn_with_state(
412
+
/// did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "transition:generic", &state),
413
+
/// auth_middleware
414
+
/// )))
415
+
/// ```
416
+
pub fn did_with_scope(
417
+
did: impl Into<String>,
418
+
scope: impl Into<String>,
419
+
state: &AppState,
420
+
) -> AuthMiddlewareState {
421
+
AuthMiddlewareState {
422
+
app_state: state.clone(),
423
+
rules: AuthRules::All(vec![
424
+
AuthRules::DidEquals(did.into()),
425
+
AuthRules::ScopeEquals(scope.into()),
426
+
]),
427
+
}
428
+
}
429
+
430
+
/// Creates an `AuthMiddlewareState` requiring DID to equal value AND have ALL specified scopes.
431
+
///
432
+
/// # Example
433
+
/// ```ignore
434
+
/// .route("/xrpc/com.atproto.admin.fullAccess",
435
+
/// post(handler).layer(from_fn_with_state(
436
+
/// did_with_scopes("did:plc:rnpkyqnmsw4ipey6eotbdnnf", ["transition:generic", "identity:*"], &state),
437
+
/// auth_middleware
438
+
/// )))
439
+
/// ```
440
+
pub fn did_with_scopes<I, T>(
441
+
did: impl Into<String>,
442
+
scopes: I,
443
+
state: &AppState,
444
+
) -> AuthMiddlewareState
445
+
where
446
+
I: IntoIterator<Item = T>,
447
+
T: Into<String>,
448
+
{
449
+
AuthMiddlewareState {
450
+
app_state: state.clone(),
451
+
rules: AuthRules::All(vec![
452
+
AuthRules::DidEquals(did.into()),
453
+
AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()),
454
+
]),
455
+
}
456
+
}
+16
src/auth/mod.rs
+16
src/auth/mod.rs
···
1
+
mod cache;
2
+
mod middleware;
3
+
mod rules;
4
+
5
+
pub use cache::HandleCache;
6
+
pub use middleware::{
7
+
// Core middleware
8
+
auth_middleware, with_rules, AuthMiddlewareState,
9
+
// Identity helpers
10
+
did_equals, did_equals_any, handle_ends_with, handle_ends_with_any,
11
+
// Scope helpers
12
+
scope_all, scope_any, scope_equals,
13
+
// Combined helpers (identity + scope)
14
+
did_with_scope, did_with_scopes, handle_ends_with_and_scope, handle_ends_with_and_scopes,
15
+
};
16
+
pub use rules::{AuthRules, SessionData};
+694
src/auth/rules.rs
+694
src/auth/rules.rs
···
1
+
/// Authentication rules that can be validated against session data
2
+
#[derive(Debug, Clone, PartialEq)]
3
+
pub enum AuthRules {
4
+
/// Handle must end with the specified suffix
5
+
HandleEndsWith(String),
6
+
/// Handle must end with any of the specified suffixes (OR logic)
7
+
HandleEndsWithAny(Vec<String>),
8
+
/// DID must exactly match the specified value
9
+
DidEquals(String),
10
+
/// DID must match any of the specified values (OR logic)
11
+
DidEqualsAny(Vec<String>),
12
+
/// Session must have the specified OAuth scope
13
+
ScopeEquals(String),
14
+
/// Session must have ANY of the specified scopes (OR logic)
15
+
ScopeEqualsAny(Vec<String>),
16
+
/// Session must have ALL of the specified scopes (AND logic)
17
+
ScopeEqualsAll(Vec<String>),
18
+
/// All nested rules must be satisfied (AND logic)
19
+
All(Vec<AuthRules>),
20
+
/// At least one nested rule must be satisfied (OR logic)
21
+
Any(Vec<AuthRules>),
22
+
}
23
+
24
+
/// Session data used for authentication validation
25
+
#[derive(Debug, Clone)]
26
+
pub struct SessionData {
27
+
/// The user's DID
28
+
pub did: String,
29
+
/// The user's handle
30
+
pub handle: String,
31
+
/// OAuth 2.0 scopes granted to this session
32
+
pub scopes: Vec<String>,
33
+
}
34
+
35
+
impl AuthRules {
36
+
/// Validates if the given session data meets the authentication requirements
37
+
pub fn validate(&self, session_data: &SessionData) -> bool {
38
+
match self {
39
+
AuthRules::HandleEndsWith(suffix) => session_data.handle.ends_with(suffix),
40
+
AuthRules::HandleEndsWithAny(suffixes) => {
41
+
suffixes.iter().any(|s| session_data.handle.ends_with(s))
42
+
}
43
+
AuthRules::DidEquals(did) => session_data.did == *did,
44
+
AuthRules::DidEqualsAny(dids) => dids.iter().any(|d| session_data.did == *d),
45
+
AuthRules::ScopeEquals(scope) => has_scope(&session_data.scopes, scope),
46
+
AuthRules::ScopeEqualsAny(scopes) => has_any_scope(&session_data.scopes, scopes),
47
+
AuthRules::ScopeEqualsAll(scopes) => has_all_scopes(&session_data.scopes, scopes),
48
+
AuthRules::All(rules) => rules.iter().all(|r| r.validate(session_data)),
49
+
AuthRules::Any(rules) => rules.iter().any(|r| r.validate(session_data)),
50
+
}
51
+
}
52
+
}
53
+
54
+
/// Checks if the session has a specific scope
55
+
pub fn has_scope(scopes: &[String], required_scope: &str) -> bool {
56
+
scopes.iter().any(|s| s == required_scope)
57
+
}
58
+
59
+
/// Checks if the session has ANY of the required scopes (OR logic)
60
+
pub fn has_any_scope(scopes: &[String], required_scopes: &[String]) -> bool {
61
+
required_scopes.iter().any(|req| has_scope(scopes, req))
62
+
}
63
+
64
+
/// Checks if the session has ALL of the required scopes (AND logic)
65
+
pub fn has_all_scopes(scopes: &[String], required_scopes: &[String]) -> bool {
66
+
required_scopes.iter().all(|req| has_scope(scopes, req))
67
+
}
68
+
69
+
#[cfg(test)]
70
+
mod tests {
71
+
use super::*;
72
+
73
+
fn test_session(did: &str, handle: &str, scopes: Vec<&str>) -> SessionData {
74
+
SessionData {
75
+
did: did.to_string(),
76
+
handle: handle.to_string(),
77
+
scopes: scopes.into_iter().map(|s| s.to_string()).collect(),
78
+
}
79
+
}
80
+
81
+
#[test]
82
+
fn test_handle_ends_with() {
83
+
let rules = AuthRules::HandleEndsWith(".blacksky.team".into());
84
+
85
+
let valid = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]);
86
+
assert!(rules.validate(&valid));
87
+
88
+
let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]);
89
+
assert!(!rules.validate(&invalid));
90
+
}
91
+
92
+
#[test]
93
+
fn test_handle_ends_with_any() {
94
+
let rules = AuthRules::HandleEndsWithAny(vec![".blacksky.team".into(), ".bsky.team".into()]);
95
+
96
+
let valid1 = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]);
97
+
assert!(rules.validate(&valid1));
98
+
99
+
let valid2 = test_session("did:plc:123", "bob.bsky.team", vec!["atproto"]);
100
+
assert!(rules.validate(&valid2));
101
+
102
+
let invalid = test_session("did:plc:123", "charlie.bsky.social", vec!["atproto"]);
103
+
assert!(!rules.validate(&invalid));
104
+
}
105
+
106
+
#[test]
107
+
fn test_did_equals() {
108
+
let rules = AuthRules::DidEquals("did:plc:alice".into());
109
+
110
+
let valid = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]);
111
+
assert!(rules.validate(&valid));
112
+
113
+
let invalid = test_session("did:plc:bob", "bob.bsky.social", vec!["atproto"]);
114
+
assert!(!rules.validate(&invalid));
115
+
}
116
+
117
+
#[test]
118
+
fn test_any_combinator() {
119
+
let rules = AuthRules::Any(vec![
120
+
AuthRules::DidEquals("did:plc:admin".into()),
121
+
AuthRules::HandleEndsWith(".blacksky.team".into()),
122
+
]);
123
+
124
+
// First condition met
125
+
let valid1 = test_session("did:plc:admin", "admin.bsky.social", vec!["atproto"]);
126
+
assert!(rules.validate(&valid1));
127
+
128
+
// Second condition met
129
+
let valid2 = test_session("did:plc:user", "user.blacksky.team", vec!["atproto"]);
130
+
assert!(rules.validate(&valid2));
131
+
132
+
// Neither condition met
133
+
let invalid = test_session("did:plc:user", "user.bsky.social", vec!["atproto"]);
134
+
assert!(!rules.validate(&invalid));
135
+
}
136
+
137
+
#[test]
138
+
fn test_all_combinator() {
139
+
let rules = AuthRules::All(vec![
140
+
AuthRules::HandleEndsWith(".blacksky.team".into()),
141
+
AuthRules::DidEqualsAny(vec!["did:plc:alice".into(), "did:plc:bob".into()]),
142
+
]);
143
+
144
+
// Both conditions met
145
+
let valid = test_session("did:plc:alice", "alice.blacksky.team", vec!["atproto"]);
146
+
assert!(rules.validate(&valid));
147
+
148
+
// Handle wrong
149
+
let invalid1 = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]);
150
+
assert!(!rules.validate(&invalid1));
151
+
152
+
// DID wrong
153
+
let invalid2 = test_session("did:plc:charlie", "charlie.blacksky.team", vec!["atproto"]);
154
+
assert!(!rules.validate(&invalid2));
155
+
}
156
+
157
+
// ========================================================================
158
+
// Scope Tests
159
+
// ========================================================================
160
+
161
+
#[test]
162
+
fn test_scope_equals() {
163
+
let rules = AuthRules::ScopeEquals("transition:generic".into());
164
+
165
+
let valid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]);
166
+
assert!(rules.validate(&valid));
167
+
168
+
let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]);
169
+
assert!(!rules.validate(&invalid));
170
+
}
171
+
172
+
#[test]
173
+
fn test_scope_any() {
174
+
let rules = AuthRules::ScopeEqualsAny(vec![
175
+
"transition:generic".into(),
176
+
"repo:app.bsky.feed.post".into(),
177
+
]);
178
+
179
+
// Has first scope
180
+
let valid1 = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]);
181
+
assert!(rules.validate(&valid1));
182
+
183
+
// Has second scope
184
+
let valid2 = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "repo:app.bsky.feed.post"]);
185
+
assert!(rules.validate(&valid2));
186
+
187
+
// Has neither
188
+
let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]);
189
+
assert!(!rules.validate(&invalid));
190
+
}
191
+
192
+
#[test]
193
+
fn test_scope_all() {
194
+
let rules = AuthRules::ScopeEqualsAll(vec![
195
+
"atproto".into(),
196
+
"transition:generic".into(),
197
+
]);
198
+
199
+
// Has both scopes
200
+
let valid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]);
201
+
assert!(rules.validate(&valid));
202
+
203
+
// Missing one scope
204
+
let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]);
205
+
assert!(!rules.validate(&invalid));
206
+
}
207
+
208
+
// ========================================================================
209
+
// Combined Rules Tests (Identity + Scope)
210
+
// ========================================================================
211
+
212
+
#[test]
213
+
fn test_handle_ends_with_and_scope() {
214
+
let rules = AuthRules::All(vec![
215
+
AuthRules::HandleEndsWith(".blacksky.team".into()),
216
+
AuthRules::ScopeEquals("transition:generic".into()),
217
+
]);
218
+
219
+
// Both conditions met
220
+
let valid = test_session(
221
+
"did:plc:123",
222
+
"alice.blacksky.team",
223
+
vec!["atproto", "transition:generic"],
224
+
);
225
+
assert!(rules.validate(&valid));
226
+
227
+
// Handle correct, scope wrong
228
+
let invalid1 = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]);
229
+
assert!(!rules.validate(&invalid1));
230
+
231
+
// Scope correct, handle wrong
232
+
let invalid2 = test_session(
233
+
"did:plc:123",
234
+
"alice.bsky.social",
235
+
vec!["atproto", "transition:generic"],
236
+
);
237
+
assert!(!rules.validate(&invalid2));
238
+
}
239
+
240
+
#[test]
241
+
fn test_did_with_scope() {
242
+
let rules = AuthRules::All(vec![
243
+
AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()),
244
+
AuthRules::ScopeEquals("transition:generic".into()),
245
+
]);
246
+
247
+
// Both conditions met
248
+
let valid = test_session(
249
+
"did:plc:rnpkyqnmsw4ipey6eotbdnnf",
250
+
"admin.bsky.social",
251
+
vec!["atproto", "transition:generic"],
252
+
);
253
+
assert!(rules.validate(&valid));
254
+
255
+
// DID correct, scope wrong
256
+
let invalid1 = test_session(
257
+
"did:plc:rnpkyqnmsw4ipey6eotbdnnf",
258
+
"admin.bsky.social",
259
+
vec!["atproto"],
260
+
);
261
+
assert!(!rules.validate(&invalid1));
262
+
263
+
// Scope correct, DID wrong
264
+
let invalid2 = test_session(
265
+
"did:plc:wrongdid",
266
+
"admin.bsky.social",
267
+
vec!["atproto", "transition:generic"],
268
+
);
269
+
assert!(!rules.validate(&invalid2));
270
+
}
271
+
272
+
#[test]
273
+
fn test_complex_admin_or_moderator_rule() {
274
+
// Admin DID OR (moderator handle + scope)
275
+
let rules = AuthRules::Any(vec![
276
+
AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()),
277
+
AuthRules::All(vec![
278
+
AuthRules::HandleEndsWith(".mod.team".into()),
279
+
AuthRules::ScopeEquals("account:email".into()),
280
+
]),
281
+
]);
282
+
283
+
// Admin DID (doesn't need scope)
284
+
let admin = test_session("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "admin.bsky.social", vec!["atproto"]);
285
+
assert!(rules.validate(&admin));
286
+
287
+
// Moderator with correct handle and scope
288
+
let mod_valid = test_session(
289
+
"did:plc:somemod",
290
+
"alice.mod.team",
291
+
vec!["atproto", "account:email"],
292
+
);
293
+
assert!(rules.validate(&mod_valid));
294
+
295
+
// Moderator handle but missing scope
296
+
let mod_no_scope = test_session("did:plc:somemod", "alice.mod.team", vec!["atproto"]);
297
+
assert!(!rules.validate(&mod_no_scope));
298
+
299
+
// Has scope but not moderator handle
300
+
let not_mod = test_session(
301
+
"did:plc:someuser",
302
+
"alice.bsky.social",
303
+
vec!["atproto", "account:email"],
304
+
);
305
+
assert!(!rules.validate(¬_mod));
306
+
}
307
+
308
+
// ========================================================================
309
+
// Additional Coverage Tests
310
+
// ========================================================================
311
+
312
+
#[test]
313
+
fn test_did_equals_any() {
314
+
let rules = AuthRules::DidEqualsAny(vec![
315
+
"did:plc:alice123456789012345678".into(),
316
+
"did:plc:bob12345678901234567890".into(),
317
+
"did:plc:charlie12345678901234567".into(),
318
+
]);
319
+
320
+
// First DID matches
321
+
let valid1 = test_session("did:plc:alice123456789012345678", "alice.bsky.social", vec!["atproto"]);
322
+
assert!(rules.validate(&valid1));
323
+
324
+
// Second DID matches
325
+
let valid2 = test_session("did:plc:bob12345678901234567890", "bob.bsky.social", vec!["atproto"]);
326
+
assert!(rules.validate(&valid2));
327
+
328
+
// Third DID matches
329
+
let valid3 = test_session("did:plc:charlie12345678901234567", "charlie.bsky.social", vec!["atproto"]);
330
+
assert!(rules.validate(&valid3));
331
+
332
+
// DID not in list
333
+
let invalid = test_session("did:plc:unknown1234567890123456", "unknown.bsky.social", vec!["atproto"]);
334
+
assert!(!rules.validate(&invalid));
335
+
336
+
// Partial match should fail (prefix)
337
+
let partial = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]);
338
+
assert!(!rules.validate(&partial));
339
+
}
340
+
341
+
// ========================================================================
342
+
// Empty Combinator Edge Cases
343
+
// ========================================================================
344
+
345
+
#[test]
346
+
fn test_empty_any_returns_false() {
347
+
// Any with empty rules should return false (no rule can be satisfied)
348
+
let rules = AuthRules::Any(vec![]);
349
+
let session = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]);
350
+
assert!(!rules.validate(&session));
351
+
}
352
+
353
+
#[test]
354
+
fn test_empty_all_returns_true() {
355
+
// All with empty rules should return true (vacuous truth - all zero rules are satisfied)
356
+
let rules = AuthRules::All(vec![]);
357
+
let session = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]);
358
+
assert!(rules.validate(&session));
359
+
}
360
+
361
+
// ========================================================================
362
+
// Handle Edge Cases
363
+
// ========================================================================
364
+
365
+
#[test]
366
+
fn test_handle_exact_suffix_match() {
367
+
// Handle that IS exactly the suffix should still match
368
+
let rules = AuthRules::HandleEndsWith(".blacksky.team".into());
369
+
370
+
// Handle is exactly the suffix
371
+
let exact = test_session("did:plc:test12345678901234567", ".blacksky.team", vec!["atproto"]);
372
+
assert!(rules.validate(&exact));
373
+
374
+
// Normal case - handle with prefix
375
+
let normal = test_session("did:plc:test12345678901234567", "alice.blacksky.team", vec!["atproto"]);
376
+
assert!(rules.validate(&normal));
377
+
378
+
// Empty handle should not match
379
+
let empty = test_session("did:plc:test12345678901234567", "", vec!["atproto"]);
380
+
assert!(!rules.validate(&empty));
381
+
}
382
+
383
+
// ========================================================================
384
+
// Deeply Nested Combinators
385
+
// ========================================================================
386
+
387
+
#[test]
388
+
fn test_deeply_nested_rules() {
389
+
// Complex nested rule: Any(All(Any(...), ...), All(...))
390
+
// Scenario: (Admin OR VIP) AND (has scope OR team member)
391
+
// OR
392
+
// Specific moderator DID
393
+
let rules = AuthRules::Any(vec![
394
+
// Branch 1: Complex nested condition
395
+
AuthRules::All(vec![
396
+
// Must be admin or VIP
397
+
AuthRules::Any(vec![
398
+
AuthRules::DidEquals("did:plc:admin123456789012345".into()),
399
+
AuthRules::HandleEndsWith(".vip.social".into()),
400
+
]),
401
+
// AND must have scope or be team member
402
+
AuthRules::Any(vec![
403
+
AuthRules::ScopeEquals("transition:generic".into()),
404
+
AuthRules::HandleEndsWith(".team.internal".into()),
405
+
]),
406
+
]),
407
+
// Branch 2: Specific moderator bypass
408
+
AuthRules::DidEquals("did:plc:moderator12345678901".into()),
409
+
]);
410
+
411
+
// Admin with required scope - should pass via Branch 1
412
+
let admin_with_scope = test_session(
413
+
"did:plc:admin123456789012345",
414
+
"admin.bsky.social",
415
+
vec!["atproto", "transition:generic"],
416
+
);
417
+
assert!(rules.validate(&admin_with_scope));
418
+
419
+
// VIP with required scope - should pass via Branch 1
420
+
let vip_with_scope = test_session(
421
+
"did:plc:somevip1234567890123",
422
+
"alice.vip.social",
423
+
vec!["atproto", "transition:generic"],
424
+
);
425
+
assert!(rules.validate(&vip_with_scope));
426
+
427
+
// Moderator bypass - should pass via Branch 2
428
+
let moderator = test_session(
429
+
"did:plc:moderator12345678901",
430
+
"mod.bsky.social",
431
+
vec!["atproto"],
432
+
);
433
+
assert!(rules.validate(&moderator));
434
+
435
+
// Admin without scope and not team member - should fail
436
+
let admin_no_scope = test_session(
437
+
"did:plc:admin123456789012345",
438
+
"admin.bsky.social",
439
+
vec!["atproto"],
440
+
);
441
+
assert!(!rules.validate(&admin_no_scope));
442
+
443
+
// Random user - should fail
444
+
let random = test_session(
445
+
"did:plc:random12345678901234",
446
+
"random.bsky.social",
447
+
vec!["atproto", "transition:generic"],
448
+
);
449
+
assert!(!rules.validate(&random));
450
+
}
451
+
452
+
// ========================================================================
453
+
// ATProto Scope Specification Tests
454
+
// ========================================================================
455
+
456
+
#[test]
457
+
fn test_scope_with_query_params() {
458
+
// ATProto scopes can have query parameters
459
+
// These are treated as literal strings (exact matching)
460
+
461
+
// blob scope with accept parameter
462
+
let blob_rules = AuthRules::ScopeEquals("blob?accept=image/*".into());
463
+
let has_blob = test_session(
464
+
"did:plc:test12345678901234567",
465
+
"test.bsky.social",
466
+
vec!["atproto", "blob?accept=image/*"],
467
+
);
468
+
assert!(blob_rules.validate(&has_blob));
469
+
470
+
// Different query param should not match
471
+
let wrong_blob = test_session(
472
+
"did:plc:test12345678901234567",
473
+
"test.bsky.social",
474
+
vec!["atproto", "blob?accept=video/*"],
475
+
);
476
+
assert!(!blob_rules.validate(&wrong_blob));
477
+
478
+
// account:repo with action parameter
479
+
let account_rules = AuthRules::ScopeEquals("account:repo?action=manage".into());
480
+
let has_account = test_session(
481
+
"did:plc:test12345678901234567",
482
+
"test.bsky.social",
483
+
vec!["atproto", "account:repo?action=manage"],
484
+
);
485
+
assert!(account_rules.validate(&has_account));
486
+
487
+
// Multiple query params
488
+
let multi_param_rules = AuthRules::ScopeEquals("blob?accept=image/*&accept=video/*".into());
489
+
let has_multi = test_session(
490
+
"did:plc:test12345678901234567",
491
+
"test.bsky.social",
492
+
vec!["atproto", "blob?accept=image/*&accept=video/*"],
493
+
);
494
+
assert!(multi_param_rules.validate(&has_multi));
495
+
}
496
+
497
+
#[test]
498
+
fn test_scope_exact_matching_no_wildcards() {
499
+
// ATProto spec: Wildcards do NOT work for collections
500
+
// repo:app.bsky.feed.post should NOT match repo:app.bsky.feed.*
501
+
// This test verifies our exact matching is correct
502
+
503
+
let rules = AuthRules::ScopeEquals("repo:app.bsky.feed.post".into());
504
+
505
+
// Exact match works
506
+
let exact = test_session(
507
+
"did:plc:test12345678901234567",
508
+
"test.bsky.social",
509
+
vec!["atproto", "repo:app.bsky.feed.post"],
510
+
);
511
+
assert!(rules.validate(&exact));
512
+
513
+
// Wildcard scope in JWT should NOT satisfy specific requirement
514
+
// (user has repo:app.bsky.feed.* but endpoint requires repo:app.bsky.feed.post)
515
+
let wildcard = test_session(
516
+
"did:plc:test12345678901234567",
517
+
"test.bsky.social",
518
+
vec!["atproto", "repo:app.bsky.feed.*"],
519
+
);
520
+
assert!(!rules.validate(&wildcard));
521
+
522
+
// Different collection should not match
523
+
let different = test_session(
524
+
"did:plc:test12345678901234567",
525
+
"test.bsky.social",
526
+
vec!["atproto", "repo:app.bsky.feed.like"],
527
+
);
528
+
assert!(!rules.validate(&different));
529
+
530
+
// Prefix should not match
531
+
let prefix = test_session(
532
+
"did:plc:test12345678901234567",
533
+
"test.bsky.social",
534
+
vec!["atproto", "repo:app.bsky.feed"],
535
+
);
536
+
assert!(!rules.validate(&prefix));
537
+
538
+
// repo:* should not match specific collection (exact matching)
539
+
let full_wildcard = test_session(
540
+
"did:plc:test12345678901234567",
541
+
"test.bsky.social",
542
+
vec!["atproto", "repo:*"],
543
+
);
544
+
assert!(!rules.validate(&full_wildcard));
545
+
}
546
+
547
+
#[test]
548
+
fn test_scope_case_sensitivity() {
549
+
// OAuth scopes are case-sensitive per RFC 6749
550
+
551
+
let rules = AuthRules::ScopeEquals("atproto".into());
552
+
553
+
// Exact case matches
554
+
let exact = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]);
555
+
assert!(rules.validate(&exact));
556
+
557
+
// UPPERCASE should NOT match
558
+
let upper = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["ATPROTO"]);
559
+
assert!(!rules.validate(&upper));
560
+
561
+
// Mixed case should NOT match
562
+
let mixed = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["AtProto"]);
563
+
assert!(!rules.validate(&mixed));
564
+
565
+
// Test with namespaced scope
566
+
let ns_rules = AuthRules::ScopeEquals("transition:generic".into());
567
+
let ns_upper = test_session(
568
+
"did:plc:test12345678901234567",
569
+
"test.bsky.social",
570
+
vec!["TRANSITION:GENERIC"],
571
+
);
572
+
assert!(!ns_rules.validate(&ns_upper));
573
+
}
574
+
575
+
#[test]
576
+
fn test_scope_with_wildcards_exact_match() {
577
+
// Wildcard scopes like blob:*/* and identity:* are stored as literal strings
578
+
// The middleware does exact matching, so JWT must contain the exact string
579
+
580
+
// blob:*/* - full blob wildcard
581
+
let blob_rules = AuthRules::ScopeEquals("blob:*/*".into());
582
+
let has_blob_wildcard = test_session(
583
+
"did:plc:test12345678901234567",
584
+
"test.bsky.social",
585
+
vec!["atproto", "blob:*/*"],
586
+
);
587
+
assert!(blob_rules.validate(&has_blob_wildcard));
588
+
589
+
// Specific blob type should NOT match blob:*/* requirement
590
+
let has_specific_blob = test_session(
591
+
"did:plc:test12345678901234567",
592
+
"test.bsky.social",
593
+
vec!["atproto", "blob:image/png"],
594
+
);
595
+
assert!(!blob_rules.validate(&has_specific_blob));
596
+
597
+
// identity:* - full identity wildcard
598
+
let identity_rules = AuthRules::ScopeEquals("identity:*".into());
599
+
let has_identity_wildcard = test_session(
600
+
"did:plc:test12345678901234567",
601
+
"test.bsky.social",
602
+
vec!["atproto", "identity:*"],
603
+
);
604
+
assert!(identity_rules.validate(&has_identity_wildcard));
605
+
606
+
// Specific identity scope should NOT match identity:* requirement
607
+
let has_specific_identity = test_session(
608
+
"did:plc:test12345678901234567",
609
+
"test.bsky.social",
610
+
vec!["atproto", "identity:handle"],
611
+
);
612
+
assert!(!identity_rules.validate(&has_specific_identity));
613
+
}
614
+
615
+
// ========================================================================
616
+
// Scope Helper Function Tests
617
+
// ========================================================================
618
+
619
+
#[test]
620
+
fn test_has_scope_helper() {
621
+
let scopes: Vec<String> = vec![
622
+
"atproto".to_string(),
623
+
"transition:generic".to_string(),
624
+
"repo:app.bsky.feed.post".to_string(),
625
+
];
626
+
627
+
// Present scopes
628
+
assert!(has_scope(&scopes, "atproto"));
629
+
assert!(has_scope(&scopes, "transition:generic"));
630
+
assert!(has_scope(&scopes, "repo:app.bsky.feed.post"));
631
+
632
+
// Absent scopes
633
+
assert!(!has_scope(&scopes, "identity:*"));
634
+
assert!(!has_scope(&scopes, ""));
635
+
assert!(!has_scope(&scopes, "ATPROTO")); // Case sensitive
636
+
637
+
// Empty scopes list
638
+
let empty: Vec<String> = vec![];
639
+
assert!(!has_scope(&empty, "atproto"));
640
+
}
641
+
642
+
#[test]
643
+
fn test_has_any_scope_helper() {
644
+
let scopes: Vec<String> = vec![
645
+
"atproto".to_string(),
646
+
"repo:app.bsky.feed.post".to_string(),
647
+
];
648
+
649
+
// Has one of the required scopes
650
+
let required1 = vec!["transition:generic".to_string(), "atproto".to_string()];
651
+
assert!(has_any_scope(&scopes, &required1));
652
+
653
+
// Has none of the required scopes
654
+
let required2 = vec!["transition:generic".to_string(), "identity:*".to_string()];
655
+
assert!(!has_any_scope(&scopes, &required2));
656
+
657
+
// Empty required list - should return false (no scope to match)
658
+
let empty_required: Vec<String> = vec![];
659
+
assert!(!has_any_scope(&scopes, &empty_required));
660
+
661
+
// Empty scopes list
662
+
let empty_scopes: Vec<String> = vec![];
663
+
assert!(!has_any_scope(&empty_scopes, &required1));
664
+
}
665
+
666
+
#[test]
667
+
fn test_has_all_scopes_helper() {
668
+
let scopes: Vec<String> = vec![
669
+
"atproto".to_string(),
670
+
"transition:generic".to_string(),
671
+
"repo:app.bsky.feed.post".to_string(),
672
+
];
673
+
674
+
// Has all required scopes
675
+
let required1 = vec!["atproto".to_string(), "transition:generic".to_string()];
676
+
assert!(has_all_scopes(&scopes, &required1));
677
+
678
+
// Missing one required scope
679
+
let required2 = vec!["atproto".to_string(), "identity:*".to_string()];
680
+
assert!(!has_all_scopes(&scopes, &required2));
681
+
682
+
// Empty required list - should return true (vacuously all zero required are present)
683
+
let empty_required: Vec<String> = vec![];
684
+
assert!(has_all_scopes(&scopes, &empty_required));
685
+
686
+
// Empty scopes list with requirements
687
+
let empty_scopes: Vec<String> = vec![];
688
+
assert!(!has_all_scopes(&empty_scopes, &required1));
689
+
690
+
// Single scope requirement
691
+
let single = vec!["atproto".to_string()];
692
+
assert!(has_all_scopes(&scopes, &single));
693
+
}
694
+
}
+247
src/gate.rs
+247
src/gate.rs
···
1
+
use crate::AppState;
2
+
use crate::helpers::{generate_gate_token, json_error_response};
3
+
use axum::Form;
4
+
use axum::extract::{Query, State};
5
+
use axum::http::StatusCode;
6
+
use axum::response::{IntoResponse, Redirect, Response};
7
+
use axum_template::RenderHtml;
8
+
use chrono::{DateTime, Utc};
9
+
use serde::{Deserialize, Serialize};
10
+
use std::env;
11
+
use tracing::log;
12
+
13
+
#[derive(Deserialize)]
14
+
pub struct GateQuery {
15
+
handle: String,
16
+
state: String,
17
+
#[serde(default)]
18
+
error: Option<String>,
19
+
#[serde(default)]
20
+
redirect_url: Option<String>,
21
+
}
22
+
23
+
#[derive(Deserialize, Serialize)]
24
+
pub struct CaptchaPage {
25
+
handle: String,
26
+
state: String,
27
+
captcha_site_key: String,
28
+
error_message: Option<String>,
29
+
pds: String,
30
+
redirect_url: Option<String>,
31
+
}
32
+
33
+
#[derive(Deserialize)]
34
+
pub struct CaptchaForm {
35
+
#[serde(rename = "h-captcha-response")]
36
+
h_captcha_response: String,
37
+
#[serde(default)]
38
+
redirect_url: Option<String>,
39
+
}
40
+
41
+
/// GET /gate - Display the captcha page
42
+
pub async fn get_gate(
43
+
Query(params): Query<GateQuery>,
44
+
State(state): State<AppState>,
45
+
) -> impl IntoResponse {
46
+
let hcaptcha_site_key = match env::var("PDS_HCAPTCHA_SITE_KEY") {
47
+
Ok(key) => key,
48
+
Err(_) => {
49
+
return json_error_response(
50
+
StatusCode::INTERNAL_SERVER_ERROR,
51
+
"ServerError",
52
+
"hCaptcha is not configured",
53
+
)
54
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
55
+
}
56
+
};
57
+
58
+
let error_message = match params.error {
59
+
None => None,
60
+
Some(error) => Some(html_escape::encode_safe(&error).to_string()),
61
+
};
62
+
63
+
RenderHtml(
64
+
"captcha.hbs",
65
+
state.template_engine,
66
+
CaptchaPage {
67
+
handle: params.handle,
68
+
state: params.state,
69
+
captcha_site_key: hcaptcha_site_key,
70
+
error_message,
71
+
pds: state.app_config.pds_service_did.replace("did:web:", ""),
72
+
redirect_url: params.redirect_url,
73
+
},
74
+
)
75
+
.into_response()
76
+
}
77
+
78
+
/// POST /gate - Verify captcha and redirect
79
+
pub async fn post_gate(
80
+
State(state): State<AppState>,
81
+
Query(params): Query<GateQuery>,
82
+
Form(form): Form<CaptchaForm>,
83
+
) -> Response {
84
+
// Verify hCaptcha response
85
+
let hcaptcha_secret = match env::var("PDS_HCAPTCHA_SECRET_KEY") {
86
+
Ok(secret) => secret,
87
+
Err(_) => {
88
+
return json_error_response(
89
+
StatusCode::INTERNAL_SERVER_ERROR,
90
+
"ServerError",
91
+
"hCaptcha is not configured",
92
+
)
93
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
94
+
}
95
+
};
96
+
97
+
let client = match reqwest::Client::builder()
98
+
.timeout(std::time::Duration::from_secs(10))
99
+
.build()
100
+
{
101
+
Ok(c) => c,
102
+
Err(e) => {
103
+
log::error!("Failed to create HTTP client: {}", e);
104
+
return json_error_response(
105
+
StatusCode::INTERNAL_SERVER_ERROR,
106
+
"ServerError",
107
+
"Failed to verify captcha",
108
+
)
109
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
110
+
}
111
+
};
112
+
113
+
#[derive(Deserialize, Serialize)]
114
+
struct HCaptchaResponse {
115
+
success: bool,
116
+
challenge_ts: DateTime<Utc>,
117
+
hostname: String,
118
+
#[serde(rename = "error-codes", default)]
119
+
error_codes: Vec<String>,
120
+
}
121
+
122
+
let verification_result = client
123
+
.post("https://api.hcaptcha.com/siteverify")
124
+
.form(&[
125
+
("secret", hcaptcha_secret.as_str()),
126
+
("response", form.h_captcha_response.as_str()),
127
+
])
128
+
.send()
129
+
.await;
130
+
131
+
let verification_response = match verification_result {
132
+
Ok(resp) => resp,
133
+
Err(e) => {
134
+
log::error!("Failed to verify hCaptcha: {}", e);
135
+
136
+
return Redirect::to(&format!(
137
+
"/gate?handle={}&state={}&error={}",
138
+
url_encode(¶ms.handle),
139
+
url_encode(¶ms.state),
140
+
url_encode("Verification failed. Please try again.")
141
+
))
142
+
.into_response();
143
+
}
144
+
};
145
+
146
+
let captcha_result: HCaptchaResponse = match verification_response.json().await {
147
+
Ok(result) => result,
148
+
Err(e) => {
149
+
log::error!("Failed to parse hCaptcha response: {}", e);
150
+
151
+
return Redirect::to(&format!(
152
+
"/gate?handle={}&state={}&error={}",
153
+
url_encode(¶ms.handle),
154
+
url_encode(¶ms.state),
155
+
url_encode("Verification failed. Please try again.")
156
+
))
157
+
.into_response();
158
+
}
159
+
};
160
+
161
+
if !captcha_result.success {
162
+
log::warn!(
163
+
"hCaptcha verification failed for handle {}: {:?}",
164
+
params.handle,
165
+
captcha_result.error_codes
166
+
);
167
+
return Redirect::to(&format!(
168
+
"/gate?handle={}&state={}&error={}",
169
+
url_encode(¶ms.handle),
170
+
url_encode(¶ms.state),
171
+
url_encode("Verification failed. Please try again.")
172
+
))
173
+
.into_response();
174
+
}
175
+
176
+
// Generate secure JWE verification token
177
+
let code = match generate_gate_token(¶ms.handle, &state.app_config.gate_jwe_key) {
178
+
Ok(token) => token,
179
+
Err(e) => {
180
+
log::error!("Failed to generate gate token: {}", e);
181
+
return json_error_response(
182
+
StatusCode::INTERNAL_SERVER_ERROR,
183
+
"ServerError",
184
+
"Failed to create verification code",
185
+
)
186
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
187
+
}
188
+
};
189
+
190
+
let now = Utc::now();
191
+
192
+
// Store the encrypted token in the database
193
+
let result = sqlx::query(
194
+
"INSERT INTO gate_codes (code, handle, created_at)
195
+
VALUES (?, ?, ?)",
196
+
)
197
+
.bind(&code)
198
+
.bind(¶ms.handle)
199
+
.bind(now)
200
+
.execute(&state.pds_gatekeeper_pool)
201
+
.await;
202
+
203
+
if let Err(e) = result {
204
+
log::error!("Failed to store gate code: {}", e);
205
+
return json_error_response(
206
+
StatusCode::INTERNAL_SERVER_ERROR,
207
+
"ServerError",
208
+
"Failed to create verification code",
209
+
)
210
+
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response());
211
+
}
212
+
213
+
// Redirects by origin if it's found. If not redirect to the configured URL.
214
+
let mut base_redirect = state.app_config.default_successful_redirect_url.clone();
215
+
if let Some(ref redirect_url) = form.redirect_url {
216
+
let trimmed = redirect_url.trim();
217
+
if !trimmed.is_empty()
218
+
&& (trimmed.starts_with("https://") || trimmed.starts_with("http://"))
219
+
{
220
+
base_redirect = trimmed.trim_end_matches('/').to_string();
221
+
}
222
+
}
223
+
224
+
let base_redirect = match state
225
+
.app_config
226
+
.captcha_success_redirects
227
+
.contains(&base_redirect)
228
+
{
229
+
true => base_redirect,
230
+
false => state.app_config.default_successful_redirect_url.clone(),
231
+
};
232
+
233
+
// Redirect to client app with code and state
234
+
let redirect_url = format!(
235
+
"{}/?code={}&state={}",
236
+
base_redirect,
237
+
url_encode(&code),
238
+
url_encode(¶ms.state)
239
+
);
240
+
241
+
Redirect::to(&redirect_url).into_response()
242
+
}
243
+
244
+
/// Simple URL encode function
245
+
fn url_encode(s: &str) -> String {
246
+
urlencoding::encode(s).to_string()
247
+
}
+175
-13
src/helpers.rs
+175
-13
src/helpers.rs
···
1
1
use crate::AppState;
2
2
use crate::helpers::TokenCheckError::InvalidToken;
3
3
use anyhow::anyhow;
4
-
use axum::body::{Body, to_bytes};
5
-
use axum::extract::Request;
6
-
use axum::http::header::CONTENT_TYPE;
7
-
use axum::http::{HeaderMap, StatusCode, Uri};
8
-
use axum::response::{IntoResponse, Response};
4
+
use axum::{
5
+
body::{Body, to_bytes},
6
+
extract::Request,
7
+
http::header::CONTENT_TYPE,
8
+
http::{HeaderMap, StatusCode, Uri},
9
+
response::{IntoResponse, Response},
10
+
};
9
11
use axum_template::TemplateEngine;
10
12
use chrono::Utc;
11
-
use lettre::message::{MultiPart, SinglePart, header};
12
-
use lettre::{AsyncTransport, Message};
13
+
use jacquard_common::{
14
+
service_auth, service_auth::PublicKey, types::did::Did, types::did_doc::VerificationMethod,
15
+
types::nsid::Nsid,
16
+
};
17
+
use jacquard_identity::{PublicResolver, resolver::IdentityResolver};
18
+
use josekit::jwe::alg::direct::DirectJweAlgorithm;
19
+
use lettre::{
20
+
AsyncTransport, Message,
21
+
message::{MultiPart, SinglePart, header},
22
+
};
13
23
use rand::Rng;
14
24
use serde::de::DeserializeOwned;
15
25
use serde_json::{Map, Value};
16
26
use sha2::{Digest, Sha256};
17
27
use sqlx::SqlitePool;
18
-
use std::env;
28
+
use std::sync::Arc;
19
29
use tracing::{error, log};
20
30
21
31
///Used to generate the email 2fa code
···
40
50
where
41
51
T: DeserializeOwned,
42
52
{
43
-
let uri = format!("{}{}", state.pds_base_url, path);
53
+
let uri = format!("{}{}", state.app_config.pds_base_url, path);
44
54
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
45
55
46
56
let result = state
···
333
343
let email_body = state
334
344
.template_engine
335
345
.render("two_factor_code.hbs", email_data)?;
336
-
let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT")
337
-
.unwrap_or("Sign in to Bluesky".to_string());
338
346
339
347
let email_message = Message::builder()
340
348
//TODO prob get the proper type in the state
341
-
.from(state.mailer_from.parse()?)
349
+
.from(state.app_config.mailer_from.parse()?)
342
350
.to(email.parse()?)
343
-
.subject(email_subject)
351
+
.subject(&state.app_config.email_subject)
344
352
.multipart(
345
353
MultiPart::alternative() // This is composed of two parts.
346
354
.singlepart(
···
523
531
524
532
format!("{masked_local}@{masked_domain}")
525
533
}
534
+
535
+
pub enum VerifyServiceAuthError {
536
+
AuthFailed,
537
+
Error(anyhow::Error),
538
+
}
539
+
540
+
/// Verifies the service auth token that is appended to an XRPC proxy request
541
+
pub async fn verify_service_auth(
542
+
jwt: &str,
543
+
lxm: &Nsid<'static>,
544
+
public_resolver: Arc<PublicResolver>,
545
+
service_did: &Did<'static>,
546
+
//The did of the user wanting to create an account
547
+
requested_did: &Did<'static>,
548
+
) -> Result<(), VerifyServiceAuthError> {
549
+
let parsed =
550
+
service_auth::parse_jwt(jwt).map_err(|e| VerifyServiceAuthError::Error(e.into()))?;
551
+
552
+
let claims = parsed.claims();
553
+
554
+
let did_doc = public_resolver
555
+
.resolve_did_doc(&requested_did)
556
+
.await
557
+
.map_err(|err| {
558
+
log::error!("Error resolving the service auth for: {}", claims.iss);
559
+
return VerifyServiceAuthError::Error(err.into());
560
+
})?;
561
+
562
+
// Parse the DID document response to get verification methods
563
+
let doc = did_doc.parse().map_err(|err| {
564
+
log::error!("Error parsing the service auth did doc: {}", claims.iss);
565
+
VerifyServiceAuthError::Error(anyhow::anyhow!(err))
566
+
})?;
567
+
568
+
let verification_methods = doc.verification_method.as_deref().ok_or_else(|| {
569
+
VerifyServiceAuthError::Error(anyhow::anyhow!(
570
+
"No verification methods in did doc: {}",
571
+
&claims.iss
572
+
))
573
+
})?;
574
+
575
+
let signing_key = extract_signing_key(verification_methods).ok_or_else(|| {
576
+
VerifyServiceAuthError::Error(anyhow::anyhow!(
577
+
"No signing key found in did doc: {}",
578
+
&claims.iss
579
+
))
580
+
})?;
581
+
582
+
service_auth::verify_signature(&parsed, &signing_key).map_err(|err| {
583
+
log::error!("Error verifying service auth signature: {}", err);
584
+
VerifyServiceAuthError::AuthFailed
585
+
})?;
586
+
587
+
// Now validate claims (audience, expiration, etc.)
588
+
claims.validate(service_did).map_err(|e| {
589
+
log::error!("Error validating service auth claims: {}", e);
590
+
VerifyServiceAuthError::AuthFailed
591
+
})?;
592
+
593
+
if claims.aud != *service_did {
594
+
log::error!("Invalid audience (did:web): {}", claims.aud);
595
+
return Err(VerifyServiceAuthError::AuthFailed);
596
+
}
597
+
598
+
let lxm_from_claims = claims.lxm.as_ref().ok_or_else(|| {
599
+
VerifyServiceAuthError::Error(anyhow::anyhow!("No lxm claim in service auth JWT"))
600
+
})?;
601
+
602
+
if lxm_from_claims != lxm {
603
+
return Err(VerifyServiceAuthError::Error(anyhow::anyhow!(
604
+
"Invalid XRPC endpoint requested"
605
+
)));
606
+
}
607
+
Ok(())
608
+
}
609
+
610
+
/// Ripped from Jacquard
611
+
///
612
+
/// Extract the signing key from a DID document's verification methods.
613
+
///
614
+
/// This looks for a key with type "atproto" or the first available key
615
+
/// if no atproto-specific key is found.
616
+
fn extract_signing_key(methods: &[VerificationMethod]) -> Option<PublicKey> {
617
+
// First try to find an atproto-specific key
618
+
let atproto_method = methods
619
+
.iter()
620
+
.find(|m| m.r#type.as_ref() == "Multikey" || m.r#type.as_ref() == "atproto");
621
+
622
+
let method = atproto_method.or_else(|| methods.first())?;
623
+
624
+
// Parse the multikey
625
+
let public_key_multibase = method.public_key_multibase.as_ref()?;
626
+
627
+
// Decode multibase
628
+
let (_, key_bytes) = multibase::decode(public_key_multibase.as_ref()).ok()?;
629
+
630
+
// First two bytes are the multicodec prefix
631
+
if key_bytes.len() < 2 {
632
+
return None;
633
+
}
634
+
635
+
let codec = &key_bytes[..2];
636
+
let key_material = &key_bytes[2..];
637
+
638
+
match codec {
639
+
// p256-pub (0x1200)
640
+
[0x80, 0x24] => PublicKey::from_p256_bytes(key_material).ok(),
641
+
// secp256k1-pub (0xe7)
642
+
[0xe7, 0x01] => PublicKey::from_k256_bytes(key_material).ok(),
643
+
_ => None,
644
+
}
645
+
}
646
+
647
+
/// Payload for gate JWE tokens
648
+
#[derive(serde::Serialize, serde::Deserialize, Debug)]
649
+
pub struct GateTokenPayload {
650
+
pub handle: String,
651
+
pub created_at: String,
652
+
}
653
+
654
+
/// Generate a secure JWE token for gate verification
655
+
pub fn generate_gate_token(handle: &str, encryption_key: &[u8]) -> Result<String, anyhow::Error> {
656
+
use josekit::jwe::{JweHeader, alg::direct::DirectJweAlgorithm};
657
+
658
+
let payload = GateTokenPayload {
659
+
handle: handle.to_string(),
660
+
created_at: Utc::now().to_rfc3339(),
661
+
};
662
+
663
+
let payload_json = serde_json::to_string(&payload)?;
664
+
665
+
let mut header = JweHeader::new();
666
+
header.set_token_type("JWT");
667
+
header.set_content_encryption("A128CBC-HS256");
668
+
669
+
let encrypter = DirectJweAlgorithm::Dir.encrypter_from_bytes(encryption_key)?;
670
+
671
+
// Encrypt
672
+
let jwe = josekit::jwe::serialize_compact(payload_json.as_bytes(), &header, &encrypter)?;
673
+
674
+
Ok(jwe)
675
+
}
676
+
677
+
/// Verify and decrypt a gate JWE token, returning the payload if valid
678
+
pub fn verify_gate_token(
679
+
token: &str,
680
+
encryption_key: &[u8],
681
+
) -> Result<GateTokenPayload, anyhow::Error> {
682
+
let decrypter = DirectJweAlgorithm::Dir.decrypter_from_bytes(encryption_key)?;
683
+
let (payload_bytes, _header) = josekit::jwe::deserialize_compact(token, &decrypter)?;
684
+
let payload: GateTokenPayload = serde_json::from_slice(&payload_bytes)?;
685
+
686
+
Ok(payload)
687
+
}
+166
-30
src/main.rs
+166
-30
src/main.rs
···
1
1
#![warn(clippy::unwrap_used)]
2
+
use crate::gate::{get_gate, post_gate};
2
3
use crate::oauth_provider::sign_in;
3
-
use crate::xrpc::com_atproto_server::{create_account, create_session, get_session, update_email};
4
-
use axum::body::Body;
5
-
use axum::handler::Handler;
6
-
use axum::http::{Method, header};
7
-
use axum::middleware as ax_middleware;
8
-
use axum::routing::post;
9
-
use axum::{Router, routing::get};
4
+
use crate::xrpc::com_atproto_server::{
5
+
create_account, create_session, describe_server, get_session, update_email,
6
+
};
7
+
use axum::{
8
+
Router,
9
+
body::Body,
10
+
handler::Handler,
11
+
http::{Method, header},
12
+
middleware as ax_middleware,
13
+
routing::get,
14
+
routing::post,
15
+
};
10
16
use axum_template::engine::Engine;
11
17
use handlebars::Handlebars;
12
-
use hyper_util::client::legacy::connect::HttpConnector;
13
-
use hyper_util::rt::TokioExecutor;
18
+
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
19
+
use jacquard_common::types::did::Did;
20
+
use jacquard_identity::{PublicResolver, resolver::PlcSource};
14
21
use lettre::{AsyncSmtpTransport, Tokio1Executor};
22
+
use rand::Rng;
15
23
use rust_embed::RustEmbed;
16
24
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
17
25
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
18
26
use std::path::Path;
27
+
use std::sync::Arc;
19
28
use std::time::Duration;
20
29
use std::{env, net::SocketAddr};
21
-
use tower_governor::GovernorLayer;
22
-
use tower_governor::governor::GovernorConfigBuilder;
23
-
use tower_http::compression::CompressionLayer;
24
-
use tower_http::cors::{Any, CorsLayer};
30
+
use tower_governor::{
31
+
GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
32
+
};
33
+
use tower_http::{
34
+
compression::CompressionLayer,
35
+
cors::{Any, CorsLayer},
36
+
};
25
37
use tracing::log;
26
38
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
27
39
40
+
mod auth;
41
+
mod gate;
28
42
pub mod helpers;
29
43
mod middleware;
30
44
mod oauth_provider;
···
37
51
#[include = "*.hbs"]
38
52
struct EmailTemplates;
39
53
54
+
#[derive(RustEmbed)]
55
+
#[folder = "html_templates"]
56
+
#[include = "*.hbs"]
57
+
struct HtmlTemplates;
58
+
59
+
/// Mostly the env variables that are used in the app
60
+
#[derive(Clone, Debug)]
61
+
pub struct AppConfig {
62
+
pds_base_url: String,
63
+
mailer_from: String,
64
+
email_subject: String,
65
+
allow_only_migrations: bool,
66
+
use_captcha: bool,
67
+
//The url to redirect to after a successful captcha. Defaults to https://bsky.app, but you may have another social-app fork you rather your users use
68
+
//that need to capture this redirect url for creating an account
69
+
default_successful_redirect_url: String,
70
+
pds_service_did: Did<'static>,
71
+
gate_jwe_key: Vec<u8>,
72
+
captcha_success_redirects: Vec<String>,
73
+
}
74
+
75
+
impl AppConfig {
76
+
pub fn new() -> Self {
77
+
let pds_base_url =
78
+
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
79
+
let mailer_from = env::var("PDS_EMAIL_FROM_ADDRESS")
80
+
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
81
+
//Hack not my favorite, but it does work
82
+
let allow_only_migrations = env::var("GATEKEEPER_ALLOW_ONLY_MIGRATIONS")
83
+
.map(|val| val.parse::<bool>().unwrap_or(false))
84
+
.unwrap_or(false);
85
+
86
+
let use_captcha = env::var("GATEKEEPER_CREATE_ACCOUNT_CAPTCHA")
87
+
.map(|val| val.parse::<bool>().unwrap_or(false))
88
+
.unwrap_or(false);
89
+
90
+
// PDS_SERVICE_DID is the did:web if set, if not it's PDS_HOSTNAME
91
+
let pds_service_did =
92
+
env::var("PDS_SERVICE_DID").unwrap_or_else(|_| match env::var("PDS_HOSTNAME") {
93
+
Ok(pds_hostname) => format!("did:web:{}", pds_hostname),
94
+
Err(_) => {
95
+
panic!("PDS_HOSTNAME or PDS_SERVICE_DID must be set in your pds.env file")
96
+
}
97
+
});
98
+
99
+
let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT")
100
+
.unwrap_or("Sign in to Bluesky".to_string());
101
+
102
+
// Load or generate JWE encryption key (32 bytes for AES-256)
103
+
let gate_jwe_key = env::var("GATEKEEPER_JWE_KEY")
104
+
.ok()
105
+
.and_then(|key_hex| hex::decode(key_hex).ok())
106
+
.unwrap_or_else(|| {
107
+
// Generate a random 32-byte key if not provided
108
+
let key: Vec<u8> = (0..32).map(|_| rand::rng().random()).collect();
109
+
log::warn!("WARNING: No GATEKEEPER_JWE_KEY found in the environment. Generated random key (hex): {}", hex::encode(&key));
110
+
log::warn!("This is not strictly needed unless you scale PDS Gatekeeper. Will not also be able to verify tokens between reboots, but they are short lived (5mins).");
111
+
key
112
+
});
113
+
114
+
if gate_jwe_key.len() != 32 {
115
+
panic!(
116
+
"GATEKEEPER_JWE_KEY must be 32 bytes (64 hex characters) for AES-256 encryption"
117
+
);
118
+
}
119
+
120
+
let captcha_success_redirects = match env::var("GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS") {
121
+
Ok(from_env) => from_env.split(",").map(|s| s.trim().to_string()).collect(),
122
+
Err(_) => {
123
+
vec![
124
+
String::from("https://bsky.app"),
125
+
String::from("https://pdsmoover.com"),
126
+
String::from("https://blacksky.community"),
127
+
String::from("https://tektite.cc"),
128
+
]
129
+
}
130
+
};
131
+
132
+
AppConfig {
133
+
pds_base_url,
134
+
mailer_from,
135
+
email_subject,
136
+
allow_only_migrations,
137
+
use_captcha,
138
+
default_successful_redirect_url: env::var("GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT")
139
+
.unwrap_or("https://bsky.app".to_string()),
140
+
pds_service_did: pds_service_did
141
+
.parse()
142
+
.expect("PDS_SERVICE_DID is not a valid did or could not infer from PDS_HOSTNAME"),
143
+
gate_jwe_key,
144
+
captcha_success_redirects,
145
+
}
146
+
}
147
+
}
148
+
40
149
#[derive(Clone)]
41
150
pub struct AppState {
42
151
account_pool: SqlitePool,
43
152
pds_gatekeeper_pool: SqlitePool,
44
153
reverse_proxy_client: HyperUtilClient,
45
-
pds_base_url: String,
46
154
mailer: AsyncSmtpTransport<Tokio1Executor>,
47
-
mailer_from: String,
48
155
template_engine: Engine<Handlebars<'static>>,
156
+
resolver: Arc<PublicResolver>,
157
+
handle_cache: auth::HandleCache,
158
+
app_config: AppConfig,
49
159
}
50
160
51
161
async fn root_handler() -> impl axum::response::IntoResponse {
···
136
246
//Emailer set up
137
247
let smtp_url =
138
248
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
139
-
let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS")
140
-
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
141
249
142
250
let mailer: AsyncSmtpTransport<Tokio1Executor> =
143
251
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
···
154
262
let _ = hbs.register_embed_templates::<EmailTemplates>();
155
263
}
156
264
157
-
let pds_base_url =
158
-
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
265
+
let _ = hbs.register_embed_templates::<HtmlTemplates>();
266
+
267
+
//Reads the PLC source from the pds env's or defaults to ol faithful
268
+
let plc_source_url =
269
+
env::var("PDS_DID_PLC_URL").unwrap_or_else(|_| "https://plc.directory".to_string());
270
+
let plc_source = PlcSource::PlcDirectory {
271
+
base: plc_source_url.parse().unwrap(),
272
+
};
273
+
let mut resolver = PublicResolver::default();
274
+
resolver = resolver.with_plc_source(plc_source.clone());
159
275
160
276
let state = AppState {
161
277
account_pool,
162
278
pds_gatekeeper_pool,
163
279
reverse_proxy_client: client,
164
-
pds_base_url,
165
280
mailer,
166
-
mailer_from: sent_from,
167
281
template_engine: Engine::from(hbs),
282
+
resolver: Arc::new(resolver),
283
+
handle_cache: auth::HandleCache::new(),
284
+
app_config: AppConfig::new(),
168
285
};
169
286
170
287
// Rate limiting
171
288
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
172
-
let create_session_governor_conf = GovernorConfigBuilder::default()
289
+
let captcha_governor_conf = GovernorConfigBuilder::default()
173
290
.per_second(60)
174
291
.burst_size(5)
292
+
.key_extractor(SmartIpKeyExtractor)
175
293
.finish()
176
294
.expect("failed to create governor config for create session. this should not happen and is a bug");
177
295
···
179
297
let sign_in_governor_conf = GovernorConfigBuilder::default()
180
298
.per_second(60)
181
299
.burst_size(5)
300
+
.key_extractor(SmartIpKeyExtractor)
182
301
.finish()
183
302
.expect(
184
303
"failed to create governor config for sign in. this should not happen and is a bug",
···
207
326
create_account_governor_conf.burst_size(burst);
208
327
}
209
328
210
-
let create_account_governor_conf = create_account_governor_conf.finish().expect(
329
+
let create_account_governor_conf = create_account_governor_conf
330
+
.key_extractor(SmartIpKeyExtractor)
331
+
.finish().expect(
211
332
"failed to create governor config for create account. this should not happen and is a bug",
212
333
);
213
334
214
-
let create_session_governor_limiter = create_session_governor_conf.limiter().clone();
335
+
let captcha_governor_limiter = captcha_governor_conf.limiter().clone();
215
336
let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone();
216
337
let create_account_governor_limiter = create_account_governor_conf.limiter().clone();
217
338
339
+
let sign_in_governor_layer = GovernorLayer::new(sign_in_governor_conf);
340
+
218
341
let interval = Duration::from_secs(60);
219
342
// a separate background task to clean up
220
343
std::thread::spawn(move || {
221
344
loop {
222
345
std::thread::sleep(interval);
223
-
create_session_governor_limiter.retain_recent();
346
+
captcha_governor_limiter.retain_recent();
224
347
sign_in_governor_limiter.retain_recent();
225
348
create_account_governor_limiter.retain_recent();
226
349
}
···
231
354
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
232
355
.allow_headers(Any);
233
356
234
-
let app = Router::new()
357
+
let mut app = Router::new()
235
358
.route("/", get(root_handler))
236
359
.route("/xrpc/com.atproto.server.getSession", get(get_session))
237
360
.route(
361
+
"/xrpc/com.atproto.server.describeServer",
362
+
get(describe_server),
363
+
)
364
+
.route(
238
365
"/xrpc/com.atproto.server.updateEmail",
239
366
post(update_email).layer(ax_middleware::from_fn(middleware::extract_did)),
240
367
)
241
368
.route(
242
369
"/@atproto/oauth-provider/~api/sign-in",
243
-
post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)),
370
+
post(sign_in).layer(sign_in_governor_layer.clone()),
244
371
)
245
372
.route(
246
373
"/xrpc/com.atproto.server.createSession",
247
-
post(create_session.layer(GovernorLayer::new(create_session_governor_conf))),
374
+
post(create_session.layer(sign_in_governor_layer)),
248
375
)
249
376
.route(
250
377
"/xrpc/com.atproto.server.createAccount",
251
378
post(create_account).layer(GovernorLayer::new(create_account_governor_conf)),
252
-
)
379
+
);
380
+
381
+
if state.app_config.use_captcha {
382
+
app = app.route(
383
+
"/gate/signup",
384
+
get(get_gate).post(post_gate.layer(GovernorLayer::new(captcha_governor_conf))),
385
+
);
386
+
}
387
+
388
+
let app = app
253
389
.layer(CompressionLayer::new())
254
390
.layer(cors)
255
391
.with_state(state);
256
392
257
-
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
393
+
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
258
394
let port: u16 = env::var("GATEKEEPER_PORT")
259
395
.ok()
260
396
.and_then(|s| s.parse().ok())
+3
-2
src/oauth_provider.rs
+3
-2
src/oauth_provider.rs
···
13
13
pub struct SignInRequest {
14
14
pub username: String,
15
15
pub password: String,
16
-
pub remember: bool,
16
+
#[serde(skip_serializing_if = "Option::is_none")]
17
+
pub remember: Option<bool>,
17
18
pub locale: String,
18
19
#[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")]
19
20
pub email_otp: Option<String>,
···
56
57
//No 2FA or already passed
57
58
let uri = format!(
58
59
"{}{}",
59
-
state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
+
state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
61
);
61
62
62
63
let mut req = axum::http::Request::post(uri);
+306
-11
src/xrpc/com_atproto_server.rs
+306
-11
src/xrpc/com_atproto_server.rs
···
1
1
use crate::AppState;
2
2
use crate::helpers::{
3
-
AuthResult, ProxiedResult, TokenCheckError, json_error_response, preauth_check, proxy_get_json,
3
+
AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response,
4
+
preauth_check, proxy_get_json, verify_gate_token, verify_service_auth,
4
5
};
5
6
use crate::middleware::Did;
6
-
use axum::body::Body;
7
+
use axum::body::{Body, to_bytes};
7
8
use axum::extract::State;
8
-
use axum::http::{HeaderMap, StatusCode};
9
+
use axum::http::{HeaderMap, StatusCode, header};
9
10
use axum::response::{IntoResponse, Response};
10
11
use axum::{Extension, Json, debug_handler, extract, extract::Request};
12
+
use chrono::{Duration, Utc};
13
+
use jacquard_common::types::did::Did as JacquardDid;
11
14
use serde::{Deserialize, Serialize};
12
15
use serde_json;
13
16
use tracing::log;
···
61
64
allow_takendown: Option<bool>,
62
65
}
63
66
67
+
#[derive(Deserialize, Serialize, Debug)]
68
+
#[serde(rename_all = "camelCase")]
69
+
pub struct CreateAccountRequest {
70
+
handle: String,
71
+
#[serde(skip_serializing_if = "Option::is_none")]
72
+
email: Option<String>,
73
+
#[serde(skip_serializing_if = "Option::is_none")]
74
+
password: Option<String>,
75
+
#[serde(skip_serializing_if = "Option::is_none")]
76
+
did: Option<String>,
77
+
#[serde(skip_serializing_if = "Option::is_none")]
78
+
invite_code: Option<String>,
79
+
#[serde(skip_serializing_if = "Option::is_none")]
80
+
verification_code: Option<String>,
81
+
#[serde(skip_serializing_if = "Option::is_none")]
82
+
plc_op: Option<serde_json::Value>,
83
+
}
84
+
85
+
#[derive(Deserialize, Serialize, Debug, Clone)]
86
+
#[serde(rename_all = "camelCase")]
87
+
pub struct DescribeServerContact {
88
+
#[serde(skip_serializing_if = "Option::is_none")]
89
+
email: Option<String>,
90
+
}
91
+
92
+
#[derive(Deserialize, Serialize, Debug, Clone)]
93
+
#[serde(rename_all = "camelCase")]
94
+
pub struct DescribeServerLinks {
95
+
#[serde(skip_serializing_if = "Option::is_none")]
96
+
privacy_policy: Option<String>,
97
+
#[serde(skip_serializing_if = "Option::is_none")]
98
+
terms_of_service: Option<String>,
99
+
}
100
+
101
+
#[derive(Deserialize, Serialize, Debug, Clone)]
102
+
#[serde(rename_all = "camelCase")]
103
+
pub struct DescribeServerResponse {
104
+
#[serde(skip_serializing_if = "Option::is_none")]
105
+
invite_code_required: Option<bool>,
106
+
#[serde(skip_serializing_if = "Option::is_none")]
107
+
phone_verification_required: Option<bool>,
108
+
#[serde(skip_serializing_if = "Option::is_none")]
109
+
available_user_domains: Option<Vec<String>>,
110
+
#[serde(skip_serializing_if = "Option::is_none")]
111
+
links: Option<DescribeServerLinks>,
112
+
#[serde(skip_serializing_if = "Option::is_none")]
113
+
contact: Option<DescribeServerContact>,
114
+
#[serde(skip_serializing_if = "Option::is_none")]
115
+
did: Option<String>,
116
+
}
117
+
64
118
pub async fn create_session(
65
119
State(state): State<AppState>,
66
120
headers: HeaderMap,
···
90
144
//No 2FA or already passed
91
145
let uri = format!(
92
146
"{}{}",
93
-
state.pds_base_url, "/xrpc/com.atproto.server.createSession"
147
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession"
94
148
);
95
149
96
150
let mut req = axum::http::Request::post(uri);
···
230
284
// Updating the actual email address by sending it on to the PDS
231
285
let uri = format!(
232
286
"{}{}",
233
-
state.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
287
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
234
288
);
235
289
let mut req = axum::http::Request::post(uri);
236
290
if let Some(req_headers) = req.headers_mut() {
···
283
337
}
284
338
}
285
339
340
+
pub async fn describe_server(
341
+
State(state): State<AppState>,
342
+
req: Request,
343
+
) -> Result<Response<Body>, StatusCode> {
344
+
match proxy_get_json::<DescribeServerResponse>(
345
+
&state,
346
+
req,
347
+
"/xrpc/com.atproto.server.describeServer",
348
+
)
349
+
.await?
350
+
{
351
+
ProxiedResult::Parsed {
352
+
value: mut server_info,
353
+
..
354
+
} => {
355
+
//This signifies the server is configured for captcha verification
356
+
server_info.phone_verification_required = Some(state.app_config.use_captcha);
357
+
Ok(Json(server_info).into_response())
358
+
}
359
+
ProxiedResult::Passthrough(resp) => Ok(resp),
360
+
}
361
+
}
362
+
363
+
/// Verify a gate code matches the handle and is not expired
364
+
async fn verify_gate_code(
365
+
state: &AppState,
366
+
code: &str,
367
+
handle: &str,
368
+
) -> Result<bool, anyhow::Error> {
369
+
// First, decrypt and verify the JWE token
370
+
let payload = match verify_gate_token(code, &state.app_config.gate_jwe_key) {
371
+
Ok(p) => p,
372
+
Err(e) => {
373
+
log::warn!("Failed to decrypt gate token: {}", e);
374
+
return Ok(false);
375
+
}
376
+
};
377
+
378
+
// Verify the handle matches
379
+
if payload.handle != handle {
380
+
log::warn!(
381
+
"Gate code handle mismatch: expected {}, got {}",
382
+
handle,
383
+
payload.handle
384
+
);
385
+
return Ok(false);
386
+
}
387
+
388
+
let created_at = chrono::DateTime::parse_from_rfc3339(&payload.created_at)
389
+
.map_err(|e| anyhow::anyhow!("Failed to parse created_at from token: {}", e))?
390
+
.with_timezone(&Utc);
391
+
392
+
let now = Utc::now();
393
+
let age = now - created_at;
394
+
395
+
// Check if the token is expired (5 minutes)
396
+
if age > Duration::minutes(5) {
397
+
log::warn!("Gate code expired for handle {}", handle);
398
+
return Ok(false);
399
+
}
400
+
401
+
// Verify the token exists in the database (to prevent reuse)
402
+
let row: Option<(String,)> =
403
+
sqlx::query_as("SELECT code FROM gate_codes WHERE code = ? and handle = ? LIMIT 1")
404
+
.bind(code)
405
+
.bind(handle)
406
+
.fetch_optional(&state.pds_gatekeeper_pool)
407
+
.await?;
408
+
409
+
if row.is_none() {
410
+
log::warn!("Gate code not found in database or already used");
411
+
return Ok(false);
412
+
}
413
+
414
+
// Token is valid, delete it so it can't be reused
415
+
//TODO probably also delete expired codes? Will need to do that at some point probably altho the where is on code and handle
416
+
417
+
sqlx::query("DELETE FROM gate_codes WHERE code = ?")
418
+
.bind(code)
419
+
.execute(&state.pds_gatekeeper_pool)
420
+
.await?;
421
+
422
+
Ok(true)
423
+
}
424
+
286
425
pub async fn create_account(
287
426
State(state): State<AppState>,
288
-
mut req: Request,
427
+
req: Request,
289
428
) -> Result<Response<Body>, StatusCode> {
290
-
//TODO if I add the block of only accounts authenticated just take the body as json here and grab the lxm token. No middle ware is needed
429
+
let headers = req.headers().clone();
430
+
let body_bytes = to_bytes(req.into_body(), usize::MAX)
431
+
.await
432
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
433
+
434
+
// Parse the body to check for verification code
435
+
let account_request: CreateAccountRequest =
436
+
serde_json::from_slice(&body_bytes).map_err(|e| {
437
+
log::error!("Failed to parse create account request: {}", e);
438
+
StatusCode::BAD_REQUEST
439
+
})?;
440
+
441
+
// Check for service auth (migrations) if configured
442
+
if state.app_config.allow_only_migrations {
443
+
// Expect Authorization: Bearer <jwt>
444
+
let auth_header = headers
445
+
.get(header::AUTHORIZATION)
446
+
.and_then(|v| v.to_str().ok())
447
+
.map(str::to_string);
448
+
449
+
let Some(value) = auth_header else {
450
+
log::error!("No Authorization header found in the request");
451
+
return json_error_response(
452
+
StatusCode::UNAUTHORIZED,
453
+
"InvalidAuth",
454
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
455
+
);
456
+
};
457
+
458
+
// Ensure Bearer prefix
459
+
let token = value.strip_prefix("Bearer ").unwrap_or("").trim();
460
+
if token.is_empty() {
461
+
log::error!("No Service Auth token found in the Authorization header");
462
+
return json_error_response(
463
+
StatusCode::UNAUTHORIZED,
464
+
"InvalidAuth",
465
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
466
+
);
467
+
}
468
+
469
+
// Ensure a non-empty DID was provided when migrations are enabled
470
+
let requested_did_str = match account_request.did.as_deref() {
471
+
Some(s) if !s.trim().is_empty() => s,
472
+
_ => {
473
+
return json_error_response(
474
+
StatusCode::BAD_REQUEST,
475
+
"InvalidRequest",
476
+
"The 'did' field is required when migrations are enforced.",
477
+
);
478
+
}
479
+
};
480
+
481
+
// Parse the DID into the expected type for verification
482
+
let requested_did: JacquardDid<'static> = match requested_did_str.parse() {
483
+
Ok(d) => d,
484
+
Err(e) => {
485
+
log::error!(
486
+
"Invalid DID format provided in createAccount: {} | error: {}",
487
+
requested_did_str,
488
+
e
489
+
);
490
+
return json_error_response(
491
+
StatusCode::BAD_REQUEST,
492
+
"InvalidRequest",
493
+
"The 'did' field is not a valid DID.",
494
+
);
495
+
}
496
+
};
291
497
498
+
let nsid = "com.atproto.server.createAccount".parse().unwrap();
499
+
match verify_service_auth(
500
+
token,
501
+
&nsid,
502
+
state.resolver.clone(),
503
+
&state.app_config.pds_service_did,
504
+
&requested_did,
505
+
)
506
+
.await
507
+
{
508
+
//Just do nothing if it passes so it continues.
509
+
Ok(_) => {}
510
+
Err(err) => match err {
511
+
VerifyServiceAuthError::AuthFailed => {
512
+
return json_error_response(
513
+
StatusCode::UNAUTHORIZED,
514
+
"InvalidAuth",
515
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
516
+
);
517
+
}
518
+
VerifyServiceAuthError::Error(err) => {
519
+
log::error!("Error verifying service auth token: {err}");
520
+
return json_error_response(
521
+
StatusCode::BAD_REQUEST,
522
+
"InvalidRequest",
523
+
"There has been an error, please contact your PDS administrator for help and for them to review the server logs.",
524
+
);
525
+
}
526
+
},
527
+
}
528
+
}
529
+
530
+
// Check for captcha verification if configured
531
+
if state.app_config.use_captcha {
532
+
if let Some(ref verification_code) = account_request.verification_code {
533
+
match verify_gate_code(&state, verification_code, &account_request.handle).await {
534
+
//TODO has a few errors to support
535
+
536
+
//expired token
537
+
// {
538
+
// "error": "ExpiredToken",
539
+
// "message": "Token has expired"
540
+
// }
541
+
542
+
//TODO ALSO add rate limits on the /gate endpoints so they can't be abused
543
+
Ok(true) => {
544
+
log::info!("Gate code verified for handle: {}", account_request.handle);
545
+
}
546
+
Ok(false) => {
547
+
log::warn!(
548
+
"Invalid or expired gate code for handle: {}",
549
+
account_request.handle
550
+
);
551
+
return json_error_response(
552
+
StatusCode::BAD_REQUEST,
553
+
"InvalidToken",
554
+
"Token could not be verified",
555
+
);
556
+
}
557
+
Err(e) => {
558
+
log::error!("Error verifying gate code: {}", e);
559
+
return json_error_response(
560
+
StatusCode::INTERNAL_SERVER_ERROR,
561
+
"InvalidToken",
562
+
"Token could not be verified",
563
+
);
564
+
}
565
+
}
566
+
} else {
567
+
// No verification code provided but captcha is required
568
+
log::warn!(
569
+
"No verification code provided for account creation: {}",
570
+
account_request.handle
571
+
);
572
+
return json_error_response(
573
+
StatusCode::BAD_REQUEST,
574
+
"InvalidRequest",
575
+
"Verification is now required on this server.",
576
+
);
577
+
}
578
+
}
579
+
580
+
// Rebuild the request with the same body and headers
292
581
let uri = format!(
293
582
"{}{}",
294
-
state.pds_base_url, "/xrpc/com.atproto.server.createAccount"
583
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount"
295
584
);
296
585
297
-
// Rewrite the URI to point at the upstream PDS; keep headers, method, and body intact
298
-
*req.uri_mut() = uri.parse().map_err(|_| StatusCode::BAD_REQUEST)?;
586
+
let mut new_req = axum::http::Request::post(&uri);
587
+
if let Some(req_headers) = new_req.headers_mut() {
588
+
*req_headers = headers;
589
+
}
590
+
591
+
let new_req = new_req
592
+
.body(Body::from(body_bytes))
593
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
299
594
300
595
let proxied = state
301
596
.reverse_proxy_client
302
-
.request(req)
597
+
.request(new_req)
303
598
.await
304
599
.map_err(|_| StatusCode::BAD_REQUEST)?
305
600
.into_response();