+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.
+1990
-522
Cargo.lock
+1990
-522
Cargo.lock
···
3
version = 4
4
5
[[package]]
6
-
name = "addr2line"
7
-
version = "0.24.2"
8
source = "registry+https://github.com/rust-lang/crates.io-index"
9
-
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10
dependencies = [
11
-
"gimli",
12
]
13
14
[[package]]
···
31
32
[[package]]
33
name = "aho-corasick"
34
-
version = "1.1.3"
35
source = "registry+https://github.com/rust-lang/crates.io-index"
36
-
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
37
dependencies = [
38
"memchr",
39
]
40
41
[[package]]
42
-
name = "allocator-api2"
43
-
version = "0.2.21"
44
source = "registry+https://github.com/rust-lang/crates.io-index"
45
-
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
46
47
[[package]]
48
-
name = "android-tzdata"
49
-
version = "0.1.1"
50
source = "registry+https://github.com/rust-lang/crates.io-index"
51
-
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
52
53
[[package]]
54
name = "android_system_properties"
···
61
62
[[package]]
63
name = "anyhow"
64
-
version = "1.0.99"
65
source = "registry+https://github.com/rust-lang/crates.io-index"
66
-
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
67
68
[[package]]
69
name = "async-compression"
70
-
version = "0.4.27"
71
source = "registry+https://github.com/rust-lang/crates.io-index"
72
-
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
73
dependencies = [
74
"futures-core",
75
-
"memchr",
76
"pin-project-lite",
77
"tokio",
78
-
"zstd",
79
-
"zstd-safe",
80
]
81
82
[[package]]
···
87
dependencies = [
88
"proc-macro2",
89
"quote",
90
-
"syn",
91
]
92
93
[[package]]
···
100
]
101
102
[[package]]
103
name = "atomic-waker"
104
version = "1.1.2"
105
source = "registry+https://github.com/rust-lang/crates.io-index"
···
113
114
[[package]]
115
name = "aws-lc-rs"
116
-
version = "1.13.3"
117
source = "registry+https://github.com/rust-lang/crates.io-index"
118
-
checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
119
dependencies = [
120
"aws-lc-sys",
121
"untrusted 0.7.1",
···
124
125
[[package]]
126
name = "aws-lc-sys"
127
-
version = "0.30.0"
128
source = "registry+https://github.com/rust-lang/crates.io-index"
129
-
checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
130
dependencies = [
131
-
"bindgen",
132
"cc",
133
"cmake",
134
"dunce",
···
137
138
[[package]]
139
name = "axum"
140
-
version = "0.8.4"
141
source = "registry+https://github.com/rust-lang/crates.io-index"
142
-
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
143
dependencies = [
144
"axum-core",
145
"axum-macros",
···
157
"mime",
158
"percent-encoding",
159
"pin-project-lite",
160
-
"rustversion",
161
-
"serde",
162
"serde_json",
163
"serde_path_to_error",
164
"serde_urlencoded",
···
172
173
[[package]]
174
name = "axum-core"
175
-
version = "0.5.2"
176
source = "registry+https://github.com/rust-lang/crates.io-index"
177
-
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
178
dependencies = [
179
"bytes",
180
"futures-core",
···
183
"http-body-util",
184
"mime",
185
"pin-project-lite",
186
-
"rustversion",
187
"sync_wrapper",
188
"tower-layer",
189
"tower-service",
···
198
dependencies = [
199
"proc-macro2",
200
"quote",
201
-
"syn",
202
]
203
204
[[package]]
···
210
"axum",
211
"handlebars",
212
"serde",
213
-
"thiserror 2.0.14",
214
]
215
216
[[package]]
217
-
name = "backtrace"
218
-
version = "0.3.75"
219
source = "registry+https://github.com/rust-lang/crates.io-index"
220
-
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
221
dependencies = [
222
-
"addr2line",
223
-
"cfg-if",
224
-
"libc",
225
-
"miniz_oxide",
226
-
"object",
227
-
"rustc-demangle",
228
-
"windows-targets 0.52.6",
229
]
230
231
[[package]]
···
236
237
[[package]]
238
name = "base64ct"
239
-
version = "1.8.0"
240
source = "registry+https://github.com/rust-lang/crates.io-index"
241
-
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
242
243
[[package]]
244
-
name = "bindgen"
245
-
version = "0.69.5"
246
source = "registry+https://github.com/rust-lang/crates.io-index"
247
-
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
248
dependencies = [
249
-
"bitflags",
250
-
"cexpr",
251
-
"clang-sys",
252
-
"itertools",
253
-
"lazy_static",
254
-
"lazycell",
255
-
"log",
256
"prettyplease",
257
"proc-macro2",
258
"quote",
259
-
"regex",
260
-
"rustc-hash",
261
-
"shlex",
262
-
"syn",
263
-
"which",
264
]
265
266
[[package]]
267
-
name = "bitflags"
268
-
version = "2.9.1"
269
source = "registry+https://github.com/rust-lang/crates.io-index"
270
-
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
271
dependencies = [
272
"serde",
273
]
274
275
[[package]]
276
-
name = "block-buffer"
277
-
version = "0.10.4"
278
source = "registry+https://github.com/rust-lang/crates.io-index"
279
-
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
280
dependencies = [
281
-
"generic-array",
282
]
283
284
[[package]]
285
-
name = "bstr"
286
-
version = "1.12.0"
287
source = "registry+https://github.com/rust-lang/crates.io-index"
288
-
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
289
dependencies = [
290
-
"memchr",
291
-
"serde",
292
]
293
294
[[package]]
295
name = "bumpalo"
296
-
version = "3.19.0"
297
source = "registry+https://github.com/rust-lang/crates.io-index"
298
-
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
299
300
[[package]]
301
name = "byteorder"
···
305
306
[[package]]
307
name = "bytes"
308
-
version = "1.10.1"
309
source = "registry+https://github.com/rust-lang/crates.io-index"
310
-
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
311
312
[[package]]
313
name = "cc"
314
-
version = "1.2.32"
315
source = "registry+https://github.com/rust-lang/crates.io-index"
316
-
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
317
dependencies = [
318
"jobserver",
319
"libc",
320
"shlex",
321
]
322
323
[[package]]
324
-
name = "cexpr"
325
-
version = "0.6.0"
326
source = "registry+https://github.com/rust-lang/crates.io-index"
327
-
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
328
dependencies = [
329
-
"nom 7.1.3",
330
]
331
332
[[package]]
333
name = "cfg-if"
334
-
version = "1.0.1"
335
source = "registry+https://github.com/rust-lang/crates.io-index"
336
-
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
337
338
[[package]]
339
name = "chrono"
340
-
version = "0.4.41"
341
source = "registry+https://github.com/rust-lang/crates.io-index"
342
-
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
343
dependencies = [
344
-
"android-tzdata",
345
"iana-time-zone",
346
"js-sys",
347
"num-traits",
348
"wasm-bindgen",
349
"windows-link",
350
]
···
384
dependencies = [
385
"ciborium-io",
386
"half",
387
]
388
389
[[package]]
···
397
]
398
399
[[package]]
400
-
name = "clang-sys"
401
-
version = "1.8.1"
402
source = "registry+https://github.com/rust-lang/crates.io-index"
403
-
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
404
dependencies = [
405
-
"glob",
406
-
"libc",
407
-
"libloading",
408
]
409
410
[[package]]
411
-
name = "cmake"
412
-
version = "0.1.54"
413
source = "registry+https://github.com/rust-lang/crates.io-index"
414
-
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
415
dependencies = [
416
-
"cc",
417
]
418
419
[[package]]
420
name = "concurrent-queue"
···
432
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
433
434
[[package]]
435
name = "core-foundation-sys"
436
version = "0.8.7"
437
source = "registry+https://github.com/rust-lang/crates.io-index"
438
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
439
440
[[package]]
441
name = "cpufeatures"
442
version = "0.2.17"
443
source = "registry+https://github.com/rust-lang/crates.io-index"
···
448
449
[[package]]
450
name = "crc"
451
-
version = "3.3.0"
452
source = "registry+https://github.com/rust-lang/crates.io-index"
453
-
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
454
dependencies = [
455
"crc-catalog",
456
]
···
462
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
463
464
[[package]]
465
name = "crossbeam-queue"
466
version = "0.3.12"
467
source = "registry+https://github.com/rust-lang/crates.io-index"
···
483
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
484
485
[[package]]
486
name = "crypto-common"
487
-
version = "0.1.6"
488
source = "registry+https://github.com/rust-lang/crates.io-index"
489
-
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
490
dependencies = [
491
"generic-array",
492
"typenum",
···
498
source = "registry+https://github.com/rust-lang/crates.io-index"
499
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
500
dependencies = [
501
-
"darling_core",
502
-
"darling_macro",
503
]
504
505
[[package]]
···
513
"proc-macro2",
514
"quote",
515
"strsim",
516
-
"syn",
517
]
518
519
[[package]]
···
522
source = "registry+https://github.com/rust-lang/crates.io-index"
523
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
524
dependencies = [
525
-
"darling_core",
526
"quote",
527
-
"syn",
528
]
529
530
[[package]]
···
542
]
543
544
[[package]]
545
name = "der"
546
version = "0.7.10"
547
source = "registry+https://github.com/rust-lang/crates.io-index"
···
553
]
554
555
[[package]]
556
name = "derive_builder"
557
version = "0.20.2"
558
source = "registry+https://github.com/rust-lang/crates.io-index"
···
567
source = "registry+https://github.com/rust-lang/crates.io-index"
568
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
569
dependencies = [
570
-
"darling",
571
"proc-macro2",
572
"quote",
573
-
"syn",
574
]
575
576
[[package]]
···
580
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
581
dependencies = [
582
"derive_builder_core",
583
-
"syn",
584
]
585
586
[[package]]
587
name = "digest"
588
version = "0.10.7"
589
source = "registry+https://github.com/rust-lang/crates.io-index"
···
603
dependencies = [
604
"proc-macro2",
605
"quote",
606
-
"syn",
607
]
608
609
[[package]]
···
619
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
620
621
[[package]]
622
name = "either"
623
version = "1.15.0"
624
source = "registry+https://github.com/rust-lang/crates.io-index"
···
628
]
629
630
[[package]]
631
name = "email-encoding"
632
version = "0.4.1"
633
source = "registry+https://github.com/rust-lang/crates.io-index"
···
644
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
645
646
[[package]]
647
name = "equivalent"
648
version = "1.0.2"
649
source = "registry+https://github.com/rust-lang/crates.io-index"
···
651
652
[[package]]
653
name = "errno"
654
-
version = "0.3.13"
655
source = "registry+https://github.com/rust-lang/crates.io-index"
656
-
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
657
dependencies = [
658
"libc",
659
"windows-sys 0.59.0",
···
686
version = "2.3.0"
687
source = "registry+https://github.com/rust-lang/crates.io-index"
688
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
689
690
[[package]]
691
name = "flume"
···
695
dependencies = [
696
"futures-core",
697
"futures-sink",
698
-
"spin",
699
]
700
701
[[package]]
···
711
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
712
713
[[package]]
714
name = "form_urlencoded"
715
-
version = "1.2.1"
716
source = "registry+https://github.com/rust-lang/crates.io-index"
717
-
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
718
dependencies = [
719
"percent-encoding",
720
]
···
736
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
737
738
[[package]]
739
name = "futures-channel"
740
version = "0.3.31"
741
source = "registry+https://github.com/rust-lang/crates.io-index"
···
780
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
781
782
[[package]]
783
name = "futures-sink"
784
version = "0.3.31"
785
source = "registry+https://github.com/rust-lang/crates.io-index"
···
805
dependencies = [
806
"futures-core",
807
"futures-io",
808
"futures-sink",
809
"futures-task",
810
"memchr",
···
814
]
815
816
[[package]]
817
name = "generic-array"
818
version = "0.14.7"
819
source = "registry+https://github.com/rust-lang/crates.io-index"
···
821
dependencies = [
822
"typenum",
823
"version_check",
824
]
825
826
[[package]]
···
830
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
831
dependencies = [
832
"cfg-if",
833
"libc",
834
-
"wasi 0.11.1+wasi-snapshot-preview1",
835
]
836
837
[[package]]
838
name = "getrandom"
839
-
version = "0.3.3"
840
source = "registry+https://github.com/rust-lang/crates.io-index"
841
-
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
842
dependencies = [
843
"cfg-if",
844
"js-sys",
845
"libc",
846
"r-efi",
847
-
"wasi 0.14.2+wasi-0.2.4",
848
"wasm-bindgen",
849
]
850
851
[[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
name = "globset"
865
-
version = "0.4.16"
866
source = "registry+https://github.com/rust-lang/crates.io-index"
867
-
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
868
dependencies = [
869
"aho-corasick",
870
"bstr",
871
"log",
872
-
"regex-automata 0.4.9",
873
-
"regex-syntax 0.8.5",
874
]
875
876
[[package]]
877
name = "governor"
878
-
version = "0.10.1"
879
source = "registry+https://github.com/rust-lang/crates.io-index"
880
-
checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19"
881
dependencies = [
882
"cfg-if",
883
"dashmap",
884
"futures-sink",
885
"futures-timer",
886
"futures-util",
887
-
"getrandom 0.3.3",
888
-
"hashbrown 0.15.5",
889
"nonzero_ext",
890
"parking_lot",
891
"portable-atomic",
···
897
]
898
899
[[package]]
900
name = "h2"
901
version = "0.4.12"
902
source = "registry+https://github.com/rust-lang/crates.io-index"
···
908
"futures-core",
909
"futures-sink",
910
"http",
911
-
"indexmap",
912
"slab",
913
"tokio",
914
"tokio-util",
···
917
918
[[package]]
919
name = "half"
920
-
version = "2.6.0"
921
source = "registry+https://github.com/rust-lang/crates.io-index"
922
-
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
923
dependencies = [
924
"cfg-if",
925
"crunchy",
926
]
927
928
[[package]]
929
name = "handlebars"
930
-
version = "6.3.2"
931
source = "registry+https://github.com/rust-lang/crates.io-index"
932
-
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
933
dependencies = [
934
"derive_builder",
935
"log",
···
939
"rust-embed",
940
"serde",
941
"serde_json",
942
-
"thiserror 2.0.14",
943
]
944
945
[[package]]
946
name = "hashbrown"
947
version = "0.14.5"
948
source = "registry+https://github.com/rust-lang/crates.io-index"
949
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
···
960
dependencies = [
961
"allocator-api2",
962
"equivalent",
963
-
"foldhash",
964
]
965
966
[[package]]
···
973
]
974
975
[[package]]
976
name = "heck"
977
version = "0.5.0"
978
source = "registry+https://github.com/rust-lang/crates.io-index"
···
985
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
986
987
[[package]]
988
name = "hkdf"
989
version = "0.12.4"
990
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1004
1005
[[package]]
1006
name = "home"
1007
-
version = "0.5.11"
1008
source = "registry+https://github.com/rust-lang/crates.io-index"
1009
-
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
1010
dependencies = [
1011
-
"windows-sys 0.59.0",
1012
]
1013
1014
[[package]]
1015
name = "http"
1016
-
version = "1.3.1"
1017
source = "registry+https://github.com/rust-lang/crates.io-index"
1018
-
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
1019
dependencies = [
1020
"bytes",
1021
-
"fnv",
1022
"itoa",
1023
]
1024
···
1059
1060
[[package]]
1061
name = "hyper"
1062
-
version = "1.6.0"
1063
source = "registry+https://github.com/rust-lang/crates.io-index"
1064
-
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
1065
dependencies = [
1066
"bytes",
1067
"futures-channel",
1068
-
"futures-util",
1069
"h2",
1070
"http",
1071
"http-body",
···
1073
"httpdate",
1074
"itoa",
1075
"pin-project-lite",
1076
"smallvec",
1077
"tokio",
1078
"want",
1079
]
1080
1081
[[package]]
1082
name = "hyper-timeout"
1083
version = "0.5.2"
1084
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1093
1094
[[package]]
1095
name = "hyper-util"
1096
-
version = "0.1.16"
1097
source = "registry+https://github.com/rust-lang/crates.io-index"
1098
-
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1099
dependencies = [
1100
"bytes",
1101
"futures-channel",
1102
"futures-core",
···
1104
"http",
1105
"http-body",
1106
"hyper",
1107
"libc",
1108
"pin-project-lite",
1109
"socket2",
1110
"tokio",
1111
"tower-service",
1112
"tracing",
1113
]
1114
1115
[[package]]
1116
name = "iana-time-zone"
1117
-
version = "0.1.63"
1118
source = "registry+https://github.com/rust-lang/crates.io-index"
1119
-
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
1120
dependencies = [
1121
"android_system_properties",
1122
"core-foundation-sys",
···
1138
1139
[[package]]
1140
name = "icu_collections"
1141
-
version = "2.0.0"
1142
source = "registry+https://github.com/rust-lang/crates.io-index"
1143
-
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
1144
dependencies = [
1145
"displaydoc",
1146
"potential_utf",
···
1151
1152
[[package]]
1153
name = "icu_locale_core"
1154
-
version = "2.0.0"
1155
source = "registry+https://github.com/rust-lang/crates.io-index"
1156
-
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
1157
dependencies = [
1158
"displaydoc",
1159
"litemap",
···
1164
1165
[[package]]
1166
name = "icu_normalizer"
1167
-
version = "2.0.0"
1168
source = "registry+https://github.com/rust-lang/crates.io-index"
1169
-
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
1170
dependencies = [
1171
-
"displaydoc",
1172
"icu_collections",
1173
"icu_normalizer_data",
1174
"icu_properties",
···
1179
1180
[[package]]
1181
name = "icu_normalizer_data"
1182
-
version = "2.0.0"
1183
source = "registry+https://github.com/rust-lang/crates.io-index"
1184
-
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
1185
1186
[[package]]
1187
name = "icu_properties"
1188
-
version = "2.0.1"
1189
source = "registry+https://github.com/rust-lang/crates.io-index"
1190
-
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
1191
dependencies = [
1192
-
"displaydoc",
1193
"icu_collections",
1194
"icu_locale_core",
1195
"icu_properties_data",
1196
"icu_provider",
1197
-
"potential_utf",
1198
"zerotrie",
1199
"zerovec",
1200
]
1201
1202
[[package]]
1203
name = "icu_properties_data"
1204
-
version = "2.0.1"
1205
source = "registry+https://github.com/rust-lang/crates.io-index"
1206
-
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
1207
1208
[[package]]
1209
name = "icu_provider"
1210
-
version = "2.0.0"
1211
source = "registry+https://github.com/rust-lang/crates.io-index"
1212
-
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
1213
dependencies = [
1214
"displaydoc",
1215
"icu_locale_core",
1216
-
"stable_deref_trait",
1217
-
"tinystr",
1218
"writeable",
1219
"yoke",
1220
"zerofrom",
···
1230
1231
[[package]]
1232
name = "idna"
1233
-
version = "1.0.3"
1234
source = "registry+https://github.com/rust-lang/crates.io-index"
1235
-
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
1236
dependencies = [
1237
"idna_adapter",
1238
"smallvec",
···
1251
1252
[[package]]
1253
name = "indexmap"
1254
-
version = "2.10.0"
1255
source = "registry+https://github.com/rust-lang/crates.io-index"
1256
-
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
1257
dependencies = [
1258
"equivalent",
1259
-
"hashbrown 0.15.5",
1260
]
1261
1262
[[package]]
···
1269
]
1270
1271
[[package]]
1272
-
name = "io-uring"
1273
-
version = "0.7.9"
1274
source = "registry+https://github.com/rust-lang/crates.io-index"
1275
-
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
1276
dependencies = [
1277
-
"bitflags",
1278
-
"cfg-if",
1279
-
"libc",
1280
]
1281
1282
[[package]]
1283
-
name = "itertools"
1284
-
version = "0.12.1"
1285
source = "registry+https://github.com/rust-lang/crates.io-index"
1286
-
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
1287
dependencies = [
1288
-
"either",
1289
]
1290
1291
[[package]]
1292
name = "itoa"
1293
-
version = "1.0.15"
1294
source = "registry+https://github.com/rust-lang/crates.io-index"
1295
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1296
1297
[[package]]
1298
name = "jobserver"
1299
-
version = "0.1.33"
1300
source = "registry+https://github.com/rust-lang/crates.io-index"
1301
-
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
1302
dependencies = [
1303
-
"getrandom 0.3.3",
1304
"libc",
1305
]
1306
1307
[[package]]
1308
name = "js-sys"
1309
-
version = "0.3.77"
1310
source = "registry+https://github.com/rust-lang/crates.io-index"
1311
-
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
1312
dependencies = [
1313
"once_cell",
1314
"wasm-bindgen",
···
1337
]
1338
1339
[[package]]
1340
-
name = "lazy_static"
1341
-
version = "1.5.0"
1342
source = "registry+https://github.com/rust-lang/crates.io-index"
1343
-
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1344
dependencies = [
1345
-
"spin",
1346
]
1347
1348
[[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
name = "lettre"
1356
-
version = "0.11.18"
1357
source = "registry+https://github.com/rust-lang/crates.io-index"
1358
-
checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56"
1359
dependencies = [
1360
"async-trait",
1361
"base64",
···
1376
"tokio",
1377
"tokio-rustls",
1378
"url",
1379
-
"webpki-roots 1.0.2",
1380
]
1381
1382
[[package]]
1383
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"
1391
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
-
]
1397
1398
[[package]]
1399
name = "libm"
···
1403
1404
[[package]]
1405
name = "libredox"
1406
-
version = "0.1.9"
1407
source = "registry+https://github.com/rust-lang/crates.io-index"
1408
-
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
1409
dependencies = [
1410
"bitflags",
1411
"libc",
1412
-
"redox_syscall",
1413
]
1414
1415
[[package]]
···
1424
]
1425
1426
[[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
name = "litemap"
1434
-
version = "0.8.0"
1435
source = "registry+https://github.com/rust-lang/crates.io-index"
1436
-
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
1437
1438
[[package]]
1439
name = "lock_api"
1440
-
version = "0.4.13"
1441
source = "registry+https://github.com/rust-lang/crates.io-index"
1442
-
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
1443
dependencies = [
1444
-
"autocfg",
1445
"scopeguard",
1446
]
1447
1448
[[package]]
1449
name = "log"
1450
-
version = "0.4.27"
1451
source = "registry+https://github.com/rust-lang/crates.io-index"
1452
-
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
1453
1454
[[package]]
1455
name = "matchers"
1456
-
version = "0.1.0"
1457
source = "registry+https://github.com/rust-lang/crates.io-index"
1458
-
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
1459
dependencies = [
1460
-
"regex-automata 0.1.10",
1461
]
1462
1463
[[package]]
···
1478
1479
[[package]]
1480
name = "memchr"
1481
-
version = "2.7.5"
1482
source = "registry+https://github.com/rust-lang/crates.io-index"
1483
-
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
1484
1485
[[package]]
1486
name = "mime"
···
1501
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1502
dependencies = [
1503
"adler2",
1504
]
1505
1506
[[package]]
1507
name = "mio"
1508
-
version = "1.0.4"
1509
source = "registry+https://github.com/rust-lang/crates.io-index"
1510
-
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
1511
dependencies = [
1512
"libc",
1513
-
"wasi 0.11.1+wasi-snapshot-preview1",
1514
-
"windows-sys 0.59.0",
1515
]
1516
1517
[[package]]
···
1547
1548
[[package]]
1549
name = "nu-ansi-term"
1550
-
version = "0.46.0"
1551
source = "registry+https://github.com/rust-lang/crates.io-index"
1552
-
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
1553
dependencies = [
1554
-
"overload",
1555
-
"winapi",
1556
]
1557
1558
[[package]]
1559
name = "num-bigint-dig"
1560
-
version = "0.8.4"
1561
source = "registry+https://github.com/rust-lang/crates.io-index"
1562
-
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
1563
dependencies = [
1564
-
"byteorder",
1565
"lazy_static",
1566
"libm",
1567
"num-integer",
···
1571
"smallvec",
1572
"zeroize",
1573
]
1574
1575
[[package]]
1576
name = "num-integer"
···
1619
1620
[[package]]
1621
name = "object"
1622
-
version = "0.36.7"
1623
source = "registry+https://github.com/rust-lang/crates.io-index"
1624
-
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
1625
dependencies = [
1626
"memchr",
1627
]
···
1633
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1634
1635
[[package]]
1636
-
name = "overload"
1637
version = "0.1.1"
1638
source = "registry+https://github.com/rust-lang/crates.io-index"
1639
-
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
1640
1641
[[package]]
1642
name = "parking"
···
1646
1647
[[package]]
1648
name = "parking_lot"
1649
-
version = "0.12.4"
1650
source = "registry+https://github.com/rust-lang/crates.io-index"
1651
-
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
1652
dependencies = [
1653
"lock_api",
1654
"parking_lot_core",
···
1656
1657
[[package]]
1658
name = "parking_lot_core"
1659
-
version = "0.9.11"
1660
source = "registry+https://github.com/rust-lang/crates.io-index"
1661
-
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
1662
dependencies = [
1663
"cfg-if",
1664
"libc",
1665
-
"redox_syscall",
1666
"smallvec",
1667
-
"windows-targets 0.52.6",
1668
]
1669
1670
[[package]]
···
1690
1691
[[package]]
1692
name = "pds_gatekeeper"
1693
-
version = "0.1.0"
1694
dependencies = [
1695
"anyhow",
1696
"aws-lc-rs",
1697
"axum",
1698
"axum-template",
1699
"chrono",
1700
"dotenvy",
1701
"handlebars",
1702
"hex",
1703
"hyper-util",
1704
"jwt-compact",
1705
"lettre",
1706
"rand 0.9.2",
1707
"rust-embed",
1708
"rustls",
1709
"scrypt",
···
1712
"sha2",
1713
"sqlx",
1714
"tokio",
1715
"tower-http",
1716
"tower_governor",
1717
"tracing",
1718
"tracing-subscriber",
1719
]
1720
1721
[[package]]
···
1729
1730
[[package]]
1731
name = "percent-encoding"
1732
-
version = "2.3.1"
1733
source = "registry+https://github.com/rust-lang/crates.io-index"
1734
-
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1735
1736
[[package]]
1737
name = "pest"
1738
-
version = "2.8.1"
1739
source = "registry+https://github.com/rust-lang/crates.io-index"
1740
-
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
1741
dependencies = [
1742
"memchr",
1743
-
"thiserror 2.0.14",
1744
"ucd-trie",
1745
]
1746
1747
[[package]]
1748
name = "pest_derive"
1749
-
version = "2.8.1"
1750
source = "registry+https://github.com/rust-lang/crates.io-index"
1751
-
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
1752
dependencies = [
1753
"pest",
1754
"pest_generator",
···
1756
1757
[[package]]
1758
name = "pest_generator"
1759
-
version = "2.8.1"
1760
source = "registry+https://github.com/rust-lang/crates.io-index"
1761
-
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
1762
dependencies = [
1763
"pest",
1764
"pest_meta",
1765
"proc-macro2",
1766
"quote",
1767
-
"syn",
1768
]
1769
1770
[[package]]
1771
name = "pest_meta"
1772
-
version = "2.8.1"
1773
source = "registry+https://github.com/rust-lang/crates.io-index"
1774
-
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
1775
dependencies = [
1776
"pest",
1777
"sha2",
···
1794
dependencies = [
1795
"proc-macro2",
1796
"quote",
1797
-
"syn",
1798
]
1799
1800
[[package]]
···
1838
1839
[[package]]
1840
name = "portable-atomic"
1841
-
version = "1.11.1"
1842
source = "registry+https://github.com/rust-lang/crates.io-index"
1843
-
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
1844
1845
[[package]]
1846
name = "potential_utf"
1847
-
version = "0.1.2"
1848
source = "registry+https://github.com/rust-lang/crates.io-index"
1849
-
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
1850
dependencies = [
1851
"zerovec",
1852
]
1853
1854
[[package]]
1855
name = "ppv-lite86"
···
1862
1863
[[package]]
1864
name = "prettyplease"
1865
-
version = "0.2.35"
1866
source = "registry+https://github.com/rust-lang/crates.io-index"
1867
-
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
1868
dependencies = [
1869
"proc-macro2",
1870
-
"syn",
1871
]
1872
1873
[[package]]
1874
name = "proc-macro2"
1875
-
version = "1.0.97"
1876
source = "registry+https://github.com/rust-lang/crates.io-index"
1877
-
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
1878
dependencies = [
1879
"unicode-ident",
1880
]
1881
1882
[[package]]
1883
name = "psm"
1884
-
version = "0.1.26"
1885
source = "registry+https://github.com/rust-lang/crates.io-index"
1886
-
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
1887
dependencies = [
1888
"cc",
1889
]
1890
···
1898
"libc",
1899
"once_cell",
1900
"raw-cpuid",
1901
-
"wasi 0.11.1+wasi-snapshot-preview1",
1902
"web-sys",
1903
"winapi",
1904
]
1905
1906
[[package]]
1907
name = "quote"
1908
-
version = "1.0.40"
1909
source = "registry+https://github.com/rust-lang/crates.io-index"
1910
-
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
1911
dependencies = [
1912
"proc-macro2",
1913
]
···
1980
source = "registry+https://github.com/rust-lang/crates.io-index"
1981
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
1982
dependencies = [
1983
-
"getrandom 0.3.3",
1984
]
1985
1986
[[package]]
1987
name = "raw-cpuid"
1988
-
version = "11.5.0"
1989
source = "registry+https://github.com/rust-lang/crates.io-index"
1990
-
checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146"
1991
dependencies = [
1992
"bitflags",
1993
]
1994
1995
[[package]]
1996
name = "redox_syscall"
1997
-
version = "0.5.17"
1998
source = "registry+https://github.com/rust-lang/crates.io-index"
1999
-
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
2000
dependencies = [
2001
"bitflags",
2002
]
2003
2004
[[package]]
2005
-
name = "regex"
2006
-
version = "1.11.1"
2007
source = "registry+https://github.com/rust-lang/crates.io-index"
2008
-
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
2009
dependencies = [
2010
-
"aho-corasick",
2011
-
"memchr",
2012
-
"regex-automata 0.4.9",
2013
-
"regex-syntax 0.8.5",
2014
]
2015
2016
[[package]]
2017
-
name = "regex-automata"
2018
-
version = "0.1.10"
2019
source = "registry+https://github.com/rust-lang/crates.io-index"
2020
-
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
2021
dependencies = [
2022
-
"regex-syntax 0.6.29",
2023
]
2024
2025
[[package]]
2026
name = "regex-automata"
2027
-
version = "0.4.9"
2028
source = "registry+https://github.com/rust-lang/crates.io-index"
2029
-
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
2030
dependencies = [
2031
"aho-corasick",
2032
"memchr",
2033
-
"regex-syntax 0.8.5",
2034
]
2035
2036
[[package]]
2037
-
name = "regex-syntax"
2038
-
version = "0.6.29"
2039
source = "registry+https://github.com/rust-lang/crates.io-index"
2040
-
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
2041
2042
[[package]]
2043
name = "regex-syntax"
2044
-
version = "0.8.5"
2045
source = "registry+https://github.com/rust-lang/crates.io-index"
2046
-
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
2047
2048
[[package]]
2049
name = "ring"
···
2061
2062
[[package]]
2063
name = "rsa"
2064
-
version = "0.9.8"
2065
source = "registry+https://github.com/rust-lang/crates.io-index"
2066
-
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
2067
dependencies = [
2068
"const-oid",
2069
"digest",
···
2081
2082
[[package]]
2083
name = "rust-embed"
2084
-
version = "8.7.2"
2085
source = "registry+https://github.com/rust-lang/crates.io-index"
2086
-
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
2087
dependencies = [
2088
"rust-embed-impl",
2089
"rust-embed-utils",
···
2092
2093
[[package]]
2094
name = "rust-embed-impl"
2095
-
version = "8.7.2"
2096
source = "registry+https://github.com/rust-lang/crates.io-index"
2097
-
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
2098
dependencies = [
2099
"proc-macro2",
2100
"quote",
2101
"rust-embed-utils",
2102
-
"syn",
2103
"walkdir",
2104
]
2105
2106
[[package]]
2107
name = "rust-embed-utils"
2108
-
version = "8.7.2"
2109
source = "registry+https://github.com/rust-lang/crates.io-index"
2110
-
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
2111
dependencies = [
2112
"globset",
2113
"sha2",
···
2115
]
2116
2117
[[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
name = "rustc-hash"
2125
-
version = "1.1.0"
2126
source = "registry+https://github.com/rust-lang/crates.io-index"
2127
-
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
2128
2129
[[package]]
2130
-
name = "rustix"
2131
-
version = "0.38.44"
2132
source = "registry+https://github.com/rust-lang/crates.io-index"
2133
-
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
2134
dependencies = [
2135
-
"bitflags",
2136
-
"errno",
2137
-
"libc",
2138
-
"linux-raw-sys",
2139
-
"windows-sys 0.59.0",
2140
]
2141
2142
[[package]]
2143
name = "rustls"
2144
-
version = "0.23.31"
2145
source = "registry+https://github.com/rust-lang/crates.io-index"
2146
-
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
2147
dependencies = [
2148
"aws-lc-rs",
2149
"log",
···
2157
2158
[[package]]
2159
name = "rustls-pki-types"
2160
-
version = "1.12.0"
2161
source = "registry+https://github.com/rust-lang/crates.io-index"
2162
-
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
2163
dependencies = [
2164
"zeroize",
2165
]
2166
2167
[[package]]
2168
name = "rustls-webpki"
2169
-
version = "0.103.4"
2170
source = "registry+https://github.com/rust-lang/crates.io-index"
2171
-
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
2172
dependencies = [
2173
"aws-lc-rs",
2174
"ring",
···
2184
2185
[[package]]
2186
name = "ryu"
2187
-
version = "1.0.20"
2188
source = "registry+https://github.com/rust-lang/crates.io-index"
2189
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
2190
2191
[[package]]
2192
name = "salsa20"
···
2207
]
2208
2209
[[package]]
2210
name = "scopeguard"
2211
version = "1.2.0"
2212
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2225
]
2226
2227
[[package]]
2228
name = "secp256k1"
2229
version = "0.28.2"
2230
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2243
]
2244
2245
[[package]]
2246
name = "serde"
2247
-
version = "1.0.219"
2248
source = "registry+https://github.com/rust-lang/crates.io-index"
2249
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
2250
dependencies = [
2251
"serde_derive",
2252
]
2253
2254
[[package]]
2255
name = "serde_derive"
2256
-
version = "1.0.219"
2257
source = "registry+https://github.com/rust-lang/crates.io-index"
2258
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
2259
dependencies = [
2260
"proc-macro2",
2261
"quote",
2262
-
"syn",
2263
]
2264
2265
[[package]]
2266
name = "serde_json"
2267
-
version = "1.0.142"
2268
source = "registry+https://github.com/rust-lang/crates.io-index"
2269
-
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
2270
dependencies = [
2271
"itoa",
2272
"memchr",
2273
-
"ryu",
2274
"serde",
2275
]
2276
2277
[[package]]
2278
name = "serde_path_to_error"
2279
-
version = "0.1.17"
2280
source = "registry+https://github.com/rust-lang/crates.io-index"
2281
-
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
2282
dependencies = [
2283
"itoa",
2284
"serde",
2285
]
2286
2287
[[package]]
···
2297
]
2298
2299
[[package]]
2300
name = "sha1"
2301
version = "0.10.6"
2302
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2335
2336
[[package]]
2337
name = "signal-hook-registry"
2338
-
version = "1.4.6"
2339
source = "registry+https://github.com/rust-lang/crates.io-index"
2340
-
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
2341
dependencies = [
2342
"libc",
2343
]
2344
···
2351
"digest",
2352
"rand_core 0.6.4",
2353
]
2354
2355
[[package]]
2356
name = "slab"
···
2368
]
2369
2370
[[package]]
2371
name = "socket2"
2372
-
version = "0.6.0"
2373
source = "registry+https://github.com/rust-lang/crates.io-index"
2374
-
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
2375
dependencies = [
2376
"libc",
2377
-
"windows-sys 0.59.0",
2378
]
2379
2380
[[package]]
···
2387
]
2388
2389
[[package]]
2390
name = "spinning_top"
2391
version = "0.3.0"
2392
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2437
"futures-util",
2438
"hashbrown 0.15.5",
2439
"hashlink",
2440
-
"indexmap",
2441
"log",
2442
"memchr",
2443
"once_cell",
···
2447
"serde_json",
2448
"sha2",
2449
"smallvec",
2450
-
"thiserror 2.0.14",
2451
"tokio",
2452
"tokio-stream",
2453
"tracing",
···
2465
"quote",
2466
"sqlx-core",
2467
"sqlx-macros-core",
2468
-
"syn",
2469
]
2470
2471
[[package]]
···
2476
dependencies = [
2477
"dotenvy",
2478
"either",
2479
-
"heck",
2480
"hex",
2481
"once_cell",
2482
"proc-macro2",
···
2488
"sqlx-mysql",
2489
"sqlx-postgres",
2490
"sqlx-sqlite",
2491
-
"syn",
2492
"tokio",
2493
"url",
2494
]
···
2531
"smallvec",
2532
"sqlx-core",
2533
"stringprep",
2534
-
"thiserror 2.0.14",
2535
"tracing",
2536
"whoami",
2537
]
···
2569
"smallvec",
2570
"sqlx-core",
2571
"stringprep",
2572
-
"thiserror 2.0.14",
2573
"tracing",
2574
"whoami",
2575
]
···
2594
"serde",
2595
"serde_urlencoded",
2596
"sqlx-core",
2597
-
"thiserror 2.0.14",
2598
"tracing",
2599
"url",
2600
]
2601
2602
[[package]]
2603
name = "stable_deref_trait"
2604
-
version = "1.2.0"
2605
source = "registry+https://github.com/rust-lang/crates.io-index"
2606
-
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
2607
2608
[[package]]
2609
name = "stacker"
2610
-
version = "0.1.21"
2611
source = "registry+https://github.com/rust-lang/crates.io-index"
2612
-
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
2613
dependencies = [
2614
"cc",
2615
"cfg-if",
···
2619
]
2620
2621
[[package]]
2622
name = "stringprep"
2623
version = "0.1.5"
2624
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2643
2644
[[package]]
2645
name = "syn"
2646
-
version = "2.0.105"
2647
source = "registry+https://github.com/rust-lang/crates.io-index"
2648
-
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
2649
dependencies = [
2650
"proc-macro2",
2651
"quote",
···
2657
version = "1.0.2"
2658
source = "registry+https://github.com/rust-lang/crates.io-index"
2659
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
2660
2661
[[package]]
2662
name = "synstructure"
···
2666
dependencies = [
2667
"proc-macro2",
2668
"quote",
2669
-
"syn",
2670
]
2671
2672
[[package]]
···
2680
2681
[[package]]
2682
name = "thiserror"
2683
-
version = "2.0.14"
2684
source = "registry+https://github.com/rust-lang/crates.io-index"
2685
-
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
2686
dependencies = [
2687
-
"thiserror-impl 2.0.14",
2688
]
2689
2690
[[package]]
···
2695
dependencies = [
2696
"proc-macro2",
2697
"quote",
2698
-
"syn",
2699
]
2700
2701
[[package]]
2702
name = "thiserror-impl"
2703
-
version = "2.0.14"
2704
source = "registry+https://github.com/rust-lang/crates.io-index"
2705
-
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
2706
dependencies = [
2707
"proc-macro2",
2708
"quote",
2709
-
"syn",
2710
]
2711
2712
[[package]]
···
2719
]
2720
2721
[[package]]
2722
name = "tinystr"
2723
-
version = "0.8.1"
2724
source = "registry+https://github.com/rust-lang/crates.io-index"
2725
-
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
2726
dependencies = [
2727
"displaydoc",
2728
"zerovec",
···
2730
2731
[[package]]
2732
name = "tinyvec"
2733
-
version = "1.9.0"
2734
source = "registry+https://github.com/rust-lang/crates.io-index"
2735
-
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
2736
dependencies = [
2737
"tinyvec_macros",
2738
]
···
2745
2746
[[package]]
2747
name = "tokio"
2748
-
version = "1.47.1"
2749
source = "registry+https://github.com/rust-lang/crates.io-index"
2750
-
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
2751
dependencies = [
2752
-
"backtrace",
2753
"bytes",
2754
-
"io-uring",
2755
"libc",
2756
"mio",
2757
"pin-project-lite",
2758
"signal-hook-registry",
2759
-
"slab",
2760
"socket2",
2761
"tokio-macros",
2762
-
"windows-sys 0.59.0",
2763
]
2764
2765
[[package]]
2766
name = "tokio-macros"
2767
-
version = "2.5.0"
2768
source = "registry+https://github.com/rust-lang/crates.io-index"
2769
-
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
2770
dependencies = [
2771
"proc-macro2",
2772
"quote",
2773
-
"syn",
2774
]
2775
2776
[[package]]
2777
name = "tokio-rustls"
2778
-
version = "0.26.2"
2779
source = "registry+https://github.com/rust-lang/crates.io-index"
2780
-
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
2781
dependencies = [
2782
"rustls",
2783
"tokio",
···
2796
2797
[[package]]
2798
name = "tokio-util"
2799
-
version = "0.7.15"
2800
source = "registry+https://github.com/rust-lang/crates.io-index"
2801
-
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
2802
dependencies = [
2803
"bytes",
2804
"futures-core",
2805
"futures-sink",
2806
"pin-project-lite",
2807
"tokio",
2808
]
2809
2810
[[package]]
2811
name = "tonic"
2812
-
version = "0.14.1"
2813
source = "registry+https://github.com/rust-lang/crates.io-index"
2814
-
checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40"
2815
dependencies = [
2816
"async-trait",
2817
"axum",
···
2844
dependencies = [
2845
"futures-core",
2846
"futures-util",
2847
-
"indexmap",
2848
"pin-project-lite",
2849
"slab",
2850
"sync_wrapper",
···
2857
2858
[[package]]
2859
name = "tower-http"
2860
-
version = "0.6.6"
2861
source = "registry+https://github.com/rust-lang/crates.io-index"
2862
-
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
2863
dependencies = [
2864
"async-compression",
2865
"bitflags",
2866
"bytes",
2867
"futures-core",
2868
"http",
2869
"http-body",
2870
"pin-project-lite",
2871
"tokio",
2872
"tokio-util",
2873
"tower-layer",
2874
"tower-service",
2875
]
···
2897
"governor",
2898
"http",
2899
"pin-project",
2900
-
"thiserror 2.0.14",
2901
"tonic",
2902
"tower",
2903
"tracing",
···
2905
2906
[[package]]
2907
name = "tracing"
2908
-
version = "0.1.41"
2909
source = "registry+https://github.com/rust-lang/crates.io-index"
2910
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
2911
dependencies = [
2912
"log",
2913
"pin-project-lite",
···
2917
2918
[[package]]
2919
name = "tracing-attributes"
2920
-
version = "0.1.30"
2921
source = "registry+https://github.com/rust-lang/crates.io-index"
2922
-
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
2923
dependencies = [
2924
"proc-macro2",
2925
"quote",
2926
-
"syn",
2927
]
2928
2929
[[package]]
2930
name = "tracing-core"
2931
-
version = "0.1.34"
2932
source = "registry+https://github.com/rust-lang/crates.io-index"
2933
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
2934
dependencies = [
2935
"once_cell",
2936
"valuable",
···
2949
2950
[[package]]
2951
name = "tracing-subscriber"
2952
-
version = "0.3.19"
2953
source = "registry+https://github.com/rust-lang/crates.io-index"
2954
-
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
2955
dependencies = [
2956
"matchers",
2957
"nu-ansi-term",
2958
"once_cell",
2959
-
"regex",
2960
"sharded-slab",
2961
"smallvec",
2962
"thread_local",
···
2966
]
2967
2968
[[package]]
2969
name = "try-lock"
2970
version = "0.2.5"
2971
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2973
2974
[[package]]
2975
name = "typenum"
2976
-
version = "1.18.0"
2977
source = "registry+https://github.com/rust-lang/crates.io-index"
2978
-
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
2979
2980
[[package]]
2981
name = "ucd-trie"
···
2991
2992
[[package]]
2993
name = "unicode-ident"
2994
-
version = "1.0.18"
2995
source = "registry+https://github.com/rust-lang/crates.io-index"
2996
-
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
2997
2998
[[package]]
2999
name = "unicode-normalization"
3000
-
version = "0.1.24"
3001
source = "registry+https://github.com/rust-lang/crates.io-index"
3002
-
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
3003
dependencies = [
3004
"tinyvec",
3005
]
3006
3007
[[package]]
3008
name = "unicode-properties"
3009
-
version = "0.1.3"
3010
source = "registry+https://github.com/rust-lang/crates.io-index"
3011
-
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
3012
3013
[[package]]
3014
name = "untrusted"
···
3024
3025
[[package]]
3026
name = "url"
3027
-
version = "2.5.4"
3028
source = "registry+https://github.com/rust-lang/crates.io-index"
3029
-
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
3030
dependencies = [
3031
"form_urlencoded",
3032
"idna",
3033
"percent-encoding",
3034
]
3035
3036
[[package]]
3037
name = "utf8_iter"
···
3083
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3084
3085
[[package]]
3086
-
name = "wasi"
3087
-
version = "0.14.2+wasi-0.2.4"
3088
source = "registry+https://github.com/rust-lang/crates.io-index"
3089
-
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
3090
dependencies = [
3091
-
"wit-bindgen-rt",
3092
]
3093
3094
[[package]]
···
3099
3100
[[package]]
3101
name = "wasm-bindgen"
3102
-
version = "0.2.100"
3103
source = "registry+https://github.com/rust-lang/crates.io-index"
3104
-
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
3105
dependencies = [
3106
"cfg-if",
3107
"once_cell",
3108
"rustversion",
3109
"wasm-bindgen-macro",
3110
]
3111
3112
[[package]]
3113
-
name = "wasm-bindgen-backend"
3114
-
version = "0.2.100"
3115
source = "registry+https://github.com/rust-lang/crates.io-index"
3116
-
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
3117
dependencies = [
3118
-
"bumpalo",
3119
-
"log",
3120
-
"proc-macro2",
3121
-
"quote",
3122
-
"syn",
3123
-
"wasm-bindgen-shared",
3124
]
3125
3126
[[package]]
3127
name = "wasm-bindgen-macro"
3128
-
version = "0.2.100"
3129
source = "registry+https://github.com/rust-lang/crates.io-index"
3130
-
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
3131
dependencies = [
3132
"quote",
3133
"wasm-bindgen-macro-support",
···
3135
3136
[[package]]
3137
name = "wasm-bindgen-macro-support"
3138
-
version = "0.2.100"
3139
source = "registry+https://github.com/rust-lang/crates.io-index"
3140
-
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
3141
dependencies = [
3142
"proc-macro2",
3143
"quote",
3144
-
"syn",
3145
-
"wasm-bindgen-backend",
3146
"wasm-bindgen-shared",
3147
]
3148
3149
[[package]]
3150
name = "wasm-bindgen-shared"
3151
-
version = "0.2.100"
3152
source = "registry+https://github.com/rust-lang/crates.io-index"
3153
-
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
3154
dependencies = [
3155
"unicode-ident",
3156
]
3157
3158
[[package]]
3159
name = "web-sys"
3160
-
version = "0.3.77"
3161
source = "registry+https://github.com/rust-lang/crates.io-index"
3162
-
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
3163
dependencies = [
3164
"js-sys",
3165
"wasm-bindgen",
···
3181
source = "registry+https://github.com/rust-lang/crates.io-index"
3182
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
3183
dependencies = [
3184
-
"webpki-roots 1.0.2",
3185
]
3186
3187
[[package]]
3188
name = "webpki-roots"
3189
-
version = "1.0.2"
3190
source = "registry+https://github.com/rust-lang/crates.io-index"
3191
-
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
3192
dependencies = [
3193
"rustls-pki-types",
3194
]
3195
3196
[[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
name = "whoami"
3210
version = "1.6.1"
3211
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3233
3234
[[package]]
3235
name = "winapi-util"
3236
-
version = "0.1.9"
3237
source = "registry+https://github.com/rust-lang/crates.io-index"
3238
-
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
3239
dependencies = [
3240
-
"windows-sys 0.59.0",
3241
]
3242
3243
[[package]]
···
3248
3249
[[package]]
3250
name = "windows-core"
3251
-
version = "0.61.2"
3252
source = "registry+https://github.com/rust-lang/crates.io-index"
3253
-
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
3254
dependencies = [
3255
"windows-implement",
3256
"windows-interface",
···
3261
3262
[[package]]
3263
name = "windows-implement"
3264
-
version = "0.60.0"
3265
source = "registry+https://github.com/rust-lang/crates.io-index"
3266
-
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
3267
dependencies = [
3268
"proc-macro2",
3269
"quote",
3270
-
"syn",
3271
]
3272
3273
[[package]]
3274
name = "windows-interface"
3275
-
version = "0.59.1"
3276
source = "registry+https://github.com/rust-lang/crates.io-index"
3277
-
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
3278
dependencies = [
3279
"proc-macro2",
3280
"quote",
3281
-
"syn",
3282
]
3283
3284
[[package]]
3285
name = "windows-link"
3286
-
version = "0.1.3"
3287
source = "registry+https://github.com/rust-lang/crates.io-index"
3288
-
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
3289
3290
[[package]]
3291
name = "windows-result"
3292
-
version = "0.3.4"
3293
source = "registry+https://github.com/rust-lang/crates.io-index"
3294
-
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
3295
dependencies = [
3296
"windows-link",
3297
]
3298
3299
[[package]]
3300
name = "windows-strings"
3301
-
version = "0.4.2"
3302
source = "registry+https://github.com/rust-lang/crates.io-index"
3303
-
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
3304
dependencies = [
3305
"windows-link",
3306
]
···
3333
]
3334
3335
[[package]]
3336
name = "windows-targets"
3337
version = "0.48.5"
3338
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3356
"windows_aarch64_gnullvm 0.52.6",
3357
"windows_aarch64_msvc 0.52.6",
3358
"windows_i686_gnu 0.52.6",
3359
-
"windows_i686_gnullvm",
3360
"windows_i686_msvc 0.52.6",
3361
"windows_x86_64_gnu 0.52.6",
3362
"windows_x86_64_gnullvm 0.52.6",
···
3364
]
3365
3366
[[package]]
3367
name = "windows_aarch64_gnullvm"
3368
version = "0.48.5"
3369
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3376
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
3377
3378
[[package]]
3379
name = "windows_aarch64_msvc"
3380
version = "0.48.5"
3381
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3388
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
3389
3390
[[package]]
3391
name = "windows_i686_gnu"
3392
version = "0.48.5"
3393
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3400
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
3401
3402
[[package]]
3403
name = "windows_i686_gnullvm"
3404
version = "0.52.6"
3405
source = "registry+https://github.com/rust-lang/crates.io-index"
3406
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
3407
3408
[[package]]
3409
name = "windows_i686_msvc"
···
3418
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
3419
3420
[[package]]
3421
name = "windows_x86_64_gnu"
3422
version = "0.48.5"
3423
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3430
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
3431
3432
[[package]]
3433
name = "windows_x86_64_gnullvm"
3434
version = "0.48.5"
3435
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3442
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
3443
3444
[[package]]
3445
name = "windows_x86_64_msvc"
3446
version = "0.48.5"
3447
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3454
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
3455
3456
[[package]]
3457
-
name = "wit-bindgen-rt"
3458
-
version = "0.39.0"
3459
source = "registry+https://github.com/rust-lang/crates.io-index"
3460
-
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
3461
-
dependencies = [
3462
-
"bitflags",
3463
-
]
3464
3465
[[package]]
3466
name = "writeable"
3467
-
version = "0.6.1"
3468
source = "registry+https://github.com/rust-lang/crates.io-index"
3469
-
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
3470
3471
[[package]]
3472
name = "yoke"
3473
-
version = "0.8.0"
3474
source = "registry+https://github.com/rust-lang/crates.io-index"
3475
-
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
3476
dependencies = [
3477
-
"serde",
3478
"stable_deref_trait",
3479
"yoke-derive",
3480
"zerofrom",
···
3482
3483
[[package]]
3484
name = "yoke-derive"
3485
-
version = "0.8.0"
3486
source = "registry+https://github.com/rust-lang/crates.io-index"
3487
-
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
3488
dependencies = [
3489
"proc-macro2",
3490
"quote",
3491
-
"syn",
3492
"synstructure",
3493
]
3494
3495
[[package]]
3496
name = "zerocopy"
3497
-
version = "0.8.26"
3498
source = "registry+https://github.com/rust-lang/crates.io-index"
3499
-
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
3500
dependencies = [
3501
"zerocopy-derive",
3502
]
3503
3504
[[package]]
3505
name = "zerocopy-derive"
3506
-
version = "0.8.26"
3507
source = "registry+https://github.com/rust-lang/crates.io-index"
3508
-
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
3509
dependencies = [
3510
"proc-macro2",
3511
"quote",
3512
-
"syn",
3513
]
3514
3515
[[package]]
···
3529
dependencies = [
3530
"proc-macro2",
3531
"quote",
3532
-
"syn",
3533
"synstructure",
3534
]
3535
3536
[[package]]
3537
name = "zeroize"
3538
-
version = "1.8.1"
3539
source = "registry+https://github.com/rust-lang/crates.io-index"
3540
-
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
3541
dependencies = [
3542
"zeroize_derive",
3543
]
3544
3545
[[package]]
3546
name = "zeroize_derive"
3547
-
version = "1.4.2"
3548
source = "registry+https://github.com/rust-lang/crates.io-index"
3549
-
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
3550
dependencies = [
3551
"proc-macro2",
3552
"quote",
3553
-
"syn",
3554
]
3555
3556
[[package]]
3557
name = "zerotrie"
3558
-
version = "0.2.2"
3559
source = "registry+https://github.com/rust-lang/crates.io-index"
3560
-
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
3561
dependencies = [
3562
"displaydoc",
3563
"yoke",
···
3566
3567
[[package]]
3568
name = "zerovec"
3569
-
version = "0.11.4"
3570
source = "registry+https://github.com/rust-lang/crates.io-index"
3571
-
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
3572
dependencies = [
3573
"yoke",
3574
"zerofrom",
···
3577
3578
[[package]]
3579
name = "zerovec-derive"
3580
-
version = "0.11.1"
3581
source = "registry+https://github.com/rust-lang/crates.io-index"
3582
-
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
3583
dependencies = [
3584
"proc-macro2",
3585
"quote",
3586
-
"syn",
3587
]
3588
3589
[[package]]
3590
name = "zstd"
3591
version = "0.13.3"
3592
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3606
3607
[[package]]
3608
name = "zstd-sys"
3609
-
version = "2.0.15+zstd.1.5.7"
3610
source = "registry+https://github.com/rust-lang/crates.io-index"
3611
-
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
3612
dependencies = [
3613
"cc",
3614
"pkg-config",
···
3
version = 4
4
5
[[package]]
6
+
name = "abnf"
7
+
version = "0.13.0"
8
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
dependencies = [
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",
22
]
23
24
[[package]]
···
41
42
[[package]]
43
name = "aho-corasick"
44
+
version = "1.1.4"
45
source = "registry+https://github.com/rust-lang/crates.io-index"
46
+
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
47
dependencies = [
48
"memchr",
49
]
50
51
[[package]]
52
+
name = "aliasable"
53
+
version = "0.1.3"
54
source = "registry+https://github.com/rust-lang/crates.io-index"
55
+
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
56
57
[[package]]
58
+
name = "allocator-api2"
59
+
version = "0.2.21"
60
source = "registry+https://github.com/rust-lang/crates.io-index"
61
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
62
63
[[package]]
64
name = "android_system_properties"
···
71
72
[[package]]
73
name = "anyhow"
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"
81
source = "registry+https://github.com/rust-lang/crates.io-index"
82
+
checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a"
83
+
dependencies = [
84
+
"object",
85
+
]
86
87
[[package]]
88
name = "async-compression"
89
+
version = "0.4.36"
90
source = "registry+https://github.com/rust-lang/crates.io-index"
91
+
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
92
dependencies = [
93
+
"compression-codecs",
94
+
"compression-core",
95
"futures-core",
96
"pin-project-lite",
97
"tokio",
98
]
99
100
[[package]]
···
105
dependencies = [
106
"proc-macro2",
107
"quote",
108
+
"syn 2.0.112",
109
]
110
111
[[package]]
···
118
]
119
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]]
130
name = "atomic-waker"
131
version = "1.1.2"
132
source = "registry+https://github.com/rust-lang/crates.io-index"
···
140
141
[[package]]
142
name = "aws-lc-rs"
143
+
version = "1.15.2"
144
source = "registry+https://github.com/rust-lang/crates.io-index"
145
+
checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288"
146
dependencies = [
147
"aws-lc-sys",
148
"untrusted 0.7.1",
···
151
152
[[package]]
153
name = "aws-lc-sys"
154
+
version = "0.35.0"
155
source = "registry+https://github.com/rust-lang/crates.io-index"
156
+
checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1"
157
dependencies = [
158
"cc",
159
"cmake",
160
"dunce",
···
163
164
[[package]]
165
name = "axum"
166
+
version = "0.8.8"
167
source = "registry+https://github.com/rust-lang/crates.io-index"
168
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
169
dependencies = [
170
"axum-core",
171
"axum-macros",
···
183
"mime",
184
"percent-encoding",
185
"pin-project-lite",
186
+
"serde_core",
187
"serde_json",
188
"serde_path_to_error",
189
"serde_urlencoded",
···
197
198
[[package]]
199
name = "axum-core"
200
+
version = "0.5.6"
201
source = "registry+https://github.com/rust-lang/crates.io-index"
202
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
203
dependencies = [
204
"bytes",
205
"futures-core",
···
208
"http-body-util",
209
"mime",
210
"pin-project-lite",
211
"sync_wrapper",
212
"tower-layer",
213
"tower-service",
···
222
dependencies = [
223
"proc-macro2",
224
"quote",
225
+
"syn 2.0.112",
226
]
227
228
[[package]]
···
234
"axum",
235
"handlebars",
236
"serde",
237
+
"thiserror 2.0.17",
238
]
239
240
[[package]]
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"
249
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
257
dependencies = [
258
+
"const-str",
259
+
"match-lookup",
260
]
261
262
[[package]]
···
267
268
[[package]]
269
name = "base64ct"
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"
277
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
291
292
[[package]]
293
+
name = "bon"
294
+
version = "3.8.1"
295
source = "registry+https://github.com/rust-lang/crates.io-index"
296
+
checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1"
297
dependencies = [
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",
310
"prettyplease",
311
"proc-macro2",
312
"quote",
313
+
"rustversion",
314
+
"syn 2.0.112",
315
]
316
317
[[package]]
318
+
name = "borsh"
319
+
version = "1.6.0"
320
source = "registry+https://github.com/rust-lang/crates.io-index"
321
+
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
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",
333
"serde",
334
]
335
336
[[package]]
337
+
name = "btree-range-map"
338
+
version = "0.7.2"
339
source = "registry+https://github.com/rust-lang/crates.io-index"
340
+
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
341
dependencies = [
342
+
"btree-slab",
343
+
"cc-traits",
344
+
"range-traits",
345
+
"serde",
346
+
"slab",
347
]
348
349
[[package]]
350
+
name = "btree-slab"
351
+
version = "0.6.1"
352
source = "registry+https://github.com/rust-lang/crates.io-index"
353
+
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
354
dependencies = [
355
+
"cc-traits",
356
+
"slab",
357
+
"smallvec",
358
]
359
360
[[package]]
361
name = "bumpalo"
362
+
version = "3.19.1"
363
source = "registry+https://github.com/rust-lang/crates.io-index"
364
+
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
365
366
[[package]]
367
name = "byteorder"
···
371
372
[[package]]
373
name = "bytes"
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"
384
source = "registry+https://github.com/rust-lang/crates.io-index"
385
+
checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4"
386
+
dependencies = [
387
+
"serde",
388
+
]
389
390
[[package]]
391
name = "cc"
392
+
version = "1.2.51"
393
source = "registry+https://github.com/rust-lang/crates.io-index"
394
+
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
395
dependencies = [
396
+
"find-msvc-tools",
397
"jobserver",
398
"libc",
399
"shlex",
400
]
401
402
[[package]]
403
+
name = "cc-traits"
404
+
version = "2.0.0"
405
source = "registry+https://github.com/rust-lang/crates.io-index"
406
+
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
407
dependencies = [
408
+
"slab",
409
]
410
411
[[package]]
412
name = "cfg-if"
413
+
version = "1.0.4"
414
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
422
423
[[package]]
424
name = "chrono"
425
+
version = "0.4.42"
426
source = "registry+https://github.com/rust-lang/crates.io-index"
427
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
428
dependencies = [
429
"iana-time-zone",
430
"js-sys",
431
"num-traits",
432
+
"serde",
433
"wasm-bindgen",
434
"windows-link",
435
]
···
469
dependencies = [
470
"ciborium-io",
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",
486
]
487
488
[[package]]
···
496
]
497
498
[[package]]
499
+
name = "cmake"
500
+
version = "0.1.57"
501
source = "registry+https://github.com/rust-lang/crates.io-index"
502
+
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
503
dependencies = [
504
+
"cc",
505
]
506
507
[[package]]
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"
519
source = "registry+https://github.com/rust-lang/crates.io-index"
520
+
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
521
dependencies = [
522
+
"compression-core",
523
+
"flate2",
524
+
"memchr",
525
+
"zstd",
526
+
"zstd-safe",
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"
534
535
[[package]]
536
name = "concurrent-queue"
···
548
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
549
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]]
577
name = "core-foundation-sys"
578
version = "0.8.7"
579
source = "registry+https://github.com/rust-lang/crates.io-index"
580
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
581
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]]
592
name = "cpufeatures"
593
version = "0.2.17"
594
source = "registry+https://github.com/rust-lang/crates.io-index"
···
599
600
[[package]]
601
name = "crc"
602
+
version = "3.4.0"
603
source = "registry+https://github.com/rust-lang/crates.io-index"
604
+
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
605
dependencies = [
606
"crc-catalog",
607
]
···
613
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
614
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]]
631
name = "crossbeam-queue"
632
version = "0.3.12"
633
source = "registry+https://github.com/rust-lang/crates.io-index"
···
649
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
650
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]]
664
name = "crypto-common"
665
+
version = "0.1.7"
666
source = "registry+https://github.com/rust-lang/crates.io-index"
667
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
668
dependencies = [
669
"generic-array",
670
"typenum",
···
676
source = "registry+https://github.com/rust-lang/crates.io-index"
677
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
678
dependencies = [
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",
691
]
692
693
[[package]]
···
701
"proc-macro2",
702
"quote",
703
"strsim",
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",
719
]
720
721
[[package]]
···
724
source = "registry+https://github.com/rust-lang/crates.io-index"
725
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
726
dependencies = [
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",
739
"quote",
740
+
"syn 2.0.112",
741
]
742
743
[[package]]
···
755
]
756
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]]
784
name = "der"
785
version = "0.7.10"
786
source = "registry+https://github.com/rust-lang/crates.io-index"
···
792
]
793
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]]
805
name = "derive_builder"
806
version = "0.20.2"
807
source = "registry+https://github.com/rust-lang/crates.io-index"
···
816
source = "registry+https://github.com/rust-lang/crates.io-index"
817
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
818
dependencies = [
819
+
"darling 0.20.11",
820
"proc-macro2",
821
"quote",
822
+
"syn 2.0.112",
823
]
824
825
[[package]]
···
829
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
830
dependencies = [
831
"derive_builder_core",
832
+
"syn 2.0.112",
833
]
834
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]]
863
name = "digest"
864
version = "0.10.7"
865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
879
dependencies = [
880
"proc-macro2",
881
"quote",
882
+
"syn 2.0.112",
883
]
884
885
[[package]]
···
895
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
896
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]]
918
name = "either"
919
version = "1.15.0"
920
source = "registry+https://github.com/rust-lang/crates.io-index"
···
924
]
925
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]]
947
name = "email-encoding"
948
version = "0.4.1"
949
source = "registry+https://github.com/rust-lang/crates.io-index"
···
960
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
961
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]]
984
name = "equivalent"
985
version = "1.0.2"
986
source = "registry+https://github.com/rust-lang/crates.io-index"
···
988
989
[[package]]
990
name = "errno"
991
+
version = "0.3.14"
992
source = "registry+https://github.com/rust-lang/crates.io-index"
993
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
994
dependencies = [
995
"libc",
996
"windows-sys 0.59.0",
···
1023
version = "2.3.0"
1024
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
1052
1053
[[package]]
1054
name = "flume"
···
1058
dependencies = [
1059
"futures-core",
1060
"futures-sink",
1061
+
"spin 0.9.8",
1062
]
1063
1064
[[package]]
···
1074
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1075
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]]
1098
name = "form_urlencoded"
1099
+
version = "1.2.2"
1100
source = "registry+https://github.com/rust-lang/crates.io-index"
1101
+
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
1102
dependencies = [
1103
"percent-encoding",
1104
]
···
1120
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
1121
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]]
1136
name = "futures-channel"
1137
version = "0.3.31"
1138
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1177
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
1178
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]]
1204
name = "futures-sink"
1205
version = "0.3.31"
1206
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1226
dependencies = [
1227
"futures-core",
1228
"futures-io",
1229
+
"futures-macro",
1230
"futures-sink",
1231
"futures-task",
1232
"memchr",
···
1236
]
1237
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]]
1254
name = "generic-array"
1255
version = "0.14.7"
1256
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1258
dependencies = [
1259
"typenum",
1260
"version_check",
1261
+
"zeroize",
1262
]
1263
1264
[[package]]
···
1268
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
1269
dependencies = [
1270
"cfg-if",
1271
+
"js-sys",
1272
"libc",
1273
+
"wasi",
1274
+
"wasm-bindgen",
1275
]
1276
1277
[[package]]
1278
name = "getrandom"
1279
+
version = "0.3.4"
1280
source = "registry+https://github.com/rust-lang/crates.io-index"
1281
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
1282
dependencies = [
1283
"cfg-if",
1284
"js-sys",
1285
"libc",
1286
"r-efi",
1287
+
"wasip2",
1288
"wasm-bindgen",
1289
]
1290
1291
[[package]]
1292
name = "globset"
1293
+
version = "0.4.18"
1294
source = "registry+https://github.com/rust-lang/crates.io-index"
1295
+
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
1296
dependencies = [
1297
"aho-corasick",
1298
"bstr",
1299
"log",
1300
+
"regex-automata",
1301
+
"regex-syntax",
1302
]
1303
1304
[[package]]
1305
name = "governor"
1306
+
version = "0.10.4"
1307
source = "registry+https://github.com/rust-lang/crates.io-index"
1308
+
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
1309
dependencies = [
1310
"cfg-if",
1311
"dashmap",
1312
"futures-sink",
1313
"futures-timer",
1314
"futures-util",
1315
+
"getrandom 0.3.4",
1316
+
"hashbrown 0.16.1",
1317
"nonzero_ext",
1318
"parking_lot",
1319
"portable-atomic",
···
1325
]
1326
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]]
1339
name = "h2"
1340
version = "0.4.12"
1341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1347
"futures-core",
1348
"futures-sink",
1349
"http",
1350
+
"indexmap 2.12.1",
1351
"slab",
1352
"tokio",
1353
"tokio-util",
···
1356
1357
[[package]]
1358
name = "half"
1359
+
version = "2.7.1"
1360
source = "registry+https://github.com/rust-lang/crates.io-index"
1361
+
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
1362
dependencies = [
1363
"cfg-if",
1364
"crunchy",
1365
+
"zerocopy",
1366
]
1367
1368
[[package]]
1369
name = "handlebars"
1370
+
version = "6.4.0"
1371
source = "registry+https://github.com/rust-lang/crates.io-index"
1372
+
checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e"
1373
dependencies = [
1374
"derive_builder",
1375
"log",
···
1379
"rust-embed",
1380
"serde",
1381
"serde_json",
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",
1392
]
1393
1394
[[package]]
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"
1402
version = "0.14.5"
1403
source = "registry+https://github.com/rust-lang/crates.io-index"
1404
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
···
1415
dependencies = [
1416
"allocator-api2",
1417
"equivalent",
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",
1430
]
1431
1432
[[package]]
···
1439
]
1440
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]]
1462
name = "heck"
1463
version = "0.5.0"
1464
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1471
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1472
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]]
1480
name = "hkdf"
1481
version = "0.12.4"
1482
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1496
1497
[[package]]
1498
name = "home"
1499
+
version = "0.5.12"
1500
source = "registry+https://github.com/rust-lang/crates.io-index"
1501
+
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
1502
dependencies = [
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",
1513
]
1514
1515
[[package]]
1516
name = "http"
1517
+
version = "1.4.0"
1518
source = "registry+https://github.com/rust-lang/crates.io-index"
1519
+
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
1520
dependencies = [
1521
"bytes",
1522
"itoa",
1523
]
1524
···
1559
1560
[[package]]
1561
name = "hyper"
1562
+
version = "1.8.1"
1563
source = "registry+https://github.com/rust-lang/crates.io-index"
1564
+
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
1565
dependencies = [
1566
+
"atomic-waker",
1567
"bytes",
1568
"futures-channel",
1569
+
"futures-core",
1570
"h2",
1571
"http",
1572
"http-body",
···
1574
"httpdate",
1575
"itoa",
1576
"pin-project-lite",
1577
+
"pin-utils",
1578
"smallvec",
1579
"tokio",
1580
"want",
1581
]
1582
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]]
1601
name = "hyper-timeout"
1602
version = "0.5.2"
1603
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1612
1613
[[package]]
1614
name = "hyper-util"
1615
+
version = "0.1.19"
1616
source = "registry+https://github.com/rust-lang/crates.io-index"
1617
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
1618
dependencies = [
1619
+
"base64",
1620
"bytes",
1621
"futures-channel",
1622
"futures-core",
···
1624
"http",
1625
"http-body",
1626
"hyper",
1627
+
"ipnet",
1628
"libc",
1629
+
"percent-encoding",
1630
"pin-project-lite",
1631
"socket2",
1632
+
"system-configuration",
1633
"tokio",
1634
"tower-service",
1635
"tracing",
1636
+
"windows-registry",
1637
]
1638
1639
[[package]]
1640
name = "iana-time-zone"
1641
+
version = "0.1.64"
1642
source = "registry+https://github.com/rust-lang/crates.io-index"
1643
+
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
1644
dependencies = [
1645
"android_system_properties",
1646
"core-foundation-sys",
···
1662
1663
[[package]]
1664
name = "icu_collections"
1665
+
version = "2.1.1"
1666
source = "registry+https://github.com/rust-lang/crates.io-index"
1667
+
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
1668
dependencies = [
1669
"displaydoc",
1670
"potential_utf",
···
1675
1676
[[package]]
1677
name = "icu_locale_core"
1678
+
version = "2.1.1"
1679
source = "registry+https://github.com/rust-lang/crates.io-index"
1680
+
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
1681
dependencies = [
1682
"displaydoc",
1683
"litemap",
···
1688
1689
[[package]]
1690
name = "icu_normalizer"
1691
+
version = "2.1.1"
1692
source = "registry+https://github.com/rust-lang/crates.io-index"
1693
+
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
1694
dependencies = [
1695
"icu_collections",
1696
"icu_normalizer_data",
1697
"icu_properties",
···
1702
1703
[[package]]
1704
name = "icu_normalizer_data"
1705
+
version = "2.1.1"
1706
source = "registry+https://github.com/rust-lang/crates.io-index"
1707
+
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
1708
1709
[[package]]
1710
name = "icu_properties"
1711
+
version = "2.1.2"
1712
source = "registry+https://github.com/rust-lang/crates.io-index"
1713
+
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1714
dependencies = [
1715
"icu_collections",
1716
"icu_locale_core",
1717
"icu_properties_data",
1718
"icu_provider",
1719
"zerotrie",
1720
"zerovec",
1721
]
1722
1723
[[package]]
1724
name = "icu_properties_data"
1725
+
version = "2.1.2"
1726
source = "registry+https://github.com/rust-lang/crates.io-index"
1727
+
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1728
1729
[[package]]
1730
name = "icu_provider"
1731
+
version = "2.1.1"
1732
source = "registry+https://github.com/rust-lang/crates.io-index"
1733
+
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
1734
dependencies = [
1735
"displaydoc",
1736
"icu_locale_core",
1737
"writeable",
1738
"yoke",
1739
"zerofrom",
···
1749
1750
[[package]]
1751
name = "idna"
1752
+
version = "1.1.0"
1753
source = "registry+https://github.com/rust-lang/crates.io-index"
1754
+
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
1755
dependencies = [
1756
"idna_adapter",
1757
"smallvec",
···
1770
1771
[[package]]
1772
name = "indexmap"
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"
1785
source = "registry+https://github.com/rust-lang/crates.io-index"
1786
+
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
1787
dependencies = [
1788
"equivalent",
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",
1801
]
1802
1803
[[package]]
···
1810
]
1811
1812
[[package]]
1813
+
name = "inventory"
1814
+
version = "0.3.21"
1815
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
1826
dependencies = [
1827
+
"cid",
1828
+
"serde",
1829
+
"serde_bytes",
1830
+
]
1831
+
1832
+
[[package]]
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"
1841
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1842
+
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
1843
+
dependencies = [
1844
+
"memchr",
1845
+
"serde",
1846
]
1847
1848
[[package]]
1849
name = "itoa"
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"
1931
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
1980
1981
[[package]]
1982
name = "jobserver"
1983
+
version = "0.1.34"
1984
source = "registry+https://github.com/rust-lang/crates.io-index"
1985
+
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
1986
dependencies = [
1987
+
"getrandom 0.3.4",
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",
2006
]
2007
2008
[[package]]
2009
name = "js-sys"
2010
+
version = "0.3.83"
2011
source = "registry+https://github.com/rust-lang/crates.io-index"
2012
+
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
2013
dependencies = [
2014
"once_cell",
2015
"wasm-bindgen",
···
2038
]
2039
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]]
2064
+
name = "lazy_static"
2065
+
version = "1.5.0"
2066
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2067
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
2068
+
dependencies = [
2069
+
"spin 0.9.8",
2070
+
]
2071
2072
[[package]]
2073
name = "lettre"
2074
+
version = "0.11.19"
2075
source = "registry+https://github.com/rust-lang/crates.io-index"
2076
+
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
2077
dependencies = [
2078
"async-trait",
2079
"base64",
···
2094
"tokio",
2095
"tokio-rustls",
2096
"url",
2097
+
"webpki-roots 1.0.5",
2098
]
2099
2100
[[package]]
2101
name = "libc"
2102
+
version = "0.2.178"
2103
source = "registry+https://github.com/rust-lang/crates.io-index"
2104
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
2105
2106
[[package]]
2107
name = "libm"
···
2111
2112
[[package]]
2113
name = "libredox"
2114
+
version = "0.1.12"
2115
source = "registry+https://github.com/rust-lang/crates.io-index"
2116
+
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
2117
dependencies = [
2118
"bitflags",
2119
"libc",
2120
+
"redox_syscall 0.7.0",
2121
]
2122
2123
[[package]]
···
2132
]
2133
2134
[[package]]
2135
name = "litemap"
2136
+
version = "0.8.1"
2137
source = "registry+https://github.com/rust-lang/crates.io-index"
2138
+
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
2139
2140
[[package]]
2141
name = "lock_api"
2142
+
version = "0.4.14"
2143
source = "registry+https://github.com/rust-lang/crates.io-index"
2144
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
2145
dependencies = [
2146
"scopeguard",
2147
]
2148
2149
[[package]]
2150
name = "log"
2151
+
version = "0.4.29"
2152
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
2184
2185
[[package]]
2186
name = "matchers"
2187
+
version = "0.2.0"
2188
source = "registry+https://github.com/rust-lang/crates.io-index"
2189
+
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
2190
dependencies = [
2191
+
"regex-automata",
2192
]
2193
2194
[[package]]
···
2209
2210
[[package]]
2211
name = "memchr"
2212
+
version = "2.7.6"
2213
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
2237
2238
[[package]]
2239
name = "mime"
···
2254
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
2255
dependencies = [
2256
"adler2",
2257
+
"simd-adler32",
2258
]
2259
2260
[[package]]
2261
name = "mio"
2262
+
version = "1.1.1"
2263
source = "registry+https://github.com/rust-lang/crates.io-index"
2264
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
2265
dependencies = [
2266
"libc",
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",
2313
]
2314
2315
[[package]]
···
2345
2346
[[package]]
2347
name = "nu-ansi-term"
2348
+
version = "0.50.3"
2349
source = "registry+https://github.com/rust-lang/crates.io-index"
2350
+
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
2351
dependencies = [
2352
+
"windows-sys 0.59.0",
2353
]
2354
2355
[[package]]
2356
name = "num-bigint-dig"
2357
+
version = "0.8.6"
2358
source = "registry+https://github.com/rust-lang/crates.io-index"
2359
+
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
2360
dependencies = [
2361
"lazy_static",
2362
"libm",
2363
"num-integer",
···
2367
"smallvec",
2368
"zeroize",
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"
2376
2377
[[package]]
2378
name = "num-integer"
···
2421
2422
[[package]]
2423
name = "object"
2424
+
version = "0.32.2"
2425
source = "registry+https://github.com/rust-lang/crates.io-index"
2426
+
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
2427
dependencies = [
2428
"memchr",
2429
]
···
2435
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
2436
2437
[[package]]
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"
2454
version = "0.1.1"
2455
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
2510
2511
[[package]]
2512
name = "parking"
···
2516
2517
[[package]]
2518
name = "parking_lot"
2519
+
version = "0.12.5"
2520
source = "registry+https://github.com/rust-lang/crates.io-index"
2521
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
2522
dependencies = [
2523
"lock_api",
2524
"parking_lot_core",
···
2526
2527
[[package]]
2528
name = "parking_lot_core"
2529
+
version = "0.9.12"
2530
source = "registry+https://github.com/rust-lang/crates.io-index"
2531
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
2532
dependencies = [
2533
"cfg-if",
2534
"libc",
2535
+
"redox_syscall 0.5.18",
2536
"smallvec",
2537
+
"windows-link",
2538
]
2539
2540
[[package]]
···
2560
2561
[[package]]
2562
name = "pds_gatekeeper"
2563
+
version = "0.1.2"
2564
dependencies = [
2565
"anyhow",
2566
"aws-lc-rs",
2567
"axum",
2568
"axum-template",
2569
"chrono",
2570
+
"dashmap",
2571
"dotenvy",
2572
"handlebars",
2573
"hex",
2574
+
"html-escape",
2575
"hyper-util",
2576
+
"jacquard-common",
2577
+
"jacquard-identity",
2578
+
"josekit",
2579
"jwt-compact",
2580
"lettre",
2581
+
"multibase",
2582
"rand 0.9.2",
2583
+
"reqwest",
2584
"rust-embed",
2585
"rustls",
2586
"scrypt",
···
2589
"sha2",
2590
"sqlx",
2591
"tokio",
2592
+
"tower",
2593
"tower-http",
2594
"tower_governor",
2595
"tracing",
2596
"tracing-subscriber",
2597
+
"urlencoding",
2598
]
2599
2600
[[package]]
···
2608
2609
[[package]]
2610
name = "percent-encoding"
2611
+
version = "2.3.2"
2612
source = "registry+https://github.com/rust-lang/crates.io-index"
2613
+
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
2614
2615
[[package]]
2616
name = "pest"
2617
+
version = "2.8.4"
2618
source = "registry+https://github.com/rust-lang/crates.io-index"
2619
+
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
2620
dependencies = [
2621
"memchr",
2622
"ucd-trie",
2623
]
2624
2625
[[package]]
2626
name = "pest_derive"
2627
+
version = "2.8.4"
2628
source = "registry+https://github.com/rust-lang/crates.io-index"
2629
+
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
2630
dependencies = [
2631
"pest",
2632
"pest_generator",
···
2634
2635
[[package]]
2636
name = "pest_generator"
2637
+
version = "2.8.4"
2638
source = "registry+https://github.com/rust-lang/crates.io-index"
2639
+
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
2640
dependencies = [
2641
"pest",
2642
"pest_meta",
2643
"proc-macro2",
2644
"quote",
2645
+
"syn 2.0.112",
2646
]
2647
2648
[[package]]
2649
name = "pest_meta"
2650
+
version = "2.8.4"
2651
source = "registry+https://github.com/rust-lang/crates.io-index"
2652
+
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
2653
dependencies = [
2654
"pest",
2655
"sha2",
···
2672
dependencies = [
2673
"proc-macro2",
2674
"quote",
2675
+
"syn 2.0.112",
2676
]
2677
2678
[[package]]
···
2716
2717
[[package]]
2718
name = "portable-atomic"
2719
+
version = "1.13.0"
2720
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
2735
2736
[[package]]
2737
name = "potential_utf"
2738
+
version = "0.1.4"
2739
source = "registry+https://github.com/rust-lang/crates.io-index"
2740
+
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
2741
dependencies = [
2742
"zerovec",
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"
2750
2751
[[package]]
2752
name = "ppv-lite86"
···
2759
2760
[[package]]
2761
name = "prettyplease"
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"
2795
source = "registry+https://github.com/rust-lang/crates.io-index"
2796
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2797
dependencies = [
2798
"proc-macro2",
2799
+
"quote",
2800
+
"version_check",
2801
]
2802
2803
[[package]]
2804
name = "proc-macro2"
2805
+
version = "1.0.104"
2806
source = "registry+https://github.com/rust-lang/crates.io-index"
2807
+
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
2808
dependencies = [
2809
"unicode-ident",
2810
]
2811
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]]
2826
name = "psm"
2827
+
version = "0.1.28"
2828
source = "registry+https://github.com/rust-lang/crates.io-index"
2829
+
checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01"
2830
dependencies = [
2831
+
"ar_archive_writer",
2832
"cc",
2833
]
2834
···
2842
"libc",
2843
"once_cell",
2844
"raw-cpuid",
2845
+
"wasi",
2846
"web-sys",
2847
"winapi",
2848
]
2849
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]]
2906
name = "quote"
2907
+
version = "1.0.42"
2908
source = "registry+https://github.com/rust-lang/crates.io-index"
2909
+
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
2910
dependencies = [
2911
"proc-macro2",
2912
]
···
2979
source = "registry+https://github.com/rust-lang/crates.io-index"
2980
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
2981
dependencies = [
2982
+
"getrandom 0.3.4",
2983
]
2984
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]]
2992
name = "raw-cpuid"
2993
+
version = "11.6.0"
2994
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2995
+
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
2996
+
dependencies = [
2997
+
"bitflags",
2998
+
]
2999
+
3000
+
[[package]]
3001
+
name = "redox_syscall"
3002
+
version = "0.5.18"
3003
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
3014
dependencies = [
3015
"bitflags",
3016
]
3017
3018
[[package]]
3019
+
name = "ref-cast"
3020
+
version = "1.0.25"
3021
source = "registry+https://github.com/rust-lang/crates.io-index"
3022
+
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
3023
dependencies = [
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",
3036
]
3037
3038
[[package]]
3039
+
name = "regex"
3040
+
version = "1.12.2"
3041
source = "registry+https://github.com/rust-lang/crates.io-index"
3042
+
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
3043
dependencies = [
3044
+
"aho-corasick",
3045
+
"memchr",
3046
+
"regex-automata",
3047
+
"regex-syntax",
3048
]
3049
3050
[[package]]
3051
name = "regex-automata"
3052
+
version = "0.4.13"
3053
source = "registry+https://github.com/rust-lang/crates.io-index"
3054
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
3055
dependencies = [
3056
"aho-corasick",
3057
"memchr",
3058
+
"regex-syntax",
3059
]
3060
3061
[[package]]
3062
+
name = "regex-lite"
3063
+
version = "0.1.8"
3064
source = "registry+https://github.com/rust-lang/crates.io-index"
3065
+
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
3066
3067
[[package]]
3068
name = "regex-syntax"
3069
+
version = "0.8.8"
3070
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
3123
3124
[[package]]
3125
name = "ring"
···
3137
3138
[[package]]
3139
name = "rsa"
3140
+
version = "0.9.9"
3141
source = "registry+https://github.com/rust-lang/crates.io-index"
3142
+
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
3143
dependencies = [
3144
"const-oid",
3145
"digest",
···
3157
3158
[[package]]
3159
name = "rust-embed"
3160
+
version = "8.9.0"
3161
source = "registry+https://github.com/rust-lang/crates.io-index"
3162
+
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
3163
dependencies = [
3164
"rust-embed-impl",
3165
"rust-embed-utils",
···
3168
3169
[[package]]
3170
name = "rust-embed-impl"
3171
+
version = "8.9.0"
3172
source = "registry+https://github.com/rust-lang/crates.io-index"
3173
+
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
3174
dependencies = [
3175
"proc-macro2",
3176
"quote",
3177
"rust-embed-utils",
3178
+
"syn 2.0.112",
3179
"walkdir",
3180
]
3181
3182
[[package]]
3183
name = "rust-embed-utils"
3184
+
version = "8.9.0"
3185
source = "registry+https://github.com/rust-lang/crates.io-index"
3186
+
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
3187
dependencies = [
3188
"globset",
3189
"sha2",
···
3191
]
3192
3193
[[package]]
3194
name = "rustc-hash"
3195
+
version = "2.1.1"
3196
source = "registry+https://github.com/rust-lang/crates.io-index"
3197
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3198
3199
[[package]]
3200
+
name = "rustc_version"
3201
+
version = "0.4.1"
3202
source = "registry+https://github.com/rust-lang/crates.io-index"
3203
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
3204
dependencies = [
3205
+
"semver",
3206
]
3207
3208
[[package]]
3209
name = "rustls"
3210
+
version = "0.23.35"
3211
source = "registry+https://github.com/rust-lang/crates.io-index"
3212
+
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
3213
dependencies = [
3214
"aws-lc-rs",
3215
"log",
···
3223
3224
[[package]]
3225
name = "rustls-pki-types"
3226
+
version = "1.13.2"
3227
source = "registry+https://github.com/rust-lang/crates.io-index"
3228
+
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
3229
dependencies = [
3230
+
"web-time",
3231
"zeroize",
3232
]
3233
3234
[[package]]
3235
name = "rustls-webpki"
3236
+
version = "0.103.8"
3237
source = "registry+https://github.com/rust-lang/crates.io-index"
3238
+
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
3239
dependencies = [
3240
"aws-lc-rs",
3241
"ring",
···
3251
3252
[[package]]
3253
name = "ryu"
3254
+
version = "1.0.22"
3255
source = "registry+https://github.com/rust-lang/crates.io-index"
3256
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
3257
3258
[[package]]
3259
name = "salsa20"
···
3274
]
3275
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]]
3307
name = "scopeguard"
3308
version = "1.2.0"
3309
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3322
]
3323
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]]
3339
name = "secp256k1"
3340
version = "0.28.2"
3341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3354
]
3355
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]]
3369
name = "serde"
3370
+
version = "1.0.228"
3371
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
3393
dependencies = [
3394
"serde_derive",
3395
]
3396
3397
[[package]]
3398
name = "serde_derive"
3399
+
version = "1.0.228"
3400
source = "registry+https://github.com/rust-lang/crates.io-index"
3401
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
3402
dependencies = [
3403
"proc-macro2",
3404
"quote",
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",
3431
]
3432
3433
[[package]]
3434
name = "serde_json"
3435
+
version = "1.0.148"
3436
source = "registry+https://github.com/rust-lang/crates.io-index"
3437
+
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
3438
dependencies = [
3439
+
"indexmap 2.12.1",
3440
"itoa",
3441
"memchr",
3442
"serde",
3443
+
"serde_core",
3444
+
"zmij",
3445
]
3446
3447
[[package]]
3448
name = "serde_path_to_error"
3449
+
version = "0.1.20"
3450
source = "registry+https://github.com/rust-lang/crates.io-index"
3451
+
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
3452
dependencies = [
3453
"itoa",
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",
3467
]
3468
3469
[[package]]
···
3479
]
3480
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]]
3513
name = "sha1"
3514
version = "0.10.6"
3515
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3548
3549
[[package]]
3550
name = "signal-hook-registry"
3551
+
version = "1.4.8"
3552
source = "registry+https://github.com/rust-lang/crates.io-index"
3553
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
3554
dependencies = [
3555
+
"errno",
3556
"libc",
3557
]
3558
···
3565
"digest",
3566
"rand_core 0.6.4",
3567
]
3568
+
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]]
3576
name = "slab"
···
3588
]
3589
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]]
3601
name = "socket2"
3602
+
version = "0.6.1"
3603
source = "registry+https://github.com/rust-lang/crates.io-index"
3604
+
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
3605
dependencies = [
3606
"libc",
3607
+
"windows-sys 0.60.2",
3608
]
3609
3610
[[package]]
···
3617
]
3618
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]]
3626
name = "spinning_top"
3627
version = "0.3.0"
3628
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3673
"futures-util",
3674
"hashbrown 0.15.5",
3675
"hashlink",
3676
+
"indexmap 2.12.1",
3677
"log",
3678
"memchr",
3679
"once_cell",
···
3683
"serde_json",
3684
"sha2",
3685
"smallvec",
3686
+
"thiserror 2.0.17",
3687
"tokio",
3688
"tokio-stream",
3689
"tracing",
···
3701
"quote",
3702
"sqlx-core",
3703
"sqlx-macros-core",
3704
+
"syn 2.0.112",
3705
]
3706
3707
[[package]]
···
3712
dependencies = [
3713
"dotenvy",
3714
"either",
3715
+
"heck 0.5.0",
3716
"hex",
3717
"once_cell",
3718
"proc-macro2",
···
3724
"sqlx-mysql",
3725
"sqlx-postgres",
3726
"sqlx-sqlite",
3727
+
"syn 2.0.112",
3728
"tokio",
3729
"url",
3730
]
···
3767
"smallvec",
3768
"sqlx-core",
3769
"stringprep",
3770
+
"thiserror 2.0.17",
3771
"tracing",
3772
"whoami",
3773
]
···
3805
"smallvec",
3806
"sqlx-core",
3807
"stringprep",
3808
+
"thiserror 2.0.17",
3809
"tracing",
3810
"whoami",
3811
]
···
3830
"serde",
3831
"serde_urlencoded",
3832
"sqlx-core",
3833
+
"thiserror 2.0.17",
3834
"tracing",
3835
"url",
3836
]
3837
3838
[[package]]
3839
name = "stable_deref_trait"
3840
+
version = "1.2.1"
3841
source = "registry+https://github.com/rust-lang/crates.io-index"
3842
+
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3843
3844
[[package]]
3845
name = "stacker"
3846
+
version = "0.1.22"
3847
source = "registry+https://github.com/rust-lang/crates.io-index"
3848
+
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
3849
dependencies = [
3850
"cc",
3851
"cfg-if",
···
3855
]
3856
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]]
3884
name = "stringprep"
3885
version = "0.1.5"
3886
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3905
3906
[[package]]
3907
name = "syn"
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"
3920
source = "registry+https://github.com/rust-lang/crates.io-index"
3921
+
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
3922
dependencies = [
3923
"proc-macro2",
3924
"quote",
···
3930
version = "1.0.2"
3931
source = "registry+https://github.com/rust-lang/crates.io-index"
3932
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3933
+
dependencies = [
3934
+
"futures-core",
3935
+
]
3936
3937
[[package]]
3938
name = "synstructure"
···
3942
dependencies = [
3943
"proc-macro2",
3944
"quote",
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",
3967
]
3968
3969
[[package]]
···
3977
3978
[[package]]
3979
name = "thiserror"
3980
+
version = "2.0.17"
3981
source = "registry+https://github.com/rust-lang/crates.io-index"
3982
+
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
3983
dependencies = [
3984
+
"thiserror-impl 2.0.17",
3985
]
3986
3987
[[package]]
···
3992
dependencies = [
3993
"proc-macro2",
3994
"quote",
3995
+
"syn 2.0.112",
3996
]
3997
3998
[[package]]
3999
name = "thiserror-impl"
4000
+
version = "2.0.17"
4001
source = "registry+https://github.com/rust-lang/crates.io-index"
4002
+
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
4003
dependencies = [
4004
"proc-macro2",
4005
"quote",
4006
+
"syn 2.0.112",
4007
]
4008
4009
[[package]]
···
4016
]
4017
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]]
4050
name = "tinystr"
4051
+
version = "0.8.2"
4052
source = "registry+https://github.com/rust-lang/crates.io-index"
4053
+
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
4054
dependencies = [
4055
"displaydoc",
4056
"zerovec",
···
4058
4059
[[package]]
4060
name = "tinyvec"
4061
+
version = "1.10.0"
4062
source = "registry+https://github.com/rust-lang/crates.io-index"
4063
+
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
4064
dependencies = [
4065
"tinyvec_macros",
4066
]
···
4073
4074
[[package]]
4075
name = "tokio"
4076
+
version = "1.48.0"
4077
source = "registry+https://github.com/rust-lang/crates.io-index"
4078
+
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
4079
dependencies = [
4080
"bytes",
4081
"libc",
4082
"mio",
4083
"pin-project-lite",
4084
"signal-hook-registry",
4085
"socket2",
4086
"tokio-macros",
4087
+
"windows-sys 0.61.2",
4088
]
4089
4090
[[package]]
4091
name = "tokio-macros"
4092
+
version = "2.6.0"
4093
source = "registry+https://github.com/rust-lang/crates.io-index"
4094
+
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
4095
dependencies = [
4096
"proc-macro2",
4097
"quote",
4098
+
"syn 2.0.112",
4099
]
4100
4101
[[package]]
4102
name = "tokio-rustls"
4103
+
version = "0.26.4"
4104
source = "registry+https://github.com/rust-lang/crates.io-index"
4105
+
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
4106
dependencies = [
4107
"rustls",
4108
"tokio",
···
4121
4122
[[package]]
4123
name = "tokio-util"
4124
+
version = "0.7.17"
4125
source = "registry+https://github.com/rust-lang/crates.io-index"
4126
+
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
4127
dependencies = [
4128
"bytes",
4129
"futures-core",
4130
"futures-sink",
4131
+
"futures-util",
4132
"pin-project-lite",
4133
"tokio",
4134
]
4135
4136
[[package]]
4137
name = "tonic"
4138
+
version = "0.14.2"
4139
source = "registry+https://github.com/rust-lang/crates.io-index"
4140
+
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
4141
dependencies = [
4142
"async-trait",
4143
"axum",
···
4170
dependencies = [
4171
"futures-core",
4172
"futures-util",
4173
+
"indexmap 2.12.1",
4174
"pin-project-lite",
4175
"slab",
4176
"sync_wrapper",
···
4183
4184
[[package]]
4185
name = "tower-http"
4186
+
version = "0.6.8"
4187
source = "registry+https://github.com/rust-lang/crates.io-index"
4188
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
4189
dependencies = [
4190
"async-compression",
4191
"bitflags",
4192
"bytes",
4193
"futures-core",
4194
+
"futures-util",
4195
"http",
4196
"http-body",
4197
+
"http-body-util",
4198
+
"iri-string",
4199
"pin-project-lite",
4200
"tokio",
4201
"tokio-util",
4202
+
"tower",
4203
"tower-layer",
4204
"tower-service",
4205
]
···
4227
"governor",
4228
"http",
4229
"pin-project",
4230
+
"thiserror 2.0.17",
4231
"tonic",
4232
"tower",
4233
"tracing",
···
4235
4236
[[package]]
4237
name = "tracing"
4238
+
version = "0.1.44"
4239
source = "registry+https://github.com/rust-lang/crates.io-index"
4240
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
4241
dependencies = [
4242
"log",
4243
"pin-project-lite",
···
4247
4248
[[package]]
4249
name = "tracing-attributes"
4250
+
version = "0.1.31"
4251
source = "registry+https://github.com/rust-lang/crates.io-index"
4252
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
4253
dependencies = [
4254
"proc-macro2",
4255
"quote",
4256
+
"syn 2.0.112",
4257
]
4258
4259
[[package]]
4260
name = "tracing-core"
4261
+
version = "0.1.36"
4262
source = "registry+https://github.com/rust-lang/crates.io-index"
4263
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
4264
dependencies = [
4265
"once_cell",
4266
"valuable",
···
4279
4280
[[package]]
4281
name = "tracing-subscriber"
4282
+
version = "0.3.22"
4283
source = "registry+https://github.com/rust-lang/crates.io-index"
4284
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
4285
dependencies = [
4286
"matchers",
4287
"nu-ansi-term",
4288
"once_cell",
4289
+
"regex-automata",
4290
"sharded-slab",
4291
"smallvec",
4292
"thread_local",
···
4296
]
4297
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]]
4310
name = "try-lock"
4311
version = "0.2.5"
4312
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4314
4315
[[package]]
4316
name = "typenum"
4317
+
version = "1.19.0"
4318
source = "registry+https://github.com/rust-lang/crates.io-index"
4319
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
4320
4321
[[package]]
4322
name = "ucd-trie"
···
4332
4333
[[package]]
4334
name = "unicode-ident"
4335
+
version = "1.0.22"
4336
source = "registry+https://github.com/rust-lang/crates.io-index"
4337
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
4338
4339
[[package]]
4340
name = "unicode-normalization"
4341
+
version = "0.1.25"
4342
source = "registry+https://github.com/rust-lang/crates.io-index"
4343
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
4344
dependencies = [
4345
"tinyvec",
4346
]
4347
4348
[[package]]
4349
name = "unicode-properties"
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"
4357
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
4377
4378
[[package]]
4379
name = "untrusted"
···
4389
4390
[[package]]
4391
name = "url"
4392
+
version = "2.5.7"
4393
source = "registry+https://github.com/rust-lang/crates.io-index"
4394
+
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
4395
dependencies = [
4396
"form_urlencoded",
4397
"idna",
4398
"percent-encoding",
4399
+
"serde",
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"
4413
4414
[[package]]
4415
name = "utf8_iter"
···
4461
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
4462
4463
[[package]]
4464
+
name = "wasip2"
4465
+
version = "1.0.1+wasi-0.2.4"
4466
source = "registry+https://github.com/rust-lang/crates.io-index"
4467
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
4468
dependencies = [
4469
+
"wit-bindgen",
4470
]
4471
4472
[[package]]
···
4477
4478
[[package]]
4479
name = "wasm-bindgen"
4480
+
version = "0.2.106"
4481
source = "registry+https://github.com/rust-lang/crates.io-index"
4482
+
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
4483
dependencies = [
4484
"cfg-if",
4485
"once_cell",
4486
"rustversion",
4487
"wasm-bindgen-macro",
4488
+
"wasm-bindgen-shared",
4489
]
4490
4491
[[package]]
4492
+
name = "wasm-bindgen-futures"
4493
+
version = "0.4.56"
4494
source = "registry+https://github.com/rust-lang/crates.io-index"
4495
+
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
4496
dependencies = [
4497
+
"cfg-if",
4498
+
"js-sys",
4499
+
"once_cell",
4500
+
"wasm-bindgen",
4501
+
"web-sys",
4502
]
4503
4504
[[package]]
4505
name = "wasm-bindgen-macro"
4506
+
version = "0.2.106"
4507
source = "registry+https://github.com/rust-lang/crates.io-index"
4508
+
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
4509
dependencies = [
4510
"quote",
4511
"wasm-bindgen-macro-support",
···
4513
4514
[[package]]
4515
name = "wasm-bindgen-macro-support"
4516
+
version = "0.2.106"
4517
source = "registry+https://github.com/rust-lang/crates.io-index"
4518
+
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
4519
dependencies = [
4520
+
"bumpalo",
4521
"proc-macro2",
4522
"quote",
4523
+
"syn 2.0.112",
4524
"wasm-bindgen-shared",
4525
]
4526
4527
[[package]]
4528
name = "wasm-bindgen-shared"
4529
+
version = "0.2.106"
4530
source = "registry+https://github.com/rust-lang/crates.io-index"
4531
+
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
4532
dependencies = [
4533
"unicode-ident",
4534
]
4535
4536
[[package]]
4537
name = "web-sys"
4538
+
version = "0.3.83"
4539
source = "registry+https://github.com/rust-lang/crates.io-index"
4540
+
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
4541
dependencies = [
4542
"js-sys",
4543
"wasm-bindgen",
···
4559
source = "registry+https://github.com/rust-lang/crates.io-index"
4560
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
4561
dependencies = [
4562
+
"webpki-roots 1.0.5",
4563
]
4564
4565
[[package]]
4566
name = "webpki-roots"
4567
+
version = "1.0.5"
4568
source = "registry+https://github.com/rust-lang/crates.io-index"
4569
+
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
4570
dependencies = [
4571
"rustls-pki-types",
4572
]
4573
4574
[[package]]
4575
name = "whoami"
4576
version = "1.6.1"
4577
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4599
4600
[[package]]
4601
name = "winapi-util"
4602
+
version = "0.1.11"
4603
source = "registry+https://github.com/rust-lang/crates.io-index"
4604
+
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
4605
dependencies = [
4606
+
"windows-sys 0.48.0",
4607
]
4608
4609
[[package]]
···
4614
4615
[[package]]
4616
name = "windows-core"
4617
+
version = "0.62.2"
4618
source = "registry+https://github.com/rust-lang/crates.io-index"
4619
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
4620
dependencies = [
4621
"windows-implement",
4622
"windows-interface",
···
4627
4628
[[package]]
4629
name = "windows-implement"
4630
+
version = "0.60.2"
4631
source = "registry+https://github.com/rust-lang/crates.io-index"
4632
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
4633
dependencies = [
4634
"proc-macro2",
4635
"quote",
4636
+
"syn 2.0.112",
4637
]
4638
4639
[[package]]
4640
name = "windows-interface"
4641
+
version = "0.59.3"
4642
source = "registry+https://github.com/rust-lang/crates.io-index"
4643
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
4644
dependencies = [
4645
"proc-macro2",
4646
"quote",
4647
+
"syn 2.0.112",
4648
]
4649
4650
[[package]]
4651
name = "windows-link"
4652
+
version = "0.2.1"
4653
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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
+
]
4666
4667
[[package]]
4668
name = "windows-result"
4669
+
version = "0.4.1"
4670
source = "registry+https://github.com/rust-lang/crates.io-index"
4671
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
4672
dependencies = [
4673
"windows-link",
4674
]
4675
4676
[[package]]
4677
name = "windows-strings"
4678
+
version = "0.5.1"
4679
source = "registry+https://github.com/rust-lang/crates.io-index"
4680
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
4681
dependencies = [
4682
"windows-link",
4683
]
···
4710
]
4711
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]]
4731
name = "windows-targets"
4732
version = "0.48.5"
4733
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4751
"windows_aarch64_gnullvm 0.52.6",
4752
"windows_aarch64_msvc 0.52.6",
4753
"windows_i686_gnu 0.52.6",
4754
+
"windows_i686_gnullvm 0.52.6",
4755
"windows_i686_msvc 0.52.6",
4756
"windows_x86_64_gnu 0.52.6",
4757
"windows_x86_64_gnullvm 0.52.6",
···
4759
]
4760
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]]
4779
name = "windows_aarch64_gnullvm"
4780
version = "0.48.5"
4781
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4788
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
4789
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]]
4797
name = "windows_aarch64_msvc"
4798
version = "0.48.5"
4799
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4806
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
4807
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]]
4815
name = "windows_i686_gnu"
4816
version = "0.48.5"
4817
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4824
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
4825
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]]
4833
name = "windows_i686_gnullvm"
4834
version = "0.52.6"
4835
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
4843
4844
[[package]]
4845
name = "windows_i686_msvc"
···
4854
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
4855
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]]
4863
name = "windows_x86_64_gnu"
4864
version = "0.48.5"
4865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4872
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
4873
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]]
4881
name = "windows_x86_64_gnullvm"
4882
version = "0.48.5"
4883
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4890
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
4891
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]]
4899
name = "windows_x86_64_msvc"
4900
version = "0.48.5"
4901
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4908
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
4909
4910
[[package]]
4911
+
name = "windows_x86_64_msvc"
4912
+
version = "0.53.1"
4913
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
4921
4922
[[package]]
4923
name = "writeable"
4924
+
version = "0.6.2"
4925
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
4933
4934
[[package]]
4935
name = "yoke"
4936
+
version = "0.8.1"
4937
source = "registry+https://github.com/rust-lang/crates.io-index"
4938
+
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
4939
dependencies = [
4940
"stable_deref_trait",
4941
"yoke-derive",
4942
"zerofrom",
···
4944
4945
[[package]]
4946
name = "yoke-derive"
4947
+
version = "0.8.1"
4948
source = "registry+https://github.com/rust-lang/crates.io-index"
4949
+
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
4950
dependencies = [
4951
"proc-macro2",
4952
"quote",
4953
+
"syn 2.0.112",
4954
"synstructure",
4955
]
4956
4957
[[package]]
4958
name = "zerocopy"
4959
+
version = "0.8.31"
4960
source = "registry+https://github.com/rust-lang/crates.io-index"
4961
+
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
4962
dependencies = [
4963
"zerocopy-derive",
4964
]
4965
4966
[[package]]
4967
name = "zerocopy-derive"
4968
+
version = "0.8.31"
4969
source = "registry+https://github.com/rust-lang/crates.io-index"
4970
+
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
4971
dependencies = [
4972
"proc-macro2",
4973
"quote",
4974
+
"syn 2.0.112",
4975
]
4976
4977
[[package]]
···
4991
dependencies = [
4992
"proc-macro2",
4993
"quote",
4994
+
"syn 2.0.112",
4995
"synstructure",
4996
]
4997
4998
[[package]]
4999
name = "zeroize"
5000
+
version = "1.8.2"
5001
source = "registry+https://github.com/rust-lang/crates.io-index"
5002
+
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
5003
dependencies = [
5004
"zeroize_derive",
5005
]
5006
5007
[[package]]
5008
name = "zeroize_derive"
5009
+
version = "1.4.3"
5010
source = "registry+https://github.com/rust-lang/crates.io-index"
5011
+
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
5012
dependencies = [
5013
"proc-macro2",
5014
"quote",
5015
+
"syn 2.0.112",
5016
]
5017
5018
[[package]]
5019
name = "zerotrie"
5020
+
version = "0.2.3"
5021
source = "registry+https://github.com/rust-lang/crates.io-index"
5022
+
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
5023
dependencies = [
5024
"displaydoc",
5025
"yoke",
···
5028
5029
[[package]]
5030
name = "zerovec"
5031
+
version = "0.11.5"
5032
source = "registry+https://github.com/rust-lang/crates.io-index"
5033
+
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
5034
dependencies = [
5035
"yoke",
5036
"zerofrom",
···
5039
5040
[[package]]
5041
name = "zerovec-derive"
5042
+
version = "0.11.2"
5043
source = "registry+https://github.com/rust-lang/crates.io-index"
5044
+
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
5045
dependencies = [
5046
"proc-macro2",
5047
"quote",
5048
+
"syn 2.0.112",
5049
]
5050
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]]
5058
name = "zstd"
5059
version = "0.13.3"
5060
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5074
5075
[[package]]
5076
name = "zstd-sys"
5077
+
version = "2.0.16+zstd.1.5.7"
5078
source = "registry+https://github.com/rust-lang/crates.io-index"
5079
+
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
5080
dependencies = [
5081
"cc",
5082
"pkg-config",
+20
-11
Cargo.toml
+20
-11
Cargo.toml
···
1
[package]
2
name = "pds_gatekeeper"
3
-
version = "0.1.0"
4
edition = "2024"
5
license = "MIT"
6
7
[dependencies]
8
-
axum = { version = "0.8.4", features = ["macros", "json"] }
9
-
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "signal"] }
10
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] }
11
dotenvy = "0.15.7"
12
serde = { version = "1.0", features = ["derive"] }
13
serde_json = "1.0"
14
tracing = "0.1"
15
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
16
-
hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] }
17
tower-http = { version = "0.6", features = ["cors", "compression-zstd"] }
18
-
tower_governor = "0.8.0"
19
hex = "0.4"
20
jwt-compact = { version = "0.8.0", features = ["es256k"] }
21
scrypt = "0.11"
22
-
#Leaveing these two cause I think it is needed by the
23
-
aws-lc-rs = "1.13.0"
24
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
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"
28
axum-template = { version = "3.0.0", features = ["handlebars"] }
29
rand = "0.9.2"
30
-
anyhow = "1.0.99"
31
-
chrono = "0.4.41"
32
sha2 = "0.10"
···
1
[package]
2
name = "pds_gatekeeper"
3
+
version = "0.1.2"
4
edition = "2024"
5
license = "MIT"
6
7
[dependencies]
8
+
axum = { version = "0.8.8", features = ["macros", "json"] }
9
+
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal"] }
10
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] }
11
dotenvy = "0.15.7"
12
serde = { version = "1.0", features = ["derive"] }
13
serde_json = "1.0"
14
tracing = "0.1"
15
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
16
+
hyper-util = { version = "0.1.19", features = ["client", "client-legacy"] }
17
tower-http = { version = "0.6", features = ["cors", "compression-zstd"] }
18
+
tower_governor = { version = "0.8.0", features = ["axum", "tracing"] }
19
hex = "0.4"
20
jwt-compact = { version = "0.8.0", features = ["es256k"] }
21
scrypt = "0.11"
22
+
#Leaveing these two cause I think it is needed by the email crate for ssl
23
+
aws-lc-rs = "1.15.2"
24
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
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.4.0", features = ["rust-embed"] }
27
+
rust-embed = "8.9.0"
28
axum-template = { version = "3.0.0", features = ["handlebars"] }
29
rand = "0.9.2"
30
+
anyhow = "1.0.100"
31
+
chrono = { version = "0.4.42", features = ["default", "serde"] }
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"
+115
-23
README.md
+115
-23
README.md
···
15
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
16
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17
18
-
## Captcha on Create Account
19
20
-
Future feature?
21
22
# Setup
23
···
37
```yml
38
gatekeeper:
39
container_name: gatekeeper
40
-
image: fatfingers23/pds_gatekeeper:arm-latest
41
network_mode: host
42
restart: unless-stopped
43
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
···
49
- pds
50
```
51
52
## Caddy setup
53
54
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
55
in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
56
This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
57
58
-
```caddyfile
59
@gatekeeper {
60
-
path /xrpc/com.atproto.server.getSession
61
-
path /xrpc/com.atproto.server.updateEmail
62
-
path /xrpc/com.atproto.server.createSession
63
-
path /@atproto/oauth-provider/~api/sign-in
64
}
65
66
handle @gatekeeper {
67
-
reverse_proxy http://localhost:8080
68
-
}
69
70
-
reverse_proxy http://localhost:3000
71
```
72
73
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
74
`localhost:8081` (or w/e port you want).
75
76
-
```caddyfile
77
http://*.localhost:8082, http://localhost:8082 {
78
-
@gatekeeper {
79
-
path /xrpc/com.atproto.server.getSession
80
-
path /xrpc/com.atproto.server.updateEmail
81
-
path /xrpc/com.atproto.server.createSession
82
-
path /@atproto/oauth-provider/~api/sign-in
83
-
}
84
-
85
-
handle @gatekeeper {
86
-
reverse_proxy http://localhost:8080
87
-
}
88
89
-
reverse_proxy http://localhost:3000
90
}
91
92
```
···
113
`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1`
114
115
`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080`
···
15
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
16
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17
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
40
+
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.
48
49
# Setup
50
···
64
```yml
65
gatekeeper:
66
container_name: gatekeeper
67
+
image: fatfingers23/pds_gatekeeper:latest
68
network_mode: host
69
restart: unless-stopped
70
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
···
76
- pds
77
```
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
+
124
## Caddy setup
125
126
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
127
in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
128
This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
129
130
+
```
131
@gatekeeper {
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/*
139
}
140
141
handle @gatekeeper {
142
+
reverse_proxy http://localhost:8080
143
+
}
144
145
+
reverse_proxy http://localhost:3000
146
```
147
148
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
149
`localhost:8081` (or w/e port you want).
150
151
+
```
152
http://*.localhost:8082, http://localhost:8082 {
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
+
}
162
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
170
}
171
172
```
···
193
`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1`
194
195
`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080`
196
+
197
+
`GATEKEEPER_CREATE_ACCOUNT_PER_SECOND` - Sets how often it takes a count off the limiter. example if you hit the rate
198
+
limit of 5 and set to 60, then in 60 seconds you will be able to make one more. Or in 5 minutes be able to make 5 more.
199
+
200
+
`GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where
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
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
-21
examples/Caddyfile
+22
-21
examples/Caddyfile
···
1
{
2
-
email youremail@myemail.com
3
-
on_demand_tls {
4
-
ask http://localhost:3000/tls-check
5
-
}
6
}
7
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 /@atproto/oauth-provider/~api/sign-in
18
-
}
19
20
-
handle @gatekeeper {
21
-
#This is the address for PDS gatekeeper, default is 8080
22
-
reverse_proxy http://localhost:8080
23
-
}
24
25
-
reverse_proxy http://localhost:3000
26
-
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
27
}
28
-
29
-
···
1
{
2
+
email youremail@myemail.com
3
+
on_demand_tls {
4
+
ask http://localhost:3000/tls-check
5
+
}
6
}
7
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.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
+
}
22
23
+
handle @gatekeeper {
24
+
#This is the address for PDS gatekeeper, default is 8080
25
+
reverse_proxy http://localhost:8080
26
+
}
27
28
+
reverse_proxy http://localhost:3000
29
+
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
30
}
+1
-1
examples/compose.yml
+1
-1
examples/compose.yml
···
39
WATCHTOWER_SCHEDULE: "@midnight"
40
gatekeeper:
41
container_name: gatekeeper
42
-
image: fatfingers23/pds_gatekeeper:arm-latest
43
network_mode: host
44
restart: unless-stopped
45
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
+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
use crate::AppState;
2
use crate::helpers::TokenCheckError::InvalidToken;
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};
9
use axum_template::TemplateEngine;
10
use chrono::Utc;
11
-
use lettre::message::{MultiPart, SinglePart, header};
12
-
use lettre::{AsyncTransport, Message};
13
use rand::Rng;
14
use serde::de::DeserializeOwned;
15
use serde_json::{Map, Value};
16
use sha2::{Digest, Sha256};
17
use sqlx::SqlitePool;
18
-
use std::env;
19
use tracing::{error, log};
20
21
///Used to generate the email 2fa code
···
40
where
41
T: DeserializeOwned,
42
{
43
-
let uri = format!("{}{}", state.pds_base_url, path);
44
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
45
46
let result = state
···
333
let email_body = state
334
.template_engine
335
.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
339
let email_message = Message::builder()
340
//TODO prob get the proper type in the state
341
-
.from(state.mailer_from.parse()?)
342
.to(email.parse()?)
343
-
.subject(email_subject)
344
.multipart(
345
MultiPart::alternative() // This is composed of two parts.
346
.singlepart(
···
523
524
format!("{masked_local}@{masked_domain}")
525
}
···
1
use crate::AppState;
2
use crate::helpers::TokenCheckError::InvalidToken;
3
use anyhow::anyhow;
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
+
};
11
use axum_template::TemplateEngine;
12
use chrono::Utc;
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
+
};
23
use rand::Rng;
24
use serde::de::DeserializeOwned;
25
use serde_json::{Map, Value};
26
use sha2::{Digest, Sha256};
27
use sqlx::SqlitePool;
28
+
use std::sync::Arc;
29
use tracing::{error, log};
30
31
///Used to generate the email 2fa code
···
50
where
51
T: DeserializeOwned,
52
{
53
+
let uri = format!("{}{}", state.app_config.pds_base_url, path);
54
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
55
56
let result = state
···
343
let email_body = state
344
.template_engine
345
.render("two_factor_code.hbs", email_data)?;
346
347
let email_message = Message::builder()
348
//TODO prob get the proper type in the state
349
+
.from(state.app_config.mailer_from.parse()?)
350
.to(email.parse()?)
351
+
.subject(&state.app_config.email_subject)
352
.multipart(
353
MultiPart::alternative() // This is composed of two parts.
354
.singlepart(
···
531
532
format!("{masked_local}@{masked_domain}")
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
+
}
+191
-49
src/main.rs
+191
-49
src/main.rs
···
1
#![warn(clippy::unwrap_used)]
2
use crate::oauth_provider::sign_in;
3
-
use crate::xrpc::com_atproto_server::{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};
10
use axum_template::engine::Engine;
11
use handlebars::Handlebars;
12
-
use hyper_util::client::legacy::connect::HttpConnector;
13
-
use hyper_util::rt::TokioExecutor;
14
use lettre::{AsyncSmtpTransport, Tokio1Executor};
15
use rust_embed::RustEmbed;
16
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
17
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
18
use std::path::Path;
19
use std::time::Duration;
20
use std::{env, net::SocketAddr};
21
-
use tower_governor::GovernorLayer;
22
-
use tower_governor::governor::{GovernorConfig, GovernorConfigBuilder};
23
-
use tower_governor::key_extractor::PeerIpKeyExtractor;
24
-
use tower_http::compression::CompressionLayer;
25
-
use tower_http::cors::{Any, CorsLayer};
26
use tracing::log;
27
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
28
29
pub mod helpers;
30
mod middleware;
31
mod oauth_provider;
···
38
#[include = "*.hbs"]
39
struct EmailTemplates;
40
41
#[derive(Clone)]
42
pub struct AppState {
43
account_pool: SqlitePool,
44
pds_gatekeeper_pool: SqlitePool,
45
reverse_proxy_client: HyperUtilClient,
46
-
pds_base_url: String,
47
mailer: AsyncSmtpTransport<Tokio1Executor>,
48
-
mailer_from: String,
49
template_engine: Engine<Handlebars<'static>>,
50
}
51
52
async fn root_handler() -> impl axum::response::IntoResponse {
···
92
let pds_env_location =
93
env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string());
94
95
-
dotenvy::from_path(Path::new(&pds_env_location))?;
96
-
let pds_root = env::var("PDS_DATA_DIRECTORY")?;
97
let account_db_url = format!("{pds_root}/account.sqlite");
98
99
let account_options = SqliteConnectOptions::new()
···
130
//Emailer set up
131
let smtp_url =
132
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
133
-
let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS")
134
-
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
135
136
let mailer: AsyncSmtpTransport<Tokio1Executor> =
137
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
···
148
let _ = hbs.register_embed_templates::<EmailTemplates>();
149
}
150
151
-
let pds_base_url =
152
-
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
153
154
let state = AppState {
155
account_pool,
156
pds_gatekeeper_pool,
157
reverse_proxy_client: client,
158
-
pds_base_url,
159
mailer,
160
-
mailer_from: sent_from,
161
template_engine: Engine::from(hbs),
162
};
163
164
// Rate limiting
165
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
166
-
let create_session_governor_conf = GovernorConfigBuilder::default()
167
.per_second(60)
168
.burst_size(5)
169
.finish()
170
.expect("failed to create governor config for create session. this should not happen and is a bug");
171
···
173
let sign_in_governor_conf = GovernorConfigBuilder::default()
174
.per_second(60)
175
.burst_size(5)
176
.finish()
177
.expect(
178
"failed to create governor config for sign in. this should not happen and is a bug",
···
182
env::var("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND").ok();
183
let create_account_limiter_burst: Option<String> =
184
env::var("GATEKEEPER_CREATE_ACCOUNT_BURST").ok();
185
-
let mut create_account_governor_conf = None;
186
187
-
if create_account_governor_conf.is_some() && create_account_limiter_time.is_some() {
188
let time = create_account_limiter_time
189
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND not set")
190
.parse::<u64>()
191
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND must be a valid integer");
192
let burst = create_account_limiter_burst
193
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST not set")
194
.parse::<u32>()
195
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST must be a valid integer");
196
-
197
-
create_account_governor_conf = Some(
198
-
GovernorConfigBuilder::default()
199
-
.per_second(time)
200
-
.burst_size(burst)
201
-
.finish()
202
-
.expect("failed to create governor config for create account. this should not happen and is a bug"),
203
-
)
204
}
205
206
-
let create_session_governor_limiter = create_session_governor_conf.limiter().clone();
207
let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone();
208
-
let create_account_governor_limiter = match create_account_governor_conf {
209
-
None => None,
210
-
Some(conf) => Some(conf.limiter().clone()),
211
-
};
212
213
let interval = Duration::from_secs(60);
214
// a separate background task to clean up
215
std::thread::spawn(move || {
216
loop {
217
std::thread::sleep(interval);
218
-
create_session_governor_limiter.retain_recent();
219
sign_in_governor_limiter.retain_recent();
220
-
if let Some(ref limiter) = create_account_governor_limiter {
221
-
limiter.retain_recent();
222
-
}
223
}
224
});
225
···
228
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
229
.allow_headers(Any);
230
231
-
let app = Router::new()
232
.route("/", get(root_handler))
233
.route("/xrpc/com.atproto.server.getSession", get(get_session))
234
.route(
235
"/xrpc/com.atproto.server.updateEmail",
236
post(update_email).layer(ax_middleware::from_fn(middleware::extract_did)),
237
)
238
.route(
239
"/@atproto/oauth-provider/~api/sign-in",
240
-
post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)),
241
)
242
.route(
243
"/xrpc/com.atproto.server.createSession",
244
-
post(create_session.layer(GovernorLayer::new(create_session_governor_conf))),
245
)
246
-
.route("/xrpc/com.atproto.server.createAccount")
247
.layer(CompressionLayer::new())
248
.layer(cors)
249
.with_state(state);
250
251
-
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
252
let port: u16 = env::var("GATEKEEPER_PORT")
253
.ok()
254
.and_then(|s| s.parse().ok())
···
1
#![warn(clippy::unwrap_used)]
2
+
use crate::gate::{get_gate, post_gate};
3
use crate::oauth_provider::sign_in;
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
+
};
16
use axum_template::engine::Engine;
17
use handlebars::Handlebars;
18
+
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
19
+
use jacquard_common::types::did::Did;
20
+
use jacquard_identity::{PublicResolver, resolver::PlcSource};
21
use lettre::{AsyncSmtpTransport, Tokio1Executor};
22
+
use rand::Rng;
23
use rust_embed::RustEmbed;
24
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
25
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
26
use std::path::Path;
27
+
use std::sync::Arc;
28
use std::time::Duration;
29
use std::{env, net::SocketAddr};
30
+
use tower_governor::{
31
+
GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
32
+
};
33
+
use tower_http::{
34
+
compression::CompressionLayer,
35
+
cors::{Any, CorsLayer},
36
+
};
37
use tracing::log;
38
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
39
40
+
mod auth;
41
+
mod gate;
42
pub mod helpers;
43
mod middleware;
44
mod oauth_provider;
···
51
#[include = "*.hbs"]
52
struct EmailTemplates;
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
+
149
#[derive(Clone)]
150
pub struct AppState {
151
account_pool: SqlitePool,
152
pds_gatekeeper_pool: SqlitePool,
153
reverse_proxy_client: HyperUtilClient,
154
mailer: AsyncSmtpTransport<Tokio1Executor>,
155
template_engine: Engine<Handlebars<'static>>,
156
+
resolver: Arc<PublicResolver>,
157
+
handle_cache: auth::HandleCache,
158
+
app_config: AppConfig,
159
}
160
161
async fn root_handler() -> impl axum::response::IntoResponse {
···
201
let pds_env_location =
202
env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string());
203
204
+
let result_of_finding_pds_env = dotenvy::from_path(Path::new(&pds_env_location));
205
+
if let Err(e) = result_of_finding_pds_env {
206
+
log::error!(
207
+
"Error loading pds.env file (ignore if you loaded your variables in the environment somehow else): {e}"
208
+
);
209
+
}
210
+
211
+
let pds_root =
212
+
env::var("PDS_DATA_DIRECTORY").expect("PDS_DATA_DIRECTORY is not set in your pds.env file");
213
let account_db_url = format!("{pds_root}/account.sqlite");
214
215
let account_options = SqliteConnectOptions::new()
···
246
//Emailer set up
247
let smtp_url =
248
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
249
250
let mailer: AsyncSmtpTransport<Tokio1Executor> =
251
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
···
262
let _ = hbs.register_embed_templates::<EmailTemplates>();
263
}
264
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());
275
276
let state = AppState {
277
account_pool,
278
pds_gatekeeper_pool,
279
reverse_proxy_client: client,
280
mailer,
281
template_engine: Engine::from(hbs),
282
+
resolver: Arc::new(resolver),
283
+
handle_cache: auth::HandleCache::new(),
284
+
app_config: AppConfig::new(),
285
};
286
287
// Rate limiting
288
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
289
+
let captcha_governor_conf = GovernorConfigBuilder::default()
290
.per_second(60)
291
.burst_size(5)
292
+
.key_extractor(SmartIpKeyExtractor)
293
.finish()
294
.expect("failed to create governor config for create session. this should not happen and is a bug");
295
···
297
let sign_in_governor_conf = GovernorConfigBuilder::default()
298
.per_second(60)
299
.burst_size(5)
300
+
.key_extractor(SmartIpKeyExtractor)
301
.finish()
302
.expect(
303
"failed to create governor config for sign in. this should not happen and is a bug",
···
307
env::var("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND").ok();
308
let create_account_limiter_burst: Option<String> =
309
env::var("GATEKEEPER_CREATE_ACCOUNT_BURST").ok();
310
311
+
//Default should be 608 requests per 5 minutes, PDS is 300 per 500 so will never hit it ideally
312
+
let mut create_account_governor_conf = GovernorConfigBuilder::default();
313
+
if create_account_limiter_time.is_some() {
314
let time = create_account_limiter_time
315
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND not set")
316
.parse::<u64>()
317
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND must be a valid integer");
318
+
create_account_governor_conf.per_second(time);
319
+
}
320
+
321
+
if create_account_limiter_burst.is_some() {
322
let burst = create_account_limiter_burst
323
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST not set")
324
.parse::<u32>()
325
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST must be a valid integer");
326
+
create_account_governor_conf.burst_size(burst);
327
}
328
329
+
let create_account_governor_conf = create_account_governor_conf
330
+
.key_extractor(SmartIpKeyExtractor)
331
+
.finish().expect(
332
+
"failed to create governor config for create account. this should not happen and is a bug",
333
+
);
334
+
335
+
let captcha_governor_limiter = captcha_governor_conf.limiter().clone();
336
let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone();
337
+
let create_account_governor_limiter = create_account_governor_conf.limiter().clone();
338
+
339
+
let sign_in_governor_layer = GovernorLayer::new(sign_in_governor_conf);
340
341
let interval = Duration::from_secs(60);
342
// a separate background task to clean up
343
std::thread::spawn(move || {
344
loop {
345
std::thread::sleep(interval);
346
+
captcha_governor_limiter.retain_recent();
347
sign_in_governor_limiter.retain_recent();
348
+
create_account_governor_limiter.retain_recent();
349
}
350
});
351
···
354
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
355
.allow_headers(Any);
356
357
+
let mut app = Router::new()
358
.route("/", get(root_handler))
359
.route("/xrpc/com.atproto.server.getSession", get(get_session))
360
.route(
361
+
"/xrpc/com.atproto.server.describeServer",
362
+
get(describe_server),
363
+
)
364
+
.route(
365
"/xrpc/com.atproto.server.updateEmail",
366
post(update_email).layer(ax_middleware::from_fn(middleware::extract_did)),
367
)
368
.route(
369
"/@atproto/oauth-provider/~api/sign-in",
370
+
post(sign_in).layer(sign_in_governor_layer.clone()),
371
)
372
.route(
373
"/xrpc/com.atproto.server.createSession",
374
+
post(create_session.layer(sign_in_governor_layer)),
375
)
376
+
.route(
377
+
"/xrpc/com.atproto.server.createAccount",
378
+
post(create_account).layer(GovernorLayer::new(create_account_governor_conf)),
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
389
.layer(CompressionLayer::new())
390
.layer(cors)
391
.with_state(state);
392
393
+
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
394
let port: u16 = env::var("GATEKEEPER_PORT")
395
.ok()
396
.and_then(|s| s.parse().ok())
-2
src/middleware.rs
-2
src/middleware.rs
···
1
use crate::helpers::json_error_response;
2
use axum::extract::Request;
3
-
use axum::http::header::AUTHORIZATION;
4
use axum::http::{HeaderMap, StatusCode};
5
use axum::middleware::Next;
6
use axum::response::IntoResponse;
···
73
.expect("Error creating an error response");
74
}
75
let token = token.expect("Already checked for error,");
76
-
// Not going to worry about expiration since it still goes to the PDS
77
req.extensions_mut()
78
.insert(Did(Some(token.claims().custom.sub.clone())));
79
}
···
1
use crate::helpers::json_error_response;
2
use axum::extract::Request;
3
use axum::http::{HeaderMap, StatusCode};
4
use axum::middleware::Next;
5
use axum::response::IntoResponse;
···
72
.expect("Error creating an error response");
73
}
74
let token = token.expect("Already checked for error,");
75
req.extensions_mut()
76
.insert(Did(Some(token.claims().custom.sub.clone())));
77
}
+3
-2
src/oauth_provider.rs
+3
-2
src/oauth_provider.rs
···
13
pub struct SignInRequest {
14
pub username: String,
15
pub password: String,
16
-
pub remember: bool,
17
pub locale: String,
18
#[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")]
19
pub email_otp: Option<String>,
···
56
//No 2FA or already passed
57
let uri = format!(
58
"{}{}",
59
-
state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
);
61
62
let mut req = axum::http::Request::post(uri);
···
13
pub struct SignInRequest {
14
pub username: String,
15
pub password: String,
16
+
#[serde(skip_serializing_if = "Option::is_none")]
17
+
pub remember: Option<bool>,
18
pub locale: String,
19
#[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")]
20
pub email_otp: Option<String>,
···
57
//No 2FA or already passed
58
let uri = format!(
59
"{}{}",
60
+
state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
61
);
62
63
let mut req = axum::http::Request::post(uri);
+350
-13
src/xrpc/com_atproto_server.rs
+350
-13
src/xrpc/com_atproto_server.rs
···
1
use crate::AppState;
2
use crate::helpers::{
3
-
AuthResult, ProxiedResult, TokenCheckError, json_error_response, preauth_check, proxy_get_json,
4
};
5
use crate::middleware::Did;
6
-
use axum::body::Body;
7
use axum::extract::State;
8
-
use axum::http::{HeaderMap, StatusCode};
9
use axum::response::{IntoResponse, Response};
10
use axum::{Extension, Json, debug_handler, extract, extract::Request};
11
use serde::{Deserialize, Serialize};
12
use serde_json;
13
use tracing::log;
···
61
allow_takendown: Option<bool>,
62
}
63
64
pub async fn create_session(
65
State(state): State<AppState>,
66
headers: HeaderMap,
···
90
//No 2FA or already passed
91
let uri = format!(
92
"{}{}",
93
-
state.pds_base_url, "/xrpc/com.atproto.server.createSession"
94
);
95
96
let mut req = axum::http::Request::post(uri);
···
155
// Email update asked for
156
if email_auth_update {
157
let email = payload.email.clone();
158
-
let email_confirmed = sqlx::query_as::<_, (String,)>(
159
"SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?",
160
)
161
.bind(&email)
162
.fetch_optional(&state.account_pool)
163
.await
164
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
165
166
//Since the email is already confirmed we can enable 2fa
167
return match email_confirmed {
···
184
if !email_auth_update && !email_auth_not_set {
185
//User wants auth turned off and has a token
186
if let Some(token) = &payload.token {
187
-
let token_found = sqlx::query_as::<_, (String,)>(
188
"SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'",
189
)
190
.bind(token)
191
.bind(&did.0)
192
.fetch_optional(&state.account_pool)
193
-
.await
194
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
195
196
return if token_found.is_some() {
197
-
let _ = sqlx::query(
198
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0",
199
)
200
.bind(&did.0)
201
.execute(&state.pds_gatekeeper_pool)
202
-
.await
203
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
204
205
Ok(StatusCode::OK.into_response())
206
} else {
···
212
// Updating the actual email address by sending it on to the PDS
213
let uri = format!(
214
"{}{}",
215
-
state.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
216
);
217
let mut req = axum::http::Request::post(uri);
218
if let Some(req_headers) = req.headers_mut() {
···
264
ProxiedResult::Passthrough(resp) => Ok(resp),
265
}
266
}
···
1
use crate::AppState;
2
use crate::helpers::{
3
+
AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response,
4
+
preauth_check, proxy_get_json, verify_gate_token, verify_service_auth,
5
};
6
use crate::middleware::Did;
7
+
use axum::body::{Body, to_bytes};
8
use axum::extract::State;
9
+
use axum::http::{HeaderMap, StatusCode, header};
10
use axum::response::{IntoResponse, Response};
11
use axum::{Extension, Json, debug_handler, extract, extract::Request};
12
+
use chrono::{Duration, Utc};
13
+
use jacquard_common::types::did::Did as JacquardDid;
14
use serde::{Deserialize, Serialize};
15
use serde_json;
16
use tracing::log;
···
64
allow_takendown: Option<bool>,
65
}
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
+
118
pub async fn create_session(
119
State(state): State<AppState>,
120
headers: HeaderMap,
···
144
//No 2FA or already passed
145
let uri = format!(
146
"{}{}",
147
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession"
148
);
149
150
let mut req = axum::http::Request::post(uri);
···
209
// Email update asked for
210
if email_auth_update {
211
let email = payload.email.clone();
212
+
let email_confirmed = match sqlx::query_as::<_, (String,)>(
213
"SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?",
214
)
215
.bind(&email)
216
.fetch_optional(&state.account_pool)
217
.await
218
+
{
219
+
Ok(row) => row,
220
+
Err(err) => {
221
+
log::error!("Error checking if email is confirmed: {err}");
222
+
return Err(StatusCode::BAD_REQUEST);
223
+
}
224
+
};
225
226
//Since the email is already confirmed we can enable 2fa
227
return match email_confirmed {
···
244
if !email_auth_update && !email_auth_not_set {
245
//User wants auth turned off and has a token
246
if let Some(token) = &payload.token {
247
+
let token_found = match sqlx::query_as::<_, (String,)>(
248
"SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'",
249
)
250
.bind(token)
251
.bind(&did.0)
252
.fetch_optional(&state.account_pool)
253
+
.await{
254
+
Ok(token) => token,
255
+
Err(err) => {
256
+
log::error!("Error checking if token is valid: {err}");
257
+
return Err(StatusCode::BAD_REQUEST);
258
+
}
259
+
};
260
261
return if token_found.is_some() {
262
+
//TODO I think there may be a bug here and need to do some retry logic
263
+
// First try was erroring, seconds was allowing
264
+
match sqlx::query(
265
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0",
266
)
267
.bind(&did.0)
268
.execute(&state.pds_gatekeeper_pool)
269
+
.await {
270
+
Ok(_) => {}
271
+
Err(err) => {
272
+
log::error!("Error updating email auth: {err}");
273
+
return Err(StatusCode::BAD_REQUEST);
274
+
}
275
+
}
276
277
Ok(StatusCode::OK.into_response())
278
} else {
···
284
// Updating the actual email address by sending it on to the PDS
285
let uri = format!(
286
"{}{}",
287
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
288
);
289
let mut req = axum::http::Request::post(uri);
290
if let Some(req_headers) = req.headers_mut() {
···
336
ProxiedResult::Passthrough(resp) => Ok(resp),
337
}
338
}
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
+
425
+
pub async fn create_account(
426
+
State(state): State<AppState>,
427
+
req: Request,
428
+
) -> Result<Response<Body>, StatusCode> {
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
+
};
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
581
+
let uri = format!(
582
+
"{}{}",
583
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount"
584
+
);
585
+
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)?;
594
+
595
+
let proxied = state
596
+
.reverse_proxy_client
597
+
.request(new_req)
598
+
.await
599
+
.map_err(|_| StatusCode::BAD_REQUEST)?
600
+
.into_response();
601
+
602
+
Ok(proxied)
603
+
}