+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.
+1998
-498
Cargo.lock
+1998
-498
Cargo.lock
···
3
3
version = 4
4
4
5
5
[[package]]
6
-
name = "addr2line"
7
-
version = "0.24.2"
6
+
name = "abnf"
7
+
version = "0.13.0"
8
8
source = "registry+https://github.com/rust-lang/crates.io-index"
9
-
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
9
+
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
10
dependencies = [
11
-
"gimli",
11
+
"abnf-core",
12
+
"nom 7.1.3",
13
+
]
14
+
15
+
[[package]]
16
+
name = "abnf-core"
17
+
version = "0.5.0"
18
+
source = "registry+https://github.com/rust-lang/crates.io-index"
19
+
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
20
+
dependencies = [
21
+
"nom 7.1.3",
12
22
]
13
23
14
24
[[package]]
···
31
41
32
42
[[package]]
33
43
name = "aho-corasick"
34
-
version = "1.1.3"
44
+
version = "1.1.4"
35
45
source = "registry+https://github.com/rust-lang/crates.io-index"
36
-
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
46
+
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
37
47
dependencies = [
38
48
"memchr",
39
49
]
40
50
41
51
[[package]]
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]]
42
58
name = "allocator-api2"
43
59
version = "0.2.21"
44
60
source = "registry+https://github.com/rust-lang/crates.io-index"
45
61
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
46
62
47
63
[[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
64
name = "android_system_properties"
55
65
version = "0.1.5"
56
66
source = "registry+https://github.com/rust-lang/crates.io-index"
···
61
71
62
72
[[package]]
63
73
name = "anyhow"
64
-
version = "1.0.99"
74
+
version = "1.0.100"
65
75
source = "registry+https://github.com/rust-lang/crates.io-index"
66
-
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
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
+
]
67
86
68
87
[[package]]
69
88
name = "async-compression"
70
-
version = "0.4.27"
89
+
version = "0.4.36"
71
90
source = "registry+https://github.com/rust-lang/crates.io-index"
72
-
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
91
+
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
73
92
dependencies = [
93
+
"compression-codecs",
94
+
"compression-core",
74
95
"futures-core",
75
-
"memchr",
76
96
"pin-project-lite",
77
97
"tokio",
78
-
"zstd",
79
-
"zstd-safe",
80
98
]
81
99
82
100
[[package]]
···
87
105
dependencies = [
88
106
"proc-macro2",
89
107
"quote",
90
-
"syn",
108
+
"syn 2.0.112",
91
109
]
92
110
93
111
[[package]]
···
100
118
]
101
119
102
120
[[package]]
121
+
name = "atomic-polyfill"
122
+
version = "1.0.3"
123
+
source = "registry+https://github.com/rust-lang/crates.io-index"
124
+
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
125
+
dependencies = [
126
+
"critical-section",
127
+
]
128
+
129
+
[[package]]
103
130
name = "atomic-waker"
104
131
version = "1.1.2"
105
132
source = "registry+https://github.com/rust-lang/crates.io-index"
···
112
139
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
113
140
114
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",
149
+
"zeroize",
150
+
]
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",
161
+
"fs_extra",
162
+
]
163
+
164
+
[[package]]
115
165
name = "axum"
116
-
version = "0.8.4"
166
+
version = "0.8.8"
117
167
source = "registry+https://github.com/rust-lang/crates.io-index"
118
-
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
168
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
119
169
dependencies = [
120
170
"axum-core",
121
171
"axum-macros",
···
133
183
"mime",
134
184
"percent-encoding",
135
185
"pin-project-lite",
136
-
"rustversion",
137
-
"serde",
186
+
"serde_core",
138
187
"serde_json",
139
188
"serde_path_to_error",
140
189
"serde_urlencoded",
···
148
197
149
198
[[package]]
150
199
name = "axum-core"
151
-
version = "0.5.2"
200
+
version = "0.5.6"
152
201
source = "registry+https://github.com/rust-lang/crates.io-index"
153
-
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
202
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
154
203
dependencies = [
155
204
"bytes",
156
205
"futures-core",
···
159
208
"http-body-util",
160
209
"mime",
161
210
"pin-project-lite",
162
-
"rustversion",
163
211
"sync_wrapper",
164
212
"tower-layer",
165
213
"tower-service",
···
174
222
dependencies = [
175
223
"proc-macro2",
176
224
"quote",
177
-
"syn",
225
+
"syn 2.0.112",
178
226
]
179
227
180
228
[[package]]
···
186
234
"axum",
187
235
"handlebars",
188
236
"serde",
189
-
"thiserror 2.0.14",
237
+
"thiserror 2.0.17",
190
238
]
191
239
192
240
[[package]]
193
-
name = "backtrace"
194
-
version = "0.3.75"
241
+
name = "base-x"
242
+
version = "0.2.11"
243
+
source = "registry+https://github.com/rust-lang/crates.io-index"
244
+
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
245
+
246
+
[[package]]
247
+
name = "base16ct"
248
+
version = "0.2.0"
195
249
source = "registry+https://github.com/rust-lang/crates.io-index"
196
-
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
250
+
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
251
+
252
+
[[package]]
253
+
name = "base256emoji"
254
+
version = "1.0.2"
255
+
source = "registry+https://github.com/rust-lang/crates.io-index"
256
+
checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c"
197
257
dependencies = [
198
-
"addr2line",
199
-
"cfg-if",
200
-
"libc",
201
-
"miniz_oxide",
202
-
"object",
203
-
"rustc-demangle",
204
-
"windows-targets 0.52.6",
258
+
"const-str",
259
+
"match-lookup",
205
260
]
206
261
207
262
[[package]]
···
212
267
213
268
[[package]]
214
269
name = "base64ct"
215
-
version = "1.8.0"
270
+
version = "1.8.1"
216
271
source = "registry+https://github.com/rust-lang/crates.io-index"
217
-
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
272
+
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
218
273
219
274
[[package]]
220
275
name = "bitflags"
221
-
version = "2.9.1"
276
+
version = "2.10.0"
222
277
source = "registry+https://github.com/rust-lang/crates.io-index"
223
-
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
278
+
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
224
279
dependencies = [
225
-
"serde",
280
+
"serde_core",
226
281
]
227
282
228
283
[[package]]
···
235
290
]
236
291
237
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]]
238
327
name = "bstr"
239
-
version = "1.12.0"
328
+
version = "1.12.1"
240
329
source = "registry+https://github.com/rust-lang/crates.io-index"
241
-
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
330
+
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
242
331
dependencies = [
243
332
"memchr",
244
333
"serde",
245
334
]
246
335
247
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]]
248
361
name = "bumpalo"
249
-
version = "3.19.0"
362
+
version = "3.19.1"
250
363
source = "registry+https://github.com/rust-lang/crates.io-index"
251
-
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
364
+
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
252
365
253
366
[[package]]
254
367
name = "byteorder"
···
258
371
259
372
[[package]]
260
373
name = "bytes"
261
-
version = "1.10.1"
374
+
version = "1.11.0"
375
+
source = "registry+https://github.com/rust-lang/crates.io-index"
376
+
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
377
+
dependencies = [
378
+
"serde",
379
+
]
380
+
381
+
[[package]]
382
+
name = "cbor4ii"
383
+
version = "0.2.14"
262
384
source = "registry+https://github.com/rust-lang/crates.io-index"
263
-
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
385
+
checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4"
386
+
dependencies = [
387
+
"serde",
388
+
]
264
389
265
390
[[package]]
266
391
name = "cc"
267
-
version = "1.2.32"
392
+
version = "1.2.51"
268
393
source = "registry+https://github.com/rust-lang/crates.io-index"
269
-
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
394
+
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
270
395
dependencies = [
396
+
"find-msvc-tools",
271
397
"jobserver",
272
398
"libc",
273
399
"shlex",
274
400
]
275
401
276
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]]
277
412
name = "cfg-if"
278
-
version = "1.0.1"
413
+
version = "1.0.4"
279
414
source = "registry+https://github.com/rust-lang/crates.io-index"
280
-
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
415
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
416
+
417
+
[[package]]
418
+
name = "cfg_aliases"
419
+
version = "0.2.1"
420
+
source = "registry+https://github.com/rust-lang/crates.io-index"
421
+
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
281
422
282
423
[[package]]
283
424
name = "chrono"
284
-
version = "0.4.41"
425
+
version = "0.4.42"
285
426
source = "registry+https://github.com/rust-lang/crates.io-index"
286
-
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
427
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
287
428
dependencies = [
288
-
"android-tzdata",
289
429
"iana-time-zone",
290
430
"js-sys",
291
431
"num-traits",
432
+
"serde",
292
433
"wasm-bindgen",
293
434
"windows-link",
294
435
]
···
331
472
]
332
473
333
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]]
334
489
name = "cipher"
335
490
version = "0.4.4"
336
491
source = "registry+https://github.com/rust-lang/crates.io-index"
···
341
496
]
342
497
343
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]]
344
536
name = "concurrent-queue"
345
537
version = "2.5.0"
346
538
source = "registry+https://github.com/rust-lang/crates.io-index"
···
356
548
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
357
549
358
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]]
359
567
name = "core-foundation"
360
568
version = "0.9.4"
361
569
source = "registry+https://github.com/rust-lang/crates.io-index"
···
372
580
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
373
581
374
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]]
375
592
name = "cpufeatures"
376
593
version = "0.2.17"
377
594
source = "registry+https://github.com/rust-lang/crates.io-index"
···
382
599
383
600
[[package]]
384
601
name = "crc"
385
-
version = "3.3.0"
602
+
version = "3.4.0"
386
603
source = "registry+https://github.com/rust-lang/crates.io-index"
387
-
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
604
+
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
388
605
dependencies = [
389
606
"crc-catalog",
390
607
]
···
396
613
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
397
614
398
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]]
399
631
name = "crossbeam-queue"
400
632
version = "0.3.12"
401
633
source = "registry+https://github.com/rust-lang/crates.io-index"
···
417
649
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
418
650
419
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]]
420
664
name = "crypto-common"
421
-
version = "0.1.6"
665
+
version = "0.1.7"
422
666
source = "registry+https://github.com/rust-lang/crates.io-index"
423
-
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
667
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
424
668
dependencies = [
425
669
"generic-array",
426
670
"typenum",
···
432
676
source = "registry+https://github.com/rust-lang/crates.io-index"
433
677
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
434
678
dependencies = [
435
-
"darling_core",
436
-
"darling_macro",
679
+
"darling_core 0.20.11",
680
+
"darling_macro 0.20.11",
681
+
]
682
+
683
+
[[package]]
684
+
name = "darling"
685
+
version = "0.21.3"
686
+
source = "registry+https://github.com/rust-lang/crates.io-index"
687
+
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
688
+
dependencies = [
689
+
"darling_core 0.21.3",
690
+
"darling_macro 0.21.3",
437
691
]
438
692
439
693
[[package]]
···
447
701
"proc-macro2",
448
702
"quote",
449
703
"strsim",
450
-
"syn",
704
+
"syn 2.0.112",
705
+
]
706
+
707
+
[[package]]
708
+
name = "darling_core"
709
+
version = "0.21.3"
710
+
source = "registry+https://github.com/rust-lang/crates.io-index"
711
+
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
712
+
dependencies = [
713
+
"fnv",
714
+
"ident_case",
715
+
"proc-macro2",
716
+
"quote",
717
+
"strsim",
718
+
"syn 2.0.112",
451
719
]
452
720
453
721
[[package]]
···
456
724
source = "registry+https://github.com/rust-lang/crates.io-index"
457
725
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
458
726
dependencies = [
459
-
"darling_core",
727
+
"darling_core 0.20.11",
460
728
"quote",
461
-
"syn",
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",
462
741
]
463
742
464
743
[[package]]
···
476
755
]
477
756
478
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]]
479
784
name = "der"
480
785
version = "0.7.10"
481
786
source = "registry+https://github.com/rust-lang/crates.io-index"
···
487
792
]
488
793
489
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]]
490
805
name = "derive_builder"
491
806
version = "0.20.2"
492
807
source = "registry+https://github.com/rust-lang/crates.io-index"
···
501
816
source = "registry+https://github.com/rust-lang/crates.io-index"
502
817
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
503
818
dependencies = [
504
-
"darling",
819
+
"darling 0.20.11",
505
820
"proc-macro2",
506
821
"quote",
507
-
"syn",
822
+
"syn 2.0.112",
508
823
]
509
824
510
825
[[package]]
···
514
829
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
515
830
dependencies = [
516
831
"derive_builder_core",
517
-
"syn",
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",
518
842
]
519
843
520
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]]
521
863
name = "digest"
522
864
version = "0.10.7"
523
865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
537
879
dependencies = [
538
880
"proc-macro2",
539
881
"quote",
540
-
"syn",
882
+
"syn 2.0.112",
541
883
]
542
884
543
885
[[package]]
···
547
889
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
548
890
549
891
[[package]]
892
+
name = "dunce"
893
+
version = "1.0.5"
894
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
550
918
name = "either"
551
919
version = "1.15.0"
552
920
source = "registry+https://github.com/rust-lang/crates.io-index"
···
556
924
]
557
925
558
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]]
559
947
name = "email-encoding"
560
948
version = "0.4.1"
561
949
source = "registry+https://github.com/rust-lang/crates.io-index"
···
572
960
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
573
961
574
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]]
575
984
name = "equivalent"
576
985
version = "1.0.2"
577
986
source = "registry+https://github.com/rust-lang/crates.io-index"
···
579
988
580
989
[[package]]
581
990
name = "errno"
582
-
version = "0.3.13"
991
+
version = "0.3.14"
583
992
source = "registry+https://github.com/rust-lang/crates.io-index"
584
-
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
993
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
585
994
dependencies = [
586
995
"libc",
587
996
"windows-sys 0.59.0",
···
616
1025
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
617
1026
618
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]]
619
1054
name = "flume"
620
1055
version = "0.11.1"
621
1056
source = "registry+https://github.com/rust-lang/crates.io-index"
···
623
1058
dependencies = [
624
1059
"futures-core",
625
1060
"futures-sink",
626
-
"spin",
1061
+
"spin 0.9.8",
627
1062
]
628
1063
629
1064
[[package]]
···
639
1074
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
640
1075
641
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]]
642
1083
name = "foreign-types"
643
1084
version = "0.3.2"
644
1085
source = "registry+https://github.com/rust-lang/crates.io-index"
···
655
1096
656
1097
[[package]]
657
1098
name = "form_urlencoded"
658
-
version = "1.2.1"
1099
+
version = "1.2.2"
659
1100
source = "registry+https://github.com/rust-lang/crates.io-index"
660
-
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
1101
+
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
661
1102
dependencies = [
662
1103
"percent-encoding",
663
1104
]
···
673
1114
]
674
1115
675
1116
[[package]]
1117
+
name = "fs_extra"
1118
+
version = "1.3.0"
1119
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
676
1136
name = "futures-channel"
677
1137
version = "0.3.31"
678
1138
source = "registry+https://github.com/rust-lang/crates.io-index"
···
717
1177
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
718
1178
719
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]]
720
1204
name = "futures-sink"
721
1205
version = "0.3.31"
722
1206
source = "registry+https://github.com/rust-lang/crates.io-index"
···
742
1226
dependencies = [
743
1227
"futures-core",
744
1228
"futures-io",
1229
+
"futures-macro",
745
1230
"futures-sink",
746
1231
"futures-task",
747
1232
"memchr",
···
751
1236
]
752
1237
753
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]]
754
1254
name = "generic-array"
755
1255
version = "0.14.7"
756
1256
source = "registry+https://github.com/rust-lang/crates.io-index"
···
758
1258
dependencies = [
759
1259
"typenum",
760
1260
"version_check",
1261
+
"zeroize",
761
1262
]
762
1263
763
1264
[[package]]
···
767
1268
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
768
1269
dependencies = [
769
1270
"cfg-if",
1271
+
"js-sys",
770
1272
"libc",
771
-
"wasi 0.11.1+wasi-snapshot-preview1",
1273
+
"wasi",
1274
+
"wasm-bindgen",
772
1275
]
773
1276
774
1277
[[package]]
775
1278
name = "getrandom"
776
-
version = "0.3.3"
1279
+
version = "0.3.4"
777
1280
source = "registry+https://github.com/rust-lang/crates.io-index"
778
-
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1281
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
779
1282
dependencies = [
780
1283
"cfg-if",
781
1284
"js-sys",
782
1285
"libc",
783
1286
"r-efi",
784
-
"wasi 0.14.2+wasi-0.2.4",
1287
+
"wasip2",
785
1288
"wasm-bindgen",
786
1289
]
787
1290
788
1291
[[package]]
789
-
name = "gimli"
790
-
version = "0.31.1"
791
-
source = "registry+https://github.com/rust-lang/crates.io-index"
792
-
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
793
-
794
-
[[package]]
795
1292
name = "globset"
796
-
version = "0.4.16"
1293
+
version = "0.4.18"
797
1294
source = "registry+https://github.com/rust-lang/crates.io-index"
798
-
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
1295
+
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
799
1296
dependencies = [
800
1297
"aho-corasick",
801
1298
"bstr",
802
1299
"log",
803
-
"regex-automata 0.4.9",
804
-
"regex-syntax 0.8.5",
1300
+
"regex-automata",
1301
+
"regex-syntax",
805
1302
]
806
1303
807
1304
[[package]]
808
1305
name = "governor"
809
-
version = "0.10.1"
1306
+
version = "0.10.4"
810
1307
source = "registry+https://github.com/rust-lang/crates.io-index"
811
-
checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19"
1308
+
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
812
1309
dependencies = [
813
1310
"cfg-if",
814
1311
"dashmap",
815
1312
"futures-sink",
816
1313
"futures-timer",
817
1314
"futures-util",
818
-
"getrandom 0.3.3",
819
-
"hashbrown 0.15.5",
1315
+
"getrandom 0.3.4",
1316
+
"hashbrown 0.16.1",
820
1317
"nonzero_ext",
821
1318
"parking_lot",
822
1319
"portable-atomic",
···
828
1325
]
829
1326
830
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]]
831
1339
name = "h2"
832
1340
version = "0.4.12"
833
1341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
839
1347
"futures-core",
840
1348
"futures-sink",
841
1349
"http",
842
-
"indexmap",
1350
+
"indexmap 2.12.1",
843
1351
"slab",
844
1352
"tokio",
845
1353
"tokio-util",
···
848
1356
849
1357
[[package]]
850
1358
name = "half"
851
-
version = "2.6.0"
1359
+
version = "2.7.1"
852
1360
source = "registry+https://github.com/rust-lang/crates.io-index"
853
-
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
1361
+
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
854
1362
dependencies = [
855
1363
"cfg-if",
856
1364
"crunchy",
1365
+
"zerocopy",
857
1366
]
858
1367
859
1368
[[package]]
860
1369
name = "handlebars"
861
-
version = "6.3.2"
1370
+
version = "6.4.0"
862
1371
source = "registry+https://github.com/rust-lang/crates.io-index"
863
-
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
1372
+
checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e"
864
1373
dependencies = [
865
1374
"derive_builder",
866
1375
"log",
···
870
1379
"rust-embed",
871
1380
"serde",
872
1381
"serde_json",
873
-
"thiserror 2.0.14",
1382
+
"thiserror 2.0.17",
874
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"
875
1399
876
1400
[[package]]
877
1401
name = "hashbrown"
···
891
1415
dependencies = [
892
1416
"allocator-api2",
893
1417
"equivalent",
894
-
"foldhash",
1418
+
"foldhash 0.1.5",
1419
+
]
1420
+
1421
+
[[package]]
1422
+
name = "hashbrown"
1423
+
version = "0.16.1"
1424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1425
+
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1426
+
dependencies = [
1427
+
"allocator-api2",
1428
+
"equivalent",
1429
+
"foldhash 0.2.0",
895
1430
]
896
1431
897
1432
[[package]]
···
904
1439
]
905
1440
906
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]]
907
1462
name = "heck"
908
1463
version = "0.5.0"
909
1464
source = "registry+https://github.com/rust-lang/crates.io-index"
···
914
1469
version = "0.4.3"
915
1470
source = "registry+https://github.com/rust-lang/crates.io-index"
916
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"
917
1478
918
1479
[[package]]
919
1480
name = "hkdf"
···
935
1496
936
1497
[[package]]
937
1498
name = "home"
938
-
version = "0.5.11"
1499
+
version = "0.5.12"
939
1500
source = "registry+https://github.com/rust-lang/crates.io-index"
940
-
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
1501
+
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
941
1502
dependencies = [
942
-
"windows-sys 0.59.0",
1503
+
"windows-sys 0.61.2",
943
1504
]
944
1505
945
1506
[[package]]
946
-
name = "hostname"
947
-
version = "0.4.1"
1507
+
name = "html-escape"
1508
+
version = "0.2.13"
948
1509
source = "registry+https://github.com/rust-lang/crates.io-index"
949
-
checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
1510
+
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
950
1511
dependencies = [
951
-
"cfg-if",
952
-
"libc",
953
-
"windows-link",
1512
+
"utf8-width",
954
1513
]
955
1514
956
1515
[[package]]
957
1516
name = "http"
958
-
version = "1.3.1"
1517
+
version = "1.4.0"
959
1518
source = "registry+https://github.com/rust-lang/crates.io-index"
960
-
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
1519
+
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
961
1520
dependencies = [
962
1521
"bytes",
963
-
"fnv",
964
1522
"itoa",
965
1523
]
966
1524
···
1001
1559
1002
1560
[[package]]
1003
1561
name = "hyper"
1004
-
version = "1.6.0"
1562
+
version = "1.8.1"
1005
1563
source = "registry+https://github.com/rust-lang/crates.io-index"
1006
-
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
1564
+
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
1007
1565
dependencies = [
1566
+
"atomic-waker",
1008
1567
"bytes",
1009
1568
"futures-channel",
1010
-
"futures-util",
1569
+
"futures-core",
1011
1570
"h2",
1012
1571
"http",
1013
1572
"http-body",
···
1015
1574
"httpdate",
1016
1575
"itoa",
1017
1576
"pin-project-lite",
1577
+
"pin-utils",
1018
1578
"smallvec",
1019
1579
"tokio",
1020
1580
"want",
1021
1581
]
1022
1582
1023
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]]
1024
1601
name = "hyper-timeout"
1025
1602
version = "0.5.2"
1026
1603
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1035
1612
1036
1613
[[package]]
1037
1614
name = "hyper-util"
1038
-
version = "0.1.16"
1615
+
version = "0.1.19"
1039
1616
source = "registry+https://github.com/rust-lang/crates.io-index"
1040
-
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1617
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
1041
1618
dependencies = [
1619
+
"base64",
1042
1620
"bytes",
1043
1621
"futures-channel",
1044
1622
"futures-core",
···
1046
1624
"http",
1047
1625
"http-body",
1048
1626
"hyper",
1627
+
"ipnet",
1049
1628
"libc",
1629
+
"percent-encoding",
1050
1630
"pin-project-lite",
1051
1631
"socket2",
1632
+
"system-configuration",
1052
1633
"tokio",
1053
1634
"tower-service",
1054
1635
"tracing",
1636
+
"windows-registry",
1055
1637
]
1056
1638
1057
1639
[[package]]
1058
1640
name = "iana-time-zone"
1059
-
version = "0.1.63"
1641
+
version = "0.1.64"
1060
1642
source = "registry+https://github.com/rust-lang/crates.io-index"
1061
-
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
1643
+
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
1062
1644
dependencies = [
1063
1645
"android_system_properties",
1064
1646
"core-foundation-sys",
···
1080
1662
1081
1663
[[package]]
1082
1664
name = "icu_collections"
1083
-
version = "2.0.0"
1665
+
version = "2.1.1"
1084
1666
source = "registry+https://github.com/rust-lang/crates.io-index"
1085
-
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
1667
+
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
1086
1668
dependencies = [
1087
1669
"displaydoc",
1088
1670
"potential_utf",
···
1093
1675
1094
1676
[[package]]
1095
1677
name = "icu_locale_core"
1096
-
version = "2.0.0"
1678
+
version = "2.1.1"
1097
1679
source = "registry+https://github.com/rust-lang/crates.io-index"
1098
-
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
1680
+
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
1099
1681
dependencies = [
1100
1682
"displaydoc",
1101
1683
"litemap",
···
1106
1688
1107
1689
[[package]]
1108
1690
name = "icu_normalizer"
1109
-
version = "2.0.0"
1691
+
version = "2.1.1"
1110
1692
source = "registry+https://github.com/rust-lang/crates.io-index"
1111
-
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
1693
+
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
1112
1694
dependencies = [
1113
-
"displaydoc",
1114
1695
"icu_collections",
1115
1696
"icu_normalizer_data",
1116
1697
"icu_properties",
···
1121
1702
1122
1703
[[package]]
1123
1704
name = "icu_normalizer_data"
1124
-
version = "2.0.0"
1705
+
version = "2.1.1"
1125
1706
source = "registry+https://github.com/rust-lang/crates.io-index"
1126
-
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
1707
+
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
1127
1708
1128
1709
[[package]]
1129
1710
name = "icu_properties"
1130
-
version = "2.0.1"
1711
+
version = "2.1.2"
1131
1712
source = "registry+https://github.com/rust-lang/crates.io-index"
1132
-
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
1713
+
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1133
1714
dependencies = [
1134
-
"displaydoc",
1135
1715
"icu_collections",
1136
1716
"icu_locale_core",
1137
1717
"icu_properties_data",
1138
1718
"icu_provider",
1139
-
"potential_utf",
1140
1719
"zerotrie",
1141
1720
"zerovec",
1142
1721
]
1143
1722
1144
1723
[[package]]
1145
1724
name = "icu_properties_data"
1146
-
version = "2.0.1"
1725
+
version = "2.1.2"
1147
1726
source = "registry+https://github.com/rust-lang/crates.io-index"
1148
-
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
1727
+
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1149
1728
1150
1729
[[package]]
1151
1730
name = "icu_provider"
1152
-
version = "2.0.0"
1731
+
version = "2.1.1"
1153
1732
source = "registry+https://github.com/rust-lang/crates.io-index"
1154
-
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
1733
+
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
1155
1734
dependencies = [
1156
1735
"displaydoc",
1157
1736
"icu_locale_core",
1158
-
"stable_deref_trait",
1159
-
"tinystr",
1160
1737
"writeable",
1161
1738
"yoke",
1162
1739
"zerofrom",
···
1172
1749
1173
1750
[[package]]
1174
1751
name = "idna"
1175
-
version = "1.0.3"
1752
+
version = "1.1.0"
1176
1753
source = "registry+https://github.com/rust-lang/crates.io-index"
1177
-
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
1754
+
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
1178
1755
dependencies = [
1179
1756
"idna_adapter",
1180
1757
"smallvec",
···
1193
1770
1194
1771
[[package]]
1195
1772
name = "indexmap"
1196
-
version = "2.10.0"
1773
+
version = "1.9.3"
1197
1774
source = "registry+https://github.com/rust-lang/crates.io-index"
1198
-
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
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"
1199
1787
dependencies = [
1200
1788
"equivalent",
1201
-
"hashbrown 0.15.5",
1789
+
"hashbrown 0.16.1",
1790
+
"serde",
1791
+
"serde_core",
1792
+
]
1793
+
1794
+
[[package]]
1795
+
name = "indoc"
1796
+
version = "2.0.7"
1797
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1798
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1799
+
dependencies = [
1800
+
"rustversion",
1202
1801
]
1203
1802
1204
1803
[[package]]
···
1211
1810
]
1212
1811
1213
1812
[[package]]
1214
-
name = "io-uring"
1215
-
version = "0.7.9"
1813
+
name = "inventory"
1814
+
version = "0.3.21"
1216
1815
source = "registry+https://github.com/rust-lang/crates.io-index"
1217
-
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
1816
+
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
1218
1817
dependencies = [
1219
-
"bitflags",
1220
-
"cfg-if",
1221
-
"libc",
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",
1222
1846
]
1223
1847
1224
1848
[[package]]
1225
1849
name = "itoa"
1226
-
version = "1.0.15"
1850
+
version = "1.0.17"
1227
1851
source = "registry+https://github.com/rust-lang/crates.io-index"
1228
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
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
+
]
1229
1980
1230
1981
[[package]]
1231
1982
name = "jobserver"
1232
-
version = "0.1.33"
1983
+
version = "0.1.34"
1233
1984
source = "registry+https://github.com/rust-lang/crates.io-index"
1234
-
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
1985
+
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
1235
1986
dependencies = [
1236
-
"getrandom 0.3.3",
1987
+
"getrandom 0.3.4",
1237
1988
"libc",
1238
1989
]
1239
1990
1240
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]]
1241
2009
name = "js-sys"
1242
-
version = "0.3.77"
2010
+
version = "0.3.83"
1243
2011
source = "registry+https://github.com/rust-lang/crates.io-index"
1244
-
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
2012
+
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
1245
2013
dependencies = [
1246
2014
"once_cell",
1247
2015
"wasm-bindgen",
···
1270
2038
]
1271
2039
1272
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]]
1273
2064
name = "lazy_static"
1274
2065
version = "1.5.0"
1275
2066
source = "registry+https://github.com/rust-lang/crates.io-index"
1276
2067
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1277
2068
dependencies = [
1278
-
"spin",
2069
+
"spin 0.9.8",
1279
2070
]
1280
2071
1281
2072
[[package]]
1282
2073
name = "lettre"
1283
-
version = "0.11.18"
2074
+
version = "0.11.19"
1284
2075
source = "registry+https://github.com/rust-lang/crates.io-index"
1285
-
checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56"
2076
+
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
1286
2077
dependencies = [
1287
2078
"async-trait",
1288
2079
"base64",
···
1292
2083
"fastrand",
1293
2084
"futures-io",
1294
2085
"futures-util",
1295
-
"hostname",
1296
2086
"httpdate",
1297
2087
"idna",
1298
2088
"mime",
1299
-
"native-tls",
1300
-
"nom",
2089
+
"nom 8.0.0",
1301
2090
"percent-encoding",
1302
2091
"quoted_printable",
2092
+
"rustls",
1303
2093
"socket2",
1304
2094
"tokio",
1305
-
"tokio-native-tls",
2095
+
"tokio-rustls",
1306
2096
"url",
2097
+
"webpki-roots 1.0.5",
1307
2098
]
1308
2099
1309
2100
[[package]]
1310
2101
name = "libc"
1311
-
version = "0.2.175"
2102
+
version = "0.2.178"
1312
2103
source = "registry+https://github.com/rust-lang/crates.io-index"
1313
-
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
2104
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
1314
2105
1315
2106
[[package]]
1316
2107
name = "libm"
···
1320
2111
1321
2112
[[package]]
1322
2113
name = "libredox"
1323
-
version = "0.1.9"
2114
+
version = "0.1.12"
1324
2115
source = "registry+https://github.com/rust-lang/crates.io-index"
1325
-
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
2116
+
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
1326
2117
dependencies = [
1327
2118
"bitflags",
1328
2119
"libc",
1329
-
"redox_syscall",
2120
+
"redox_syscall 0.7.0",
1330
2121
]
1331
2122
1332
2123
[[package]]
···
1341
2132
]
1342
2133
1343
2134
[[package]]
1344
-
name = "linux-raw-sys"
1345
-
version = "0.9.4"
1346
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1347
-
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
1348
-
1349
-
[[package]]
1350
2135
name = "litemap"
1351
-
version = "0.8.0"
2136
+
version = "0.8.1"
1352
2137
source = "registry+https://github.com/rust-lang/crates.io-index"
1353
-
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
2138
+
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
1354
2139
1355
2140
[[package]]
1356
2141
name = "lock_api"
1357
-
version = "0.4.13"
2142
+
version = "0.4.14"
1358
2143
source = "registry+https://github.com/rust-lang/crates.io-index"
1359
-
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
2144
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1360
2145
dependencies = [
1361
-
"autocfg",
1362
2146
"scopeguard",
1363
2147
]
1364
2148
1365
2149
[[package]]
1366
2150
name = "log"
1367
-
version = "0.4.27"
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"
1368
2171
source = "registry+https://github.com/rust-lang/crates.io-index"
1369
-
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
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
+
]
1370
2184
1371
2185
[[package]]
1372
2186
name = "matchers"
1373
-
version = "0.1.0"
2187
+
version = "0.2.0"
1374
2188
source = "registry+https://github.com/rust-lang/crates.io-index"
1375
-
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
2189
+
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
1376
2190
dependencies = [
1377
-
"regex-automata 0.1.10",
2191
+
"regex-automata",
1378
2192
]
1379
2193
1380
2194
[[package]]
···
1395
2209
1396
2210
[[package]]
1397
2211
name = "memchr"
1398
-
version = "2.7.5"
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"
1399
2230
source = "registry+https://github.com/rust-lang/crates.io-index"
1400
-
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
2231
+
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
2232
+
dependencies = [
2233
+
"proc-macro2",
2234
+
"quote",
2235
+
"syn 2.0.112",
2236
+
]
1401
2237
1402
2238
[[package]]
1403
2239
name = "mime"
···
1406
2242
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1407
2243
1408
2244
[[package]]
2245
+
name = "minimal-lexical"
2246
+
version = "0.2.1"
2247
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2248
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2249
+
2250
+
[[package]]
1409
2251
name = "miniz_oxide"
1410
2252
version = "0.8.9"
1411
2253
source = "registry+https://github.com/rust-lang/crates.io-index"
1412
2254
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1413
2255
dependencies = [
1414
2256
"adler2",
2257
+
"simd-adler32",
1415
2258
]
1416
2259
1417
2260
[[package]]
1418
2261
name = "mio"
1419
-
version = "1.0.4"
2262
+
version = "1.1.1"
1420
2263
source = "registry+https://github.com/rust-lang/crates.io-index"
1421
-
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
2264
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
1422
2265
dependencies = [
1423
2266
"libc",
1424
-
"wasi 0.11.1+wasi-snapshot-preview1",
1425
-
"windows-sys 0.59.0",
2267
+
"wasi",
2268
+
"windows-sys 0.61.2",
1426
2269
]
1427
2270
1428
2271
[[package]]
1429
-
name = "native-tls"
1430
-
version = "0.2.14"
2272
+
name = "multibase"
2273
+
version = "0.9.2"
1431
2274
source = "registry+https://github.com/rust-lang/crates.io-index"
1432
-
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
2275
+
checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77"
1433
2276
dependencies = [
1434
-
"libc",
1435
-
"log",
1436
-
"openssl",
1437
-
"openssl-probe",
1438
-
"openssl-sys",
1439
-
"schannel",
1440
-
"security-framework",
1441
-
"security-framework-sys",
1442
-
"tempfile",
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]]
2316
+
name = "nom"
2317
+
version = "7.1.3"
2318
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2319
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2320
+
dependencies = [
2321
+
"memchr",
2322
+
"minimal-lexical",
1443
2323
]
1444
2324
1445
2325
[[package]]
···
1465
2345
1466
2346
[[package]]
1467
2347
name = "nu-ansi-term"
1468
-
version = "0.46.0"
2348
+
version = "0.50.3"
1469
2349
source = "registry+https://github.com/rust-lang/crates.io-index"
1470
-
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
2350
+
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
1471
2351
dependencies = [
1472
-
"overload",
1473
-
"winapi",
2352
+
"windows-sys 0.59.0",
1474
2353
]
1475
2354
1476
2355
[[package]]
1477
2356
name = "num-bigint-dig"
1478
-
version = "0.8.4"
2357
+
version = "0.8.6"
1479
2358
source = "registry+https://github.com/rust-lang/crates.io-index"
1480
-
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
2359
+
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
1481
2360
dependencies = [
1482
-
"byteorder",
1483
2361
"lazy_static",
1484
2362
"libm",
1485
2363
"num-integer",
···
1489
2367
"smallvec",
1490
2368
"zeroize",
1491
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"
1492
2376
1493
2377
[[package]]
1494
2378
name = "num-integer"
···
1537
2421
1538
2422
[[package]]
1539
2423
name = "object"
1540
-
version = "0.36.7"
2424
+
version = "0.32.2"
1541
2425
source = "registry+https://github.com/rust-lang/crates.io-index"
1542
-
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
2426
+
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
1543
2427
dependencies = [
1544
2428
"memchr",
1545
2429
]
···
1552
2436
1553
2437
[[package]]
1554
2438
name = "openssl"
1555
-
version = "0.10.73"
2439
+
version = "0.10.75"
1556
2440
source = "registry+https://github.com/rust-lang/crates.io-index"
1557
-
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
2441
+
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
1558
2442
dependencies = [
1559
2443
"bitflags",
1560
2444
"cfg-if",
···
1573
2457
dependencies = [
1574
2458
"proc-macro2",
1575
2459
"quote",
1576
-
"syn",
2460
+
"syn 2.0.112",
1577
2461
]
1578
2462
1579
2463
[[package]]
1580
-
name = "openssl-probe"
1581
-
version = "0.1.6"
1582
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1583
-
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
1584
-
1585
-
[[package]]
1586
2464
name = "openssl-sys"
1587
-
version = "0.9.109"
2465
+
version = "0.9.111"
1588
2466
source = "registry+https://github.com/rust-lang/crates.io-index"
1589
-
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
2467
+
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
1590
2468
dependencies = [
1591
2469
"cc",
1592
2470
"libc",
···
1595
2473
]
1596
2474
1597
2475
[[package]]
1598
-
name = "overload"
1599
-
version = "0.1.1"
2476
+
name = "ouroboros"
2477
+
version = "0.18.5"
1600
2478
source = "registry+https://github.com/rust-lang/crates.io-index"
1601
-
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
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
+
]
1602
2510
1603
2511
[[package]]
1604
2512
name = "parking"
···
1608
2516
1609
2517
[[package]]
1610
2518
name = "parking_lot"
1611
-
version = "0.12.4"
2519
+
version = "0.12.5"
1612
2520
source = "registry+https://github.com/rust-lang/crates.io-index"
1613
-
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
2521
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1614
2522
dependencies = [
1615
2523
"lock_api",
1616
2524
"parking_lot_core",
···
1618
2526
1619
2527
[[package]]
1620
2528
name = "parking_lot_core"
1621
-
version = "0.9.11"
2529
+
version = "0.9.12"
1622
2530
source = "registry+https://github.com/rust-lang/crates.io-index"
1623
-
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
2531
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1624
2532
dependencies = [
1625
2533
"cfg-if",
1626
2534
"libc",
1627
-
"redox_syscall",
2535
+
"redox_syscall 0.5.18",
1628
2536
"smallvec",
1629
-
"windows-targets 0.52.6",
2537
+
"windows-link",
1630
2538
]
1631
2539
1632
2540
[[package]]
···
1652
2560
1653
2561
[[package]]
1654
2562
name = "pds_gatekeeper"
1655
-
version = "0.1.0"
2563
+
version = "0.1.2"
1656
2564
dependencies = [
1657
2565
"anyhow",
2566
+
"aws-lc-rs",
1658
2567
"axum",
1659
2568
"axum-template",
1660
2569
"chrono",
2570
+
"dashmap",
1661
2571
"dotenvy",
1662
2572
"handlebars",
1663
2573
"hex",
2574
+
"html-escape",
1664
2575
"hyper-util",
2576
+
"jacquard-common",
2577
+
"jacquard-identity",
2578
+
"josekit",
1665
2579
"jwt-compact",
1666
2580
"lettre",
2581
+
"multibase",
1667
2582
"rand 0.9.2",
2583
+
"reqwest",
1668
2584
"rust-embed",
2585
+
"rustls",
1669
2586
"scrypt",
1670
2587
"serde",
1671
2588
"serde_json",
1672
2589
"sha2",
1673
2590
"sqlx",
1674
2591
"tokio",
2592
+
"tower",
1675
2593
"tower-http",
1676
2594
"tower_governor",
1677
2595
"tracing",
1678
2596
"tracing-subscriber",
2597
+
"urlencoding",
1679
2598
]
1680
2599
1681
2600
[[package]]
···
1689
2608
1690
2609
[[package]]
1691
2610
name = "percent-encoding"
1692
-
version = "2.3.1"
2611
+
version = "2.3.2"
1693
2612
source = "registry+https://github.com/rust-lang/crates.io-index"
1694
-
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
2613
+
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1695
2614
1696
2615
[[package]]
1697
2616
name = "pest"
1698
-
version = "2.8.1"
2617
+
version = "2.8.4"
1699
2618
source = "registry+https://github.com/rust-lang/crates.io-index"
1700
-
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
2619
+
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
1701
2620
dependencies = [
1702
2621
"memchr",
1703
-
"thiserror 2.0.14",
1704
2622
"ucd-trie",
1705
2623
]
1706
2624
1707
2625
[[package]]
1708
2626
name = "pest_derive"
1709
-
version = "2.8.1"
2627
+
version = "2.8.4"
1710
2628
source = "registry+https://github.com/rust-lang/crates.io-index"
1711
-
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
2629
+
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
1712
2630
dependencies = [
1713
2631
"pest",
1714
2632
"pest_generator",
···
1716
2634
1717
2635
[[package]]
1718
2636
name = "pest_generator"
1719
-
version = "2.8.1"
2637
+
version = "2.8.4"
1720
2638
source = "registry+https://github.com/rust-lang/crates.io-index"
1721
-
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
2639
+
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
1722
2640
dependencies = [
1723
2641
"pest",
1724
2642
"pest_meta",
1725
2643
"proc-macro2",
1726
2644
"quote",
1727
-
"syn",
2645
+
"syn 2.0.112",
1728
2646
]
1729
2647
1730
2648
[[package]]
1731
2649
name = "pest_meta"
1732
-
version = "2.8.1"
2650
+
version = "2.8.4"
1733
2651
source = "registry+https://github.com/rust-lang/crates.io-index"
1734
-
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
2652
+
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
1735
2653
dependencies = [
1736
2654
"pest",
1737
2655
"sha2",
···
1754
2672
dependencies = [
1755
2673
"proc-macro2",
1756
2674
"quote",
1757
-
"syn",
2675
+
"syn 2.0.112",
1758
2676
]
1759
2677
1760
2678
[[package]]
···
1798
2716
1799
2717
[[package]]
1800
2718
name = "portable-atomic"
1801
-
version = "1.11.1"
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"
1802
2726
source = "registry+https://github.com/rust-lang/crates.io-index"
1803
-
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2727
+
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
2728
+
dependencies = [
2729
+
"cobs",
2730
+
"embedded-io 0.4.0",
2731
+
"embedded-io 0.6.1",
2732
+
"heapless",
2733
+
"serde",
2734
+
]
1804
2735
1805
2736
[[package]]
1806
2737
name = "potential_utf"
1807
-
version = "0.1.2"
2738
+
version = "0.1.4"
1808
2739
source = "registry+https://github.com/rust-lang/crates.io-index"
1809
-
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
2740
+
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
1810
2741
dependencies = [
1811
2742
"zerovec",
1812
2743
]
1813
2744
1814
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]]
1815
2752
name = "ppv-lite86"
1816
2753
version = "0.2.21"
1817
2754
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1821
2758
]
1822
2759
1823
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]]
1824
2804
name = "proc-macro2"
1825
-
version = "1.0.97"
2805
+
version = "1.0.104"
1826
2806
source = "registry+https://github.com/rust-lang/crates.io-index"
1827
-
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
2807
+
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
1828
2808
dependencies = [
1829
2809
"unicode-ident",
1830
2810
]
1831
2811
1832
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]]
1833
2826
name = "psm"
1834
-
version = "0.1.26"
2827
+
version = "0.1.28"
1835
2828
source = "registry+https://github.com/rust-lang/crates.io-index"
1836
-
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
2829
+
checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01"
1837
2830
dependencies = [
2831
+
"ar_archive_writer",
1838
2832
"cc",
1839
2833
]
1840
2834
···
1848
2842
"libc",
1849
2843
"once_cell",
1850
2844
"raw-cpuid",
1851
-
"wasi 0.11.1+wasi-snapshot-preview1",
2845
+
"wasi",
1852
2846
"web-sys",
1853
2847
"winapi",
1854
2848
]
1855
2849
1856
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]]
1857
2906
name = "quote"
1858
-
version = "1.0.40"
2907
+
version = "1.0.42"
1859
2908
source = "registry+https://github.com/rust-lang/crates.io-index"
1860
-
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
2909
+
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
1861
2910
dependencies = [
1862
2911
"proc-macro2",
1863
2912
]
···
1930
2979
source = "registry+https://github.com/rust-lang/crates.io-index"
1931
2980
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
1932
2981
dependencies = [
1933
-
"getrandom 0.3.3",
2982
+
"getrandom 0.3.4",
1934
2983
]
1935
2984
1936
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]]
1937
2992
name = "raw-cpuid"
1938
-
version = "11.5.0"
2993
+
version = "11.6.0"
1939
2994
source = "registry+https://github.com/rust-lang/crates.io-index"
1940
-
checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146"
2995
+
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
1941
2996
dependencies = [
1942
2997
"bitflags",
1943
2998
]
1944
2999
1945
3000
[[package]]
1946
3001
name = "redox_syscall"
1947
-
version = "0.5.17"
3002
+
version = "0.5.18"
1948
3003
source = "registry+https://github.com/rust-lang/crates.io-index"
1949
-
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
3004
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1950
3005
dependencies = [
1951
3006
"bitflags",
1952
3007
]
1953
3008
1954
3009
[[package]]
1955
-
name = "regex"
1956
-
version = "1.11.1"
3010
+
name = "redox_syscall"
3011
+
version = "0.7.0"
1957
3012
source = "registry+https://github.com/rust-lang/crates.io-index"
1958
-
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
3013
+
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
1959
3014
dependencies = [
1960
-
"aho-corasick",
1961
-
"memchr",
1962
-
"regex-automata 0.4.9",
1963
-
"regex-syntax 0.8.5",
3015
+
"bitflags",
1964
3016
]
1965
3017
1966
3018
[[package]]
1967
-
name = "regex-automata"
1968
-
version = "0.1.10"
3019
+
name = "ref-cast"
3020
+
version = "1.0.25"
1969
3021
source = "registry+https://github.com/rust-lang/crates.io-index"
1970
-
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
3022
+
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
1971
3023
dependencies = [
1972
-
"regex-syntax 0.6.29",
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",
1973
3048
]
1974
3049
1975
3050
[[package]]
1976
3051
name = "regex-automata"
1977
-
version = "0.4.9"
3052
+
version = "0.4.13"
1978
3053
source = "registry+https://github.com/rust-lang/crates.io-index"
1979
-
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
3054
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
1980
3055
dependencies = [
1981
3056
"aho-corasick",
1982
3057
"memchr",
1983
-
"regex-syntax 0.8.5",
3058
+
"regex-syntax",
1984
3059
]
1985
3060
1986
3061
[[package]]
1987
-
name = "regex-syntax"
1988
-
version = "0.6.29"
3062
+
name = "regex-lite"
3063
+
version = "0.1.8"
1989
3064
source = "registry+https://github.com/rust-lang/crates.io-index"
1990
-
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
3065
+
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
1991
3066
1992
3067
[[package]]
1993
3068
name = "regex-syntax"
1994
-
version = "0.8.5"
3069
+
version = "0.8.8"
1995
3070
source = "registry+https://github.com/rust-lang/crates.io-index"
1996
-
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
3071
+
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
3072
+
3073
+
[[package]]
3074
+
name = "reqwest"
3075
+
version = "0.12.28"
3076
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3077
+
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
3078
+
dependencies = [
3079
+
"base64",
3080
+
"bytes",
3081
+
"encoding_rs",
3082
+
"futures-core",
3083
+
"h2",
3084
+
"http",
3085
+
"http-body",
3086
+
"http-body-util",
3087
+
"hyper",
3088
+
"hyper-rustls",
3089
+
"hyper-util",
3090
+
"js-sys",
3091
+
"log",
3092
+
"mime",
3093
+
"percent-encoding",
3094
+
"pin-project-lite",
3095
+
"quinn",
3096
+
"rustls",
3097
+
"rustls-pki-types",
3098
+
"serde",
3099
+
"serde_json",
3100
+
"serde_urlencoded",
3101
+
"sync_wrapper",
3102
+
"tokio",
3103
+
"tokio-rustls",
3104
+
"tower",
3105
+
"tower-http",
3106
+
"tower-service",
3107
+
"url",
3108
+
"wasm-bindgen",
3109
+
"wasm-bindgen-futures",
3110
+
"web-sys",
3111
+
"webpki-roots 1.0.5",
3112
+
]
3113
+
3114
+
[[package]]
3115
+
name = "rfc6979"
3116
+
version = "0.4.0"
3117
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3118
+
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
3119
+
dependencies = [
3120
+
"hmac",
3121
+
"subtle",
3122
+
]
1997
3123
1998
3124
[[package]]
1999
3125
name = "ring"
···
2005
3131
"cfg-if",
2006
3132
"getrandom 0.2.16",
2007
3133
"libc",
2008
-
"untrusted",
3134
+
"untrusted 0.9.0",
2009
3135
"windows-sys 0.52.0",
2010
3136
]
2011
3137
2012
3138
[[package]]
2013
3139
name = "rsa"
2014
-
version = "0.9.8"
3140
+
version = "0.9.9"
2015
3141
source = "registry+https://github.com/rust-lang/crates.io-index"
2016
-
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
3142
+
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
2017
3143
dependencies = [
2018
3144
"const-oid",
2019
3145
"digest",
···
2031
3157
2032
3158
[[package]]
2033
3159
name = "rust-embed"
2034
-
version = "8.7.2"
3160
+
version = "8.9.0"
2035
3161
source = "registry+https://github.com/rust-lang/crates.io-index"
2036
-
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
3162
+
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
2037
3163
dependencies = [
2038
3164
"rust-embed-impl",
2039
3165
"rust-embed-utils",
···
2042
3168
2043
3169
[[package]]
2044
3170
name = "rust-embed-impl"
2045
-
version = "8.7.2"
3171
+
version = "8.9.0"
2046
3172
source = "registry+https://github.com/rust-lang/crates.io-index"
2047
-
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
3173
+
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
2048
3174
dependencies = [
2049
3175
"proc-macro2",
2050
3176
"quote",
2051
3177
"rust-embed-utils",
2052
-
"syn",
3178
+
"syn 2.0.112",
2053
3179
"walkdir",
2054
3180
]
2055
3181
2056
3182
[[package]]
2057
3183
name = "rust-embed-utils"
2058
-
version = "8.7.2"
3184
+
version = "8.9.0"
2059
3185
source = "registry+https://github.com/rust-lang/crates.io-index"
2060
-
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
3186
+
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
2061
3187
dependencies = [
2062
3188
"globset",
2063
3189
"sha2",
···
2065
3191
]
2066
3192
2067
3193
[[package]]
2068
-
name = "rustc-demangle"
2069
-
version = "0.1.26"
3194
+
name = "rustc-hash"
3195
+
version = "2.1.1"
2070
3196
source = "registry+https://github.com/rust-lang/crates.io-index"
2071
-
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
3197
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
2072
3198
2073
3199
[[package]]
2074
-
name = "rustix"
2075
-
version = "1.0.8"
3200
+
name = "rustc_version"
3201
+
version = "0.4.1"
2076
3202
source = "registry+https://github.com/rust-lang/crates.io-index"
2077
-
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
3203
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2078
3204
dependencies = [
2079
-
"bitflags",
2080
-
"errno",
2081
-
"libc",
2082
-
"linux-raw-sys",
2083
-
"windows-sys 0.59.0",
3205
+
"semver",
2084
3206
]
2085
3207
2086
3208
[[package]]
2087
3209
name = "rustls"
2088
-
version = "0.23.31"
3210
+
version = "0.23.35"
2089
3211
source = "registry+https://github.com/rust-lang/crates.io-index"
2090
-
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
3212
+
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
2091
3213
dependencies = [
3214
+
"aws-lc-rs",
3215
+
"log",
2092
3216
"once_cell",
2093
3217
"ring",
2094
3218
"rustls-pki-types",
···
2099
3223
2100
3224
[[package]]
2101
3225
name = "rustls-pki-types"
2102
-
version = "1.12.0"
3226
+
version = "1.13.2"
2103
3227
source = "registry+https://github.com/rust-lang/crates.io-index"
2104
-
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
3228
+
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
2105
3229
dependencies = [
3230
+
"web-time",
2106
3231
"zeroize",
2107
3232
]
2108
3233
2109
3234
[[package]]
2110
3235
name = "rustls-webpki"
2111
-
version = "0.103.4"
3236
+
version = "0.103.8"
2112
3237
source = "registry+https://github.com/rust-lang/crates.io-index"
2113
-
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
3238
+
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
2114
3239
dependencies = [
3240
+
"aws-lc-rs",
2115
3241
"ring",
2116
3242
"rustls-pki-types",
2117
-
"untrusted",
3243
+
"untrusted 0.9.0",
2118
3244
]
2119
3245
2120
3246
[[package]]
···
2125
3251
2126
3252
[[package]]
2127
3253
name = "ryu"
2128
-
version = "1.0.20"
3254
+
version = "1.0.22"
2129
3255
source = "registry+https://github.com/rust-lang/crates.io-index"
2130
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
3256
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
2131
3257
2132
3258
[[package]]
2133
3259
name = "salsa20"
···
2148
3274
]
2149
3275
2150
3276
[[package]]
2151
-
name = "schannel"
2152
-
version = "0.1.27"
3277
+
name = "schemars"
3278
+
version = "0.9.0"
2153
3279
source = "registry+https://github.com/rust-lang/crates.io-index"
2154
-
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
3280
+
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
2155
3281
dependencies = [
2156
-
"windows-sys 0.59.0",
3282
+
"dyn-clone",
3283
+
"ref-cast",
3284
+
"serde",
3285
+
"serde_json",
2157
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"
2158
3305
2159
3306
[[package]]
2160
3307
name = "scopeguard"
···
2175
3322
]
2176
3323
2177
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]]
2178
3339
name = "secp256k1"
2179
3340
version = "0.28.2"
2180
3341
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2193
3354
]
2194
3355
2195
3356
[[package]]
2196
-
name = "security-framework"
2197
-
version = "2.11.1"
3357
+
name = "semver"
3358
+
version = "1.0.27"
2198
3359
source = "registry+https://github.com/rust-lang/crates.io-index"
2199
-
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
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"
2200
3373
dependencies = [
2201
-
"bitflags",
2202
-
"core-foundation",
2203
-
"core-foundation-sys",
2204
-
"libc",
2205
-
"security-framework-sys",
3374
+
"serde_core",
3375
+
"serde_derive",
2206
3376
]
2207
3377
2208
3378
[[package]]
2209
-
name = "security-framework-sys"
2210
-
version = "2.14.0"
3379
+
name = "serde_bytes"
3380
+
version = "0.11.19"
2211
3381
source = "registry+https://github.com/rust-lang/crates.io-index"
2212
-
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
3382
+
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
2213
3383
dependencies = [
2214
-
"core-foundation-sys",
2215
-
"libc",
3384
+
"serde",
3385
+
"serde_core",
2216
3386
]
2217
3387
2218
3388
[[package]]
2219
-
name = "serde"
2220
-
version = "1.0.219"
3389
+
name = "serde_core"
3390
+
version = "1.0.228"
2221
3391
source = "registry+https://github.com/rust-lang/crates.io-index"
2222
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3392
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2223
3393
dependencies = [
2224
3394
"serde_derive",
2225
3395
]
2226
3396
2227
3397
[[package]]
2228
3398
name = "serde_derive"
2229
-
version = "1.0.219"
3399
+
version = "1.0.228"
2230
3400
source = "registry+https://github.com/rust-lang/crates.io-index"
2231
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3401
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
2232
3402
dependencies = [
2233
3403
"proc-macro2",
2234
3404
"quote",
2235
-
"syn",
3405
+
"syn 2.0.112",
3406
+
]
3407
+
3408
+
[[package]]
3409
+
name = "serde_html_form"
3410
+
version = "0.2.8"
3411
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3412
+
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
3413
+
dependencies = [
3414
+
"form_urlencoded",
3415
+
"indexmap 2.12.1",
3416
+
"itoa",
3417
+
"ryu",
3418
+
"serde_core",
3419
+
]
3420
+
3421
+
[[package]]
3422
+
name = "serde_ipld_dagcbor"
3423
+
version = "0.6.4"
3424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3425
+
checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778"
3426
+
dependencies = [
3427
+
"cbor4ii",
3428
+
"ipld-core",
3429
+
"scopeguard",
3430
+
"serde",
2236
3431
]
2237
3432
2238
3433
[[package]]
2239
3434
name = "serde_json"
2240
-
version = "1.0.142"
3435
+
version = "1.0.148"
2241
3436
source = "registry+https://github.com/rust-lang/crates.io-index"
2242
-
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
3437
+
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
2243
3438
dependencies = [
3439
+
"indexmap 2.12.1",
2244
3440
"itoa",
2245
3441
"memchr",
2246
-
"ryu",
2247
3442
"serde",
3443
+
"serde_core",
3444
+
"zmij",
2248
3445
]
2249
3446
2250
3447
[[package]]
2251
3448
name = "serde_path_to_error"
2252
-
version = "0.1.17"
3449
+
version = "0.1.20"
2253
3450
source = "registry+https://github.com/rust-lang/crates.io-index"
2254
-
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
3451
+
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
2255
3452
dependencies = [
2256
3453
"itoa",
2257
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",
2258
3467
]
2259
3468
2260
3469
[[package]]
···
2270
3479
]
2271
3480
2272
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]]
2273
3513
name = "sha1"
2274
3514
version = "0.10.6"
2275
3515
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2308
3548
2309
3549
[[package]]
2310
3550
name = "signal-hook-registry"
2311
-
version = "1.4.6"
3551
+
version = "1.4.8"
2312
3552
source = "registry+https://github.com/rust-lang/crates.io-index"
2313
-
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
3553
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
2314
3554
dependencies = [
3555
+
"errno",
2315
3556
"libc",
2316
3557
]
2317
3558
···
2326
3567
]
2327
3568
2328
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]]
2329
3576
name = "slab"
2330
3577
version = "0.4.11"
2331
3578
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2341
3588
]
2342
3589
2343
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]]
2344
3601
name = "socket2"
2345
-
version = "0.6.0"
3602
+
version = "0.6.1"
2346
3603
source = "registry+https://github.com/rust-lang/crates.io-index"
2347
-
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
3604
+
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
2348
3605
dependencies = [
2349
3606
"libc",
2350
-
"windows-sys 0.59.0",
3607
+
"windows-sys 0.60.2",
2351
3608
]
2352
3609
2353
3610
[[package]]
···
2358
3615
dependencies = [
2359
3616
"lock_api",
2360
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"
2361
3624
2362
3625
[[package]]
2363
3626
name = "spinning_top"
···
2410
3673
"futures-util",
2411
3674
"hashbrown 0.15.5",
2412
3675
"hashlink",
2413
-
"indexmap",
3676
+
"indexmap 2.12.1",
2414
3677
"log",
2415
3678
"memchr",
2416
3679
"once_cell",
···
2420
3683
"serde_json",
2421
3684
"sha2",
2422
3685
"smallvec",
2423
-
"thiserror 2.0.14",
3686
+
"thiserror 2.0.17",
2424
3687
"tokio",
2425
3688
"tokio-stream",
2426
3689
"tracing",
···
2438
3701
"quote",
2439
3702
"sqlx-core",
2440
3703
"sqlx-macros-core",
2441
-
"syn",
3704
+
"syn 2.0.112",
2442
3705
]
2443
3706
2444
3707
[[package]]
···
2449
3712
dependencies = [
2450
3713
"dotenvy",
2451
3714
"either",
2452
-
"heck",
3715
+
"heck 0.5.0",
2453
3716
"hex",
2454
3717
"once_cell",
2455
3718
"proc-macro2",
···
2461
3724
"sqlx-mysql",
2462
3725
"sqlx-postgres",
2463
3726
"sqlx-sqlite",
2464
-
"syn",
3727
+
"syn 2.0.112",
2465
3728
"tokio",
2466
3729
"url",
2467
3730
]
···
2504
3767
"smallvec",
2505
3768
"sqlx-core",
2506
3769
"stringprep",
2507
-
"thiserror 2.0.14",
3770
+
"thiserror 2.0.17",
2508
3771
"tracing",
2509
3772
"whoami",
2510
3773
]
···
2542
3805
"smallvec",
2543
3806
"sqlx-core",
2544
3807
"stringprep",
2545
-
"thiserror 2.0.14",
3808
+
"thiserror 2.0.17",
2546
3809
"tracing",
2547
3810
"whoami",
2548
3811
]
···
2567
3830
"serde",
2568
3831
"serde_urlencoded",
2569
3832
"sqlx-core",
2570
-
"thiserror 2.0.14",
3833
+
"thiserror 2.0.17",
2571
3834
"tracing",
2572
3835
"url",
2573
3836
]
2574
3837
2575
3838
[[package]]
2576
3839
name = "stable_deref_trait"
2577
-
version = "1.2.0"
3840
+
version = "1.2.1"
2578
3841
source = "registry+https://github.com/rust-lang/crates.io-index"
2579
-
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
3842
+
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
2580
3843
2581
3844
[[package]]
2582
3845
name = "stacker"
2583
-
version = "0.1.21"
3846
+
version = "0.1.22"
2584
3847
source = "registry+https://github.com/rust-lang/crates.io-index"
2585
-
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
3848
+
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
2586
3849
dependencies = [
2587
3850
"cc",
2588
3851
"cfg-if",
···
2592
3855
]
2593
3856
2594
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]]
2595
3884
name = "stringprep"
2596
3885
version = "0.1.5"
2597
3886
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2616
3905
2617
3906
[[package]]
2618
3907
name = "syn"
2619
-
version = "2.0.105"
3908
+
version = "1.0.109"
3909
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3910
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
3911
+
dependencies = [
3912
+
"proc-macro2",
3913
+
"quote",
3914
+
"unicode-ident",
3915
+
]
3916
+
3917
+
[[package]]
3918
+
name = "syn"
3919
+
version = "2.0.112"
2620
3920
source = "registry+https://github.com/rust-lang/crates.io-index"
2621
-
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
3921
+
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
2622
3922
dependencies = [
2623
3923
"proc-macro2",
2624
3924
"quote",
···
2630
3930
version = "1.0.2"
2631
3931
source = "registry+https://github.com/rust-lang/crates.io-index"
2632
3932
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3933
+
dependencies = [
3934
+
"futures-core",
3935
+
]
2633
3936
2634
3937
[[package]]
2635
3938
name = "synstructure"
···
2639
3942
dependencies = [
2640
3943
"proc-macro2",
2641
3944
"quote",
2642
-
"syn",
3945
+
"syn 2.0.112",
2643
3946
]
2644
3947
2645
3948
[[package]]
2646
-
name = "tempfile"
2647
-
version = "3.21.0"
3949
+
name = "system-configuration"
3950
+
version = "0.6.1"
2648
3951
source = "registry+https://github.com/rust-lang/crates.io-index"
2649
-
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
3952
+
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
2650
3953
dependencies = [
2651
-
"fastrand",
2652
-
"getrandom 0.3.3",
2653
-
"once_cell",
2654
-
"rustix",
2655
-
"windows-sys 0.59.0",
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",
2656
3967
]
2657
3968
2658
3969
[[package]]
···
2666
3977
2667
3978
[[package]]
2668
3979
name = "thiserror"
2669
-
version = "2.0.14"
3980
+
version = "2.0.17"
2670
3981
source = "registry+https://github.com/rust-lang/crates.io-index"
2671
-
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
3982
+
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
2672
3983
dependencies = [
2673
-
"thiserror-impl 2.0.14",
3984
+
"thiserror-impl 2.0.17",
2674
3985
]
2675
3986
2676
3987
[[package]]
···
2681
3992
dependencies = [
2682
3993
"proc-macro2",
2683
3994
"quote",
2684
-
"syn",
3995
+
"syn 2.0.112",
2685
3996
]
2686
3997
2687
3998
[[package]]
2688
3999
name = "thiserror-impl"
2689
-
version = "2.0.14"
4000
+
version = "2.0.17"
2690
4001
source = "registry+https://github.com/rust-lang/crates.io-index"
2691
-
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
4002
+
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
2692
4003
dependencies = [
2693
4004
"proc-macro2",
2694
4005
"quote",
2695
-
"syn",
4006
+
"syn 2.0.112",
2696
4007
]
2697
4008
2698
4009
[[package]]
···
2705
4016
]
2706
4017
2707
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]]
2708
4050
name = "tinystr"
2709
-
version = "0.8.1"
4051
+
version = "0.8.2"
2710
4052
source = "registry+https://github.com/rust-lang/crates.io-index"
2711
-
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
4053
+
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
2712
4054
dependencies = [
2713
4055
"displaydoc",
2714
4056
"zerovec",
···
2716
4058
2717
4059
[[package]]
2718
4060
name = "tinyvec"
2719
-
version = "1.9.0"
4061
+
version = "1.10.0"
2720
4062
source = "registry+https://github.com/rust-lang/crates.io-index"
2721
-
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
4063
+
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
2722
4064
dependencies = [
2723
4065
"tinyvec_macros",
2724
4066
]
···
2731
4073
2732
4074
[[package]]
2733
4075
name = "tokio"
2734
-
version = "1.47.1"
4076
+
version = "1.48.0"
2735
4077
source = "registry+https://github.com/rust-lang/crates.io-index"
2736
-
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
4078
+
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
2737
4079
dependencies = [
2738
-
"backtrace",
2739
4080
"bytes",
2740
-
"io-uring",
2741
4081
"libc",
2742
4082
"mio",
2743
4083
"pin-project-lite",
2744
4084
"signal-hook-registry",
2745
-
"slab",
2746
4085
"socket2",
2747
4086
"tokio-macros",
2748
-
"windows-sys 0.59.0",
4087
+
"windows-sys 0.61.2",
2749
4088
]
2750
4089
2751
4090
[[package]]
2752
4091
name = "tokio-macros"
2753
-
version = "2.5.0"
4092
+
version = "2.6.0"
2754
4093
source = "registry+https://github.com/rust-lang/crates.io-index"
2755
-
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
4094
+
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
2756
4095
dependencies = [
2757
4096
"proc-macro2",
2758
4097
"quote",
2759
-
"syn",
4098
+
"syn 2.0.112",
2760
4099
]
2761
4100
2762
4101
[[package]]
2763
-
name = "tokio-native-tls"
2764
-
version = "0.3.1"
4102
+
name = "tokio-rustls"
4103
+
version = "0.26.4"
2765
4104
source = "registry+https://github.com/rust-lang/crates.io-index"
2766
-
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
4105
+
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
2767
4106
dependencies = [
2768
-
"native-tls",
4107
+
"rustls",
2769
4108
"tokio",
2770
4109
]
2771
4110
···
2782
4121
2783
4122
[[package]]
2784
4123
name = "tokio-util"
2785
-
version = "0.7.15"
4124
+
version = "0.7.17"
2786
4125
source = "registry+https://github.com/rust-lang/crates.io-index"
2787
-
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
4126
+
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
2788
4127
dependencies = [
2789
4128
"bytes",
2790
4129
"futures-core",
2791
4130
"futures-sink",
4131
+
"futures-util",
2792
4132
"pin-project-lite",
2793
4133
"tokio",
2794
4134
]
2795
4135
2796
4136
[[package]]
2797
4137
name = "tonic"
2798
-
version = "0.14.1"
4138
+
version = "0.14.2"
2799
4139
source = "registry+https://github.com/rust-lang/crates.io-index"
2800
-
checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40"
4140
+
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
2801
4141
dependencies = [
2802
4142
"async-trait",
2803
4143
"axum",
···
2830
4170
dependencies = [
2831
4171
"futures-core",
2832
4172
"futures-util",
2833
-
"indexmap",
4173
+
"indexmap 2.12.1",
2834
4174
"pin-project-lite",
2835
4175
"slab",
2836
4176
"sync_wrapper",
···
2843
4183
2844
4184
[[package]]
2845
4185
name = "tower-http"
2846
-
version = "0.6.6"
4186
+
version = "0.6.8"
2847
4187
source = "registry+https://github.com/rust-lang/crates.io-index"
2848
-
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
4188
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
2849
4189
dependencies = [
2850
4190
"async-compression",
2851
4191
"bitflags",
2852
4192
"bytes",
2853
4193
"futures-core",
4194
+
"futures-util",
2854
4195
"http",
2855
4196
"http-body",
4197
+
"http-body-util",
4198
+
"iri-string",
2856
4199
"pin-project-lite",
2857
4200
"tokio",
2858
4201
"tokio-util",
4202
+
"tower",
2859
4203
"tower-layer",
2860
4204
"tower-service",
2861
4205
]
···
2883
4227
"governor",
2884
4228
"http",
2885
4229
"pin-project",
2886
-
"thiserror 2.0.14",
4230
+
"thiserror 2.0.17",
2887
4231
"tonic",
2888
4232
"tower",
2889
4233
"tracing",
···
2891
4235
2892
4236
[[package]]
2893
4237
name = "tracing"
2894
-
version = "0.1.41"
4238
+
version = "0.1.44"
2895
4239
source = "registry+https://github.com/rust-lang/crates.io-index"
2896
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
4240
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
2897
4241
dependencies = [
2898
4242
"log",
2899
4243
"pin-project-lite",
···
2903
4247
2904
4248
[[package]]
2905
4249
name = "tracing-attributes"
2906
-
version = "0.1.30"
4250
+
version = "0.1.31"
2907
4251
source = "registry+https://github.com/rust-lang/crates.io-index"
2908
-
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
4252
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
2909
4253
dependencies = [
2910
4254
"proc-macro2",
2911
4255
"quote",
2912
-
"syn",
4256
+
"syn 2.0.112",
2913
4257
]
2914
4258
2915
4259
[[package]]
2916
4260
name = "tracing-core"
2917
-
version = "0.1.34"
4261
+
version = "0.1.36"
2918
4262
source = "registry+https://github.com/rust-lang/crates.io-index"
2919
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
4263
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
2920
4264
dependencies = [
2921
4265
"once_cell",
2922
4266
"valuable",
···
2935
4279
2936
4280
[[package]]
2937
4281
name = "tracing-subscriber"
2938
-
version = "0.3.19"
4282
+
version = "0.3.22"
2939
4283
source = "registry+https://github.com/rust-lang/crates.io-index"
2940
-
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
4284
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
2941
4285
dependencies = [
2942
4286
"matchers",
2943
4287
"nu-ansi-term",
2944
4288
"once_cell",
2945
-
"regex",
4289
+
"regex-automata",
2946
4290
"sharded-slab",
2947
4291
"smallvec",
2948
4292
"thread_local",
···
2952
4296
]
2953
4297
2954
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]]
2955
4310
name = "try-lock"
2956
4311
version = "0.2.5"
2957
4312
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2959
4314
2960
4315
[[package]]
2961
4316
name = "typenum"
2962
-
version = "1.18.0"
4317
+
version = "1.19.0"
2963
4318
source = "registry+https://github.com/rust-lang/crates.io-index"
2964
-
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
4319
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
2965
4320
2966
4321
[[package]]
2967
4322
name = "ucd-trie"
···
2977
4332
2978
4333
[[package]]
2979
4334
name = "unicode-ident"
2980
-
version = "1.0.18"
4335
+
version = "1.0.22"
2981
4336
source = "registry+https://github.com/rust-lang/crates.io-index"
2982
-
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
4337
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
2983
4338
2984
4339
[[package]]
2985
4340
name = "unicode-normalization"
2986
-
version = "0.1.24"
4341
+
version = "0.1.25"
2987
4342
source = "registry+https://github.com/rust-lang/crates.io-index"
2988
-
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
4343
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
2989
4344
dependencies = [
2990
4345
"tinyvec",
2991
4346
]
2992
4347
2993
4348
[[package]]
2994
4349
name = "unicode-properties"
2995
-
version = "0.1.3"
4350
+
version = "0.1.4"
2996
4351
source = "registry+https://github.com/rust-lang/crates.io-index"
2997
-
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
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"
4380
+
version = "0.7.1"
4381
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4382
+
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
2998
4383
2999
4384
[[package]]
3000
4385
name = "untrusted"
···
3004
4389
3005
4390
[[package]]
3006
4391
name = "url"
3007
-
version = "2.5.4"
4392
+
version = "2.5.7"
3008
4393
source = "registry+https://github.com/rust-lang/crates.io-index"
3009
-
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
4394
+
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
3010
4395
dependencies = [
3011
4396
"form_urlencoded",
3012
4397
"idna",
3013
4398
"percent-encoding",
4399
+
"serde",
3014
4400
]
3015
4401
3016
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]]
3017
4415
name = "utf8_iter"
3018
4416
version = "1.0.4"
3019
4417
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3063
4461
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3064
4462
3065
4463
[[package]]
3066
-
name = "wasi"
3067
-
version = "0.14.2+wasi-0.2.4"
4464
+
name = "wasip2"
4465
+
version = "1.0.1+wasi-0.2.4"
3068
4466
source = "registry+https://github.com/rust-lang/crates.io-index"
3069
-
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
4467
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
3070
4468
dependencies = [
3071
-
"wit-bindgen-rt",
4469
+
"wit-bindgen",
3072
4470
]
3073
4471
3074
4472
[[package]]
···
3079
4477
3080
4478
[[package]]
3081
4479
name = "wasm-bindgen"
3082
-
version = "0.2.100"
4480
+
version = "0.2.106"
3083
4481
source = "registry+https://github.com/rust-lang/crates.io-index"
3084
-
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
4482
+
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
3085
4483
dependencies = [
3086
4484
"cfg-if",
3087
4485
"once_cell",
3088
4486
"rustversion",
3089
4487
"wasm-bindgen-macro",
4488
+
"wasm-bindgen-shared",
3090
4489
]
3091
4490
3092
4491
[[package]]
3093
-
name = "wasm-bindgen-backend"
3094
-
version = "0.2.100"
4492
+
name = "wasm-bindgen-futures"
4493
+
version = "0.4.56"
3095
4494
source = "registry+https://github.com/rust-lang/crates.io-index"
3096
-
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
4495
+
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
3097
4496
dependencies = [
3098
-
"bumpalo",
3099
-
"log",
3100
-
"proc-macro2",
3101
-
"quote",
3102
-
"syn",
3103
-
"wasm-bindgen-shared",
4497
+
"cfg-if",
4498
+
"js-sys",
4499
+
"once_cell",
4500
+
"wasm-bindgen",
4501
+
"web-sys",
3104
4502
]
3105
4503
3106
4504
[[package]]
3107
4505
name = "wasm-bindgen-macro"
3108
-
version = "0.2.100"
4506
+
version = "0.2.106"
3109
4507
source = "registry+https://github.com/rust-lang/crates.io-index"
3110
-
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
4508
+
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
3111
4509
dependencies = [
3112
4510
"quote",
3113
4511
"wasm-bindgen-macro-support",
···
3115
4513
3116
4514
[[package]]
3117
4515
name = "wasm-bindgen-macro-support"
3118
-
version = "0.2.100"
4516
+
version = "0.2.106"
3119
4517
source = "registry+https://github.com/rust-lang/crates.io-index"
3120
-
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
4518
+
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
3121
4519
dependencies = [
4520
+
"bumpalo",
3122
4521
"proc-macro2",
3123
4522
"quote",
3124
-
"syn",
3125
-
"wasm-bindgen-backend",
4523
+
"syn 2.0.112",
3126
4524
"wasm-bindgen-shared",
3127
4525
]
3128
4526
3129
4527
[[package]]
3130
4528
name = "wasm-bindgen-shared"
3131
-
version = "0.2.100"
4529
+
version = "0.2.106"
3132
4530
source = "registry+https://github.com/rust-lang/crates.io-index"
3133
-
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
4531
+
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
3134
4532
dependencies = [
3135
4533
"unicode-ident",
3136
4534
]
3137
4535
3138
4536
[[package]]
3139
4537
name = "web-sys"
3140
-
version = "0.3.77"
4538
+
version = "0.3.83"
3141
4539
source = "registry+https://github.com/rust-lang/crates.io-index"
3142
-
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
4540
+
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
3143
4541
dependencies = [
3144
4542
"js-sys",
3145
4543
"wasm-bindgen",
···
3161
4559
source = "registry+https://github.com/rust-lang/crates.io-index"
3162
4560
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
3163
4561
dependencies = [
3164
-
"webpki-roots 1.0.2",
4562
+
"webpki-roots 1.0.5",
3165
4563
]
3166
4564
3167
4565
[[package]]
3168
4566
name = "webpki-roots"
3169
-
version = "1.0.2"
4567
+
version = "1.0.5"
3170
4568
source = "registry+https://github.com/rust-lang/crates.io-index"
3171
-
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
4569
+
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
3172
4570
dependencies = [
3173
4571
"rustls-pki-types",
3174
4572
]
···
3201
4599
3202
4600
[[package]]
3203
4601
name = "winapi-util"
3204
-
version = "0.1.9"
4602
+
version = "0.1.11"
3205
4603
source = "registry+https://github.com/rust-lang/crates.io-index"
3206
-
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
4604
+
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
3207
4605
dependencies = [
3208
-
"windows-sys 0.59.0",
4606
+
"windows-sys 0.48.0",
3209
4607
]
3210
4608
3211
4609
[[package]]
···
3216
4614
3217
4615
[[package]]
3218
4616
name = "windows-core"
3219
-
version = "0.61.2"
4617
+
version = "0.62.2"
3220
4618
source = "registry+https://github.com/rust-lang/crates.io-index"
3221
-
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
4619
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
3222
4620
dependencies = [
3223
4621
"windows-implement",
3224
4622
"windows-interface",
···
3229
4627
3230
4628
[[package]]
3231
4629
name = "windows-implement"
3232
-
version = "0.60.0"
4630
+
version = "0.60.2"
3233
4631
source = "registry+https://github.com/rust-lang/crates.io-index"
3234
-
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
4632
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
3235
4633
dependencies = [
3236
4634
"proc-macro2",
3237
4635
"quote",
3238
-
"syn",
4636
+
"syn 2.0.112",
3239
4637
]
3240
4638
3241
4639
[[package]]
3242
4640
name = "windows-interface"
3243
-
version = "0.59.1"
4641
+
version = "0.59.3"
3244
4642
source = "registry+https://github.com/rust-lang/crates.io-index"
3245
-
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
4643
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
3246
4644
dependencies = [
3247
4645
"proc-macro2",
3248
4646
"quote",
3249
-
"syn",
4647
+
"syn 2.0.112",
3250
4648
]
3251
4649
3252
4650
[[package]]
3253
4651
name = "windows-link"
3254
-
version = "0.1.3"
4652
+
version = "0.2.1"
3255
4653
source = "registry+https://github.com/rust-lang/crates.io-index"
3256
-
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
4654
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4655
+
4656
+
[[package]]
4657
+
name = "windows-registry"
4658
+
version = "0.6.1"
4659
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4660
+
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
4661
+
dependencies = [
4662
+
"windows-link",
4663
+
"windows-result",
4664
+
"windows-strings",
4665
+
]
3257
4666
3258
4667
[[package]]
3259
4668
name = "windows-result"
3260
-
version = "0.3.4"
4669
+
version = "0.4.1"
3261
4670
source = "registry+https://github.com/rust-lang/crates.io-index"
3262
-
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
4671
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
3263
4672
dependencies = [
3264
4673
"windows-link",
3265
4674
]
3266
4675
3267
4676
[[package]]
3268
4677
name = "windows-strings"
3269
-
version = "0.4.2"
4678
+
version = "0.5.1"
3270
4679
source = "registry+https://github.com/rust-lang/crates.io-index"
3271
-
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
4680
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
3272
4681
dependencies = [
3273
4682
"windows-link",
3274
4683
]
···
3301
4710
]
3302
4711
3303
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]]
3304
4731
name = "windows-targets"
3305
4732
version = "0.48.5"
3306
4733
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3324
4751
"windows_aarch64_gnullvm 0.52.6",
3325
4752
"windows_aarch64_msvc 0.52.6",
3326
4753
"windows_i686_gnu 0.52.6",
3327
-
"windows_i686_gnullvm",
4754
+
"windows_i686_gnullvm 0.52.6",
3328
4755
"windows_i686_msvc 0.52.6",
3329
4756
"windows_x86_64_gnu 0.52.6",
3330
4757
"windows_x86_64_gnullvm 0.52.6",
···
3332
4759
]
3333
4760
3334
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]]
3335
4779
name = "windows_aarch64_gnullvm"
3336
4780
version = "0.48.5"
3337
4781
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3344
4788
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
3345
4789
3346
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]]
3347
4797
name = "windows_aarch64_msvc"
3348
4798
version = "0.48.5"
3349
4799
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3356
4806
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
3357
4807
3358
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]]
3359
4815
name = "windows_i686_gnu"
3360
4816
version = "0.48.5"
3361
4817
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3368
4824
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
3369
4825
3370
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]]
3371
4833
name = "windows_i686_gnullvm"
3372
4834
version = "0.52.6"
3373
4835
source = "registry+https://github.com/rust-lang/crates.io-index"
3374
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"
3375
4843
3376
4844
[[package]]
3377
4845
name = "windows_i686_msvc"
···
3386
4854
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
3387
4855
3388
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]]
3389
4863
name = "windows_x86_64_gnu"
3390
4864
version = "0.48.5"
3391
4865
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3398
4872
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
3399
4873
3400
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]]
3401
4881
name = "windows_x86_64_gnullvm"
3402
4882
version = "0.48.5"
3403
4883
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3410
4890
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
3411
4891
3412
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]]
3413
4899
name = "windows_x86_64_msvc"
3414
4900
version = "0.48.5"
3415
4901
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3422
4908
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
3423
4909
3424
4910
[[package]]
3425
-
name = "wit-bindgen-rt"
3426
-
version = "0.39.0"
4911
+
name = "windows_x86_64_msvc"
4912
+
version = "0.53.1"
3427
4913
source = "registry+https://github.com/rust-lang/crates.io-index"
3428
-
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
3429
-
dependencies = [
3430
-
"bitflags",
3431
-
]
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"
3432
4921
3433
4922
[[package]]
3434
4923
name = "writeable"
3435
-
version = "0.6.1"
4924
+
version = "0.6.2"
3436
4925
source = "registry+https://github.com/rust-lang/crates.io-index"
3437
-
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
4926
+
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
4927
+
4928
+
[[package]]
4929
+
name = "yansi"
4930
+
version = "1.0.1"
4931
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4932
+
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
3438
4933
3439
4934
[[package]]
3440
4935
name = "yoke"
3441
-
version = "0.8.0"
4936
+
version = "0.8.1"
3442
4937
source = "registry+https://github.com/rust-lang/crates.io-index"
3443
-
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
4938
+
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
3444
4939
dependencies = [
3445
-
"serde",
3446
4940
"stable_deref_trait",
3447
4941
"yoke-derive",
3448
4942
"zerofrom",
···
3450
4944
3451
4945
[[package]]
3452
4946
name = "yoke-derive"
3453
-
version = "0.8.0"
4947
+
version = "0.8.1"
3454
4948
source = "registry+https://github.com/rust-lang/crates.io-index"
3455
-
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
4949
+
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
3456
4950
dependencies = [
3457
4951
"proc-macro2",
3458
4952
"quote",
3459
-
"syn",
4953
+
"syn 2.0.112",
3460
4954
"synstructure",
3461
4955
]
3462
4956
3463
4957
[[package]]
3464
4958
name = "zerocopy"
3465
-
version = "0.8.26"
4959
+
version = "0.8.31"
3466
4960
source = "registry+https://github.com/rust-lang/crates.io-index"
3467
-
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
4961
+
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
3468
4962
dependencies = [
3469
4963
"zerocopy-derive",
3470
4964
]
3471
4965
3472
4966
[[package]]
3473
4967
name = "zerocopy-derive"
3474
-
version = "0.8.26"
4968
+
version = "0.8.31"
3475
4969
source = "registry+https://github.com/rust-lang/crates.io-index"
3476
-
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
4970
+
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
3477
4971
dependencies = [
3478
4972
"proc-macro2",
3479
4973
"quote",
3480
-
"syn",
4974
+
"syn 2.0.112",
3481
4975
]
3482
4976
3483
4977
[[package]]
···
3497
4991
dependencies = [
3498
4992
"proc-macro2",
3499
4993
"quote",
3500
-
"syn",
4994
+
"syn 2.0.112",
3501
4995
"synstructure",
3502
4996
]
3503
4997
3504
4998
[[package]]
3505
4999
name = "zeroize"
3506
-
version = "1.8.1"
5000
+
version = "1.8.2"
3507
5001
source = "registry+https://github.com/rust-lang/crates.io-index"
3508
-
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
5002
+
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
3509
5003
dependencies = [
3510
5004
"zeroize_derive",
3511
5005
]
3512
5006
3513
5007
[[package]]
3514
5008
name = "zeroize_derive"
3515
-
version = "1.4.2"
5009
+
version = "1.4.3"
3516
5010
source = "registry+https://github.com/rust-lang/crates.io-index"
3517
-
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
5011
+
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
3518
5012
dependencies = [
3519
5013
"proc-macro2",
3520
5014
"quote",
3521
-
"syn",
5015
+
"syn 2.0.112",
3522
5016
]
3523
5017
3524
5018
[[package]]
3525
5019
name = "zerotrie"
3526
-
version = "0.2.2"
5020
+
version = "0.2.3"
3527
5021
source = "registry+https://github.com/rust-lang/crates.io-index"
3528
-
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
5022
+
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
3529
5023
dependencies = [
3530
5024
"displaydoc",
3531
5025
"yoke",
···
3534
5028
3535
5029
[[package]]
3536
5030
name = "zerovec"
3537
-
version = "0.11.4"
5031
+
version = "0.11.5"
3538
5032
source = "registry+https://github.com/rust-lang/crates.io-index"
3539
-
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
5033
+
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
3540
5034
dependencies = [
3541
5035
"yoke",
3542
5036
"zerofrom",
···
3545
5039
3546
5040
[[package]]
3547
5041
name = "zerovec-derive"
3548
-
version = "0.11.1"
5042
+
version = "0.11.2"
3549
5043
source = "registry+https://github.com/rust-lang/crates.io-index"
3550
-
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
5044
+
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
3551
5045
dependencies = [
3552
5046
"proc-macro2",
3553
5047
"quote",
3554
-
"syn",
5048
+
"syn 2.0.112",
3555
5049
]
3556
5050
3557
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]]
3558
5058
name = "zstd"
3559
5059
version = "0.13.3"
3560
5060
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3574
5074
3575
5075
[[package]]
3576
5076
name = "zstd-sys"
3577
-
version = "2.0.15+zstd.1.5.7"
5077
+
version = "2.0.16+zstd.1.5.7"
3578
5078
source = "registry+https://github.com/rust-lang/crates.io-index"
3579
-
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
5079
+
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
3580
5080
dependencies = [
3581
5081
"cc",
3582
5082
"pkg-config",
+23
-10
Cargo.toml
+23
-10
Cargo.toml
···
1
1
[package]
2
2
name = "pds_gatekeeper"
3
-
version = "0.1.0"
3
+
version = "0.1.2"
4
4
edition = "2024"
5
+
license = "MIT"
5
6
6
7
[dependencies]
7
-
axum = { version = "0.8.4", features = ["macros", "json"] }
8
-
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "signal"] }
8
+
axum = { version = "0.8.8", features = ["macros", "json"] }
9
+
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal"] }
9
10
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] }
10
11
dotenvy = "0.15.7"
11
12
serde = { version = "1.0", features = ["derive"] }
12
13
serde_json = "1.0"
13
14
tracing = "0.1"
14
15
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
15
-
hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] }
16
+
hyper-util = { version = "0.1.19", features = ["client", "client-legacy"] }
16
17
tower-http = { version = "0.6", features = ["cors", "compression-zstd"] }
17
-
tower_governor = "0.8.0"
18
+
tower_governor = { version = "0.8.0", features = ["axum", "tracing"] }
18
19
hex = "0.4"
19
20
jwt-compact = { version = "0.8.0", features = ["es256k"] }
20
21
scrypt = "0.11"
21
-
lettre = { version = "0.11.18", features = ["tokio1", "pool", "tokio1-native-tls"] }
22
-
handlebars = { version = "6.3.2", features = ["rust-embed"] }
23
-
rust-embed = "8.7.2"
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"
24
28
axum-template = { version = "3.0.0", features = ["handlebars"] }
25
29
rand = "0.9.2"
26
-
anyhow = "1.0.99"
27
-
chrono = "0.4.41"
30
+
anyhow = "1.0.100"
31
+
chrono = { version = "0.4.42", features = ["default", "serde"] }
28
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"
+10
Dockerfile
+10
Dockerfile
···
1
+
FROM rust:1.89.0-bookworm AS builder
2
+
WORKDIR /app
3
+
COPY ../ /app
4
+
RUN cargo build --release
5
+
#
6
+
FROM rust:1.89-slim-bookworm AS api
7
+
RUN apt-get update
8
+
RUN apt-get install -y ca-certificates
9
+
COPY --from=builder /app/target/release/pds_gatekeeper /usr/local/bin/pds_gatekeeper
10
+
CMD ["pds_gatekeeper"]
+21
LICENSE.md
+21
LICENSE.md
···
1
+
MIT License
2
+
3
+
Copyright (c) 2025 Bailey Townsend
4
+
5
+
Permission is hereby granted, free of charge, to any person obtaining a copy
6
+
of this software and associated documentation files (the "Software"), to deal
7
+
in the Software without restriction, including without limitation the rights
8
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+
copies of the Software, and to permit persons to whom the Software is
10
+
furnished to do so, subject to the following conditions:
11
+
12
+
The above copyright notice and this permission notice shall be included in all
13
+
copies or substantial portions of the Software.
14
+
15
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+
SOFTWARE.
+171
-12
README.md
+171
-12
README.md
···
15
15
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
16
16
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17
17
18
-
## Captcha on Create Account
18
+
## Captcha on account creation
19
19
20
-
Future feature?
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.
21
48
22
49
# Setup
23
50
24
-
We are getting close! Testing now
51
+
PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be
52
+
assuming you setup the PDS following the directions
53
+
found [here](https://atproto.com/guides/self-hosting), but if yours is different, or you have questions, feel free to
54
+
let
55
+
me know, and we can figure it out.
56
+
57
+
## Docker compose
58
+
59
+
The pds gatekeeper container can be found on docker hub under the name `fatfingers23/pds_gatekeeper`. The container does
60
+
need access to the `/pds` root folder to access the same db's as your PDS. The part you need to add would look a bit
61
+
like below. You can find a full example of what I use for my pds at [./examples/compose.yml](./examples/compose.yml).
62
+
This is usually found at `/pds/compose.yaml`on your PDS>
63
+
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
71
+
volumes:
72
+
- type: bind
73
+
source: /pds
74
+
target: /pds
75
+
depends_on:
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
+
```
25
123
26
-
Nothing here yet! If you are brave enough to try before full release, let me know and I'll help you set it up.
27
-
But I want to run it locally on my own PDS first to test run it a bit.
124
+
## Caddy setup
28
125
29
-
Example Caddyfile (mostly so I don't lose it for now. Will have a better one in the future)
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.
30
129
31
-
```caddyfile
32
-
http://localhost {
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
+
}
33
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 {
34
153
@gatekeeper {
35
154
path /xrpc/com.atproto.server.getSession
155
+
path /xrpc/com.atproto.server.describeServer
36
156
path /xrpc/com.atproto.server.updateEmail
37
157
path /xrpc/com.atproto.server.createSession
158
+
path /xrpc/com.atproto.server.createAccount
38
159
path /@atproto/oauth-provider/~api/sign-in
160
+
path /gate/*
39
161
}
40
162
41
163
handle @gatekeeper {
42
-
reverse_proxy http://localhost:8080
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}
43
168
}
169
+
reverse_proxy http://localhost:3000
170
+
}
171
+
172
+
```
173
+
174
+
# Environment variables and bonuses
175
+
176
+
Every environment variable can be set in the `pds.env` and shared between PDS and gatekeeper and the PDS, with the
177
+
exception of `PDS_ENV_LOCATION`. This can be set to load the pds.env, by default it checks `/pds/pds.env` and is
178
+
recommended to mount the `/pds` folder on the server to `/pds` in the pds gatekeeper container.
179
+
180
+
`PDS_DATA_DIRECTORY` - Root directory of the PDS. Same as the one found in `pds.env` this is how pds gatekeeper knows
181
+
knows the rest of the environment variables.
182
+
183
+
`GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY` - The folder for templates of the emails PDS gatekeeper sends. You can find them
184
+
in [./email_templates](./email_templates). You are free to edit them as you please and set this variable to a location
185
+
in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the
186
+
same.
44
187
45
-
reverse_proxy /* http://localhost:3000
46
-
}
188
+
`GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT` - Subject of the email sent to the user when they turn on 2FA. Defaults to
189
+
`Sign in to Bluesky`
190
+
191
+
`PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default
192
+
193
+
`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1`
194
+
195
+
`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080`
47
196
48
-
```
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
+
+30
examples/Caddyfile
+30
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.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
+
}
+51
examples/compose.yml
+51
examples/compose.yml
···
1
+
version: '3.9'
2
+
services:
3
+
caddy:
4
+
container_name: caddy
5
+
image: caddy:2
6
+
network_mode: host
7
+
depends_on:
8
+
- pds
9
+
restart: unless-stopped
10
+
volumes:
11
+
- type: bind
12
+
source: /pds/caddy/data
13
+
target: /data
14
+
- type: bind
15
+
source: /pds/caddy/etc/caddy
16
+
target: /etc/caddy
17
+
pds:
18
+
container_name: pds
19
+
image: ghcr.io/bluesky-social/pds:0.4
20
+
network_mode: host
21
+
restart: unless-stopped
22
+
volumes:
23
+
- type: bind
24
+
source: /pds
25
+
target: /pds
26
+
env_file:
27
+
- /pds/pds.env
28
+
watchtower:
29
+
container_name: watchtower
30
+
image: containrrr/watchtower:latest
31
+
network_mode: host
32
+
volumes:
33
+
- type: bind
34
+
source: /var/run/docker.sock
35
+
target: /var/run/docker.sock
36
+
restart: unless-stopped
37
+
environment:
38
+
WATCHTOWER_CLEANUP: true
39
+
WATCHTOWER_SCHEDULE: "@midnight"
40
+
gatekeeper:
41
+
container_name: gatekeeper
42
+
image: fatfingers23/pds_gatekeeper: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
46
+
volumes:
47
+
- type: bind
48
+
source: /pds
49
+
target: /pds
50
+
depends_on:
51
+
- pds
+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>
+6
justfile
+6
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
+
}
+177
-14
src/helpers.rs
+177
-14
src/helpers.rs
···
1
1
use crate::AppState;
2
2
use crate::helpers::TokenCheckError::InvalidToken;
3
3
use anyhow::anyhow;
4
-
use axum::body::{Body, to_bytes};
5
-
use axum::extract::Request;
6
-
use axum::http::header::CONTENT_TYPE;
7
-
use axum::http::{HeaderMap, StatusCode, Uri};
8
-
use axum::response::{IntoResponse, Response};
4
+
use axum::{
5
+
body::{Body, to_bytes},
6
+
extract::Request,
7
+
http::header::CONTENT_TYPE,
8
+
http::{HeaderMap, StatusCode, Uri},
9
+
response::{IntoResponse, Response},
10
+
};
9
11
use axum_template::TemplateEngine;
10
12
use chrono::Utc;
11
-
use lettre::message::{MultiPart, SinglePart, header};
12
-
use lettre::{AsyncTransport, Message};
13
+
use jacquard_common::{
14
+
service_auth, service_auth::PublicKey, types::did::Did, types::did_doc::VerificationMethod,
15
+
types::nsid::Nsid,
16
+
};
17
+
use jacquard_identity::{PublicResolver, resolver::IdentityResolver};
18
+
use josekit::jwe::alg::direct::DirectJweAlgorithm;
19
+
use lettre::{
20
+
AsyncTransport, Message,
21
+
message::{MultiPart, SinglePart, header},
22
+
};
13
23
use rand::Rng;
14
24
use serde::de::DeserializeOwned;
15
25
use serde_json::{Map, Value};
16
26
use sha2::{Digest, Sha256};
17
27
use sqlx::SqlitePool;
28
+
use std::sync::Arc;
18
29
use tracing::{error, log};
19
30
20
31
///Used to generate the email 2fa code
···
39
50
where
40
51
T: DeserializeOwned,
41
52
{
42
-
let uri = format!("{}{}", state.pds_base_url, path);
53
+
let uri = format!("{}{}", state.app_config.pds_base_url, path);
43
54
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
44
55
45
56
let result = state
···
134
145
full_code.push(UPPERCASE_BASE32_CHARS[idx] as char);
135
146
}
136
147
137
-
//The PDS implementation creates in lowercase, then converts to uppercase.
138
-
//Just going a head and doing uppercase here.
139
-
let slice_one = &full_code[0..5].to_ascii_uppercase();
140
-
let slice_two = &full_code[5..10].to_ascii_uppercase();
148
+
let slice_one = &full_code[0..5];
149
+
let slice_two = &full_code[5..10];
141
150
format!("{slice_one}-{slice_two}")
142
151
}
143
152
···
337
346
338
347
let email_message = Message::builder()
339
348
//TODO prob get the proper type in the state
340
-
.from(state.mailer_from.parse()?)
349
+
.from(state.app_config.mailer_from.parse()?)
341
350
.to(email.parse()?)
342
-
.subject("Sign in to Bluesky")
351
+
.subject(&state.app_config.email_subject)
343
352
.multipart(
344
353
MultiPart::alternative() // This is composed of two parts.
345
354
.singlepart(
···
522
531
523
532
format!("{masked_local}@{masked_domain}")
524
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
+
}
+214
-35
src/main.rs
+214
-35
src/main.rs
···
1
1
#![warn(clippy::unwrap_used)]
2
+
use crate::gate::{get_gate, post_gate};
2
3
use crate::oauth_provider::sign_in;
3
-
use crate::xrpc::com_atproto_server::{create_session, get_session, update_email};
4
-
use axum::body::Body;
5
-
use axum::handler::Handler;
6
-
use axum::http::{Method, header};
7
-
use axum::middleware as ax_middleware;
8
-
use axum::routing::post;
9
-
use axum::{Router, routing::get};
4
+
use crate::xrpc::com_atproto_server::{
5
+
create_account, create_session, describe_server, get_session, update_email,
6
+
};
7
+
use axum::{
8
+
Router,
9
+
body::Body,
10
+
handler::Handler,
11
+
http::{Method, header},
12
+
middleware as ax_middleware,
13
+
routing::get,
14
+
routing::post,
15
+
};
10
16
use axum_template::engine::Engine;
11
17
use handlebars::Handlebars;
12
-
use hyper_util::client::legacy::connect::HttpConnector;
13
-
use hyper_util::rt::TokioExecutor;
18
+
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
19
+
use jacquard_common::types::did::Did;
20
+
use jacquard_identity::{PublicResolver, resolver::PlcSource};
14
21
use lettre::{AsyncSmtpTransport, Tokio1Executor};
22
+
use rand::Rng;
15
23
use rust_embed::RustEmbed;
16
24
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
17
25
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
18
26
use std::path::Path;
27
+
use std::sync::Arc;
19
28
use std::time::Duration;
20
29
use std::{env, net::SocketAddr};
21
-
use tower_governor::GovernorLayer;
22
-
use tower_governor::governor::GovernorConfigBuilder;
23
-
use tower_http::compression::CompressionLayer;
24
-
use tower_http::cors::{Any, CorsLayer};
30
+
use tower_governor::{
31
+
GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
32
+
};
33
+
use tower_http::{
34
+
compression::CompressionLayer,
35
+
cors::{Any, CorsLayer},
36
+
};
25
37
use tracing::log;
26
38
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
27
39
40
+
mod auth;
41
+
mod gate;
28
42
pub mod helpers;
29
43
mod middleware;
30
44
mod oauth_provider;
···
37
51
#[include = "*.hbs"]
38
52
struct EmailTemplates;
39
53
54
+
#[derive(RustEmbed)]
55
+
#[folder = "html_templates"]
56
+
#[include = "*.hbs"]
57
+
struct HtmlTemplates;
58
+
59
+
/// Mostly the env variables that are used in the app
60
+
#[derive(Clone, Debug)]
61
+
pub struct AppConfig {
62
+
pds_base_url: String,
63
+
mailer_from: String,
64
+
email_subject: String,
65
+
allow_only_migrations: bool,
66
+
use_captcha: bool,
67
+
//The url to redirect to after a successful captcha. Defaults to https://bsky.app, but you may have another social-app fork you rather your users use
68
+
//that need to capture this redirect url for creating an account
69
+
default_successful_redirect_url: String,
70
+
pds_service_did: Did<'static>,
71
+
gate_jwe_key: Vec<u8>,
72
+
captcha_success_redirects: Vec<String>,
73
+
}
74
+
75
+
impl AppConfig {
76
+
pub fn new() -> Self {
77
+
let pds_base_url =
78
+
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
79
+
let mailer_from = env::var("PDS_EMAIL_FROM_ADDRESS")
80
+
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
81
+
//Hack not my favorite, but it does work
82
+
let allow_only_migrations = env::var("GATEKEEPER_ALLOW_ONLY_MIGRATIONS")
83
+
.map(|val| val.parse::<bool>().unwrap_or(false))
84
+
.unwrap_or(false);
85
+
86
+
let use_captcha = env::var("GATEKEEPER_CREATE_ACCOUNT_CAPTCHA")
87
+
.map(|val| val.parse::<bool>().unwrap_or(false))
88
+
.unwrap_or(false);
89
+
90
+
// PDS_SERVICE_DID is the did:web if set, if not it's PDS_HOSTNAME
91
+
let pds_service_did =
92
+
env::var("PDS_SERVICE_DID").unwrap_or_else(|_| match env::var("PDS_HOSTNAME") {
93
+
Ok(pds_hostname) => format!("did:web:{}", pds_hostname),
94
+
Err(_) => {
95
+
panic!("PDS_HOSTNAME or PDS_SERVICE_DID must be set in your pds.env file")
96
+
}
97
+
});
98
+
99
+
let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT")
100
+
.unwrap_or("Sign in to Bluesky".to_string());
101
+
102
+
// Load or generate JWE encryption key (32 bytes for AES-256)
103
+
let gate_jwe_key = env::var("GATEKEEPER_JWE_KEY")
104
+
.ok()
105
+
.and_then(|key_hex| hex::decode(key_hex).ok())
106
+
.unwrap_or_else(|| {
107
+
// Generate a random 32-byte key if not provided
108
+
let key: Vec<u8> = (0..32).map(|_| rand::rng().random()).collect();
109
+
log::warn!("WARNING: No GATEKEEPER_JWE_KEY found in the environment. Generated random key (hex): {}", hex::encode(&key));
110
+
log::warn!("This is not strictly needed unless you scale PDS Gatekeeper. Will not also be able to verify tokens between reboots, but they are short lived (5mins).");
111
+
key
112
+
});
113
+
114
+
if gate_jwe_key.len() != 32 {
115
+
panic!(
116
+
"GATEKEEPER_JWE_KEY must be 32 bytes (64 hex characters) for AES-256 encryption"
117
+
);
118
+
}
119
+
120
+
let captcha_success_redirects = match env::var("GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS") {
121
+
Ok(from_env) => from_env.split(",").map(|s| s.trim().to_string()).collect(),
122
+
Err(_) => {
123
+
vec![
124
+
String::from("https://bsky.app"),
125
+
String::from("https://pdsmoover.com"),
126
+
String::from("https://blacksky.community"),
127
+
String::from("https://tektite.cc"),
128
+
]
129
+
}
130
+
};
131
+
132
+
AppConfig {
133
+
pds_base_url,
134
+
mailer_from,
135
+
email_subject,
136
+
allow_only_migrations,
137
+
use_captcha,
138
+
default_successful_redirect_url: env::var("GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT")
139
+
.unwrap_or("https://bsky.app".to_string()),
140
+
pds_service_did: pds_service_did
141
+
.parse()
142
+
.expect("PDS_SERVICE_DID is not a valid did or could not infer from PDS_HOSTNAME"),
143
+
gate_jwe_key,
144
+
captcha_success_redirects,
145
+
}
146
+
}
147
+
}
148
+
40
149
#[derive(Clone)]
41
150
pub struct AppState {
42
151
account_pool: SqlitePool,
43
152
pds_gatekeeper_pool: SqlitePool,
44
153
reverse_proxy_client: HyperUtilClient,
45
-
pds_base_url: String,
46
154
mailer: AsyncSmtpTransport<Tokio1Executor>,
47
-
mailer_from: String,
48
155
template_engine: Engine<Handlebars<'static>>,
156
+
resolver: Arc<PublicResolver>,
157
+
handle_cache: auth::HandleCache,
158
+
app_config: AppConfig,
49
159
}
50
160
51
161
async fn root_handler() -> impl axum::response::IntoResponse {
···
88
198
#[tokio::main]
89
199
async fn main() -> Result<(), Box<dyn std::error::Error>> {
90
200
setup_tracing();
91
-
//TODO may need to change where this reads from? Like an env variable for it's location? Or arg?
92
-
dotenvy::from_path(Path::new("./pds.env"))?;
93
-
let pds_root = env::var("PDS_DATA_DIRECTORY")?;
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");
94
213
let account_db_url = format!("{pds_root}/account.sqlite");
95
214
96
215
let account_options = SqliteConnectOptions::new()
···
127
246
//Emailer set up
128
247
let smtp_url =
129
248
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
130
-
let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS")
131
-
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
249
+
132
250
let mailer: AsyncSmtpTransport<Tokio1Executor> =
133
251
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
134
252
//Email templates setup
···
144
262
let _ = hbs.register_embed_templates::<EmailTemplates>();
145
263
}
146
264
147
-
let pds_base_url =
148
-
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
265
+
let _ = hbs.register_embed_templates::<HtmlTemplates>();
266
+
267
+
//Reads the PLC source from the pds env's or defaults to ol faithful
268
+
let plc_source_url =
269
+
env::var("PDS_DID_PLC_URL").unwrap_or_else(|_| "https://plc.directory".to_string());
270
+
let plc_source = PlcSource::PlcDirectory {
271
+
base: plc_source_url.parse().unwrap(),
272
+
};
273
+
let mut resolver = PublicResolver::default();
274
+
resolver = resolver.with_plc_source(plc_source.clone());
149
275
150
276
let state = AppState {
151
277
account_pool,
152
278
pds_gatekeeper_pool,
153
279
reverse_proxy_client: client,
154
-
pds_base_url,
155
280
mailer,
156
-
mailer_from: sent_from,
157
281
template_engine: Engine::from(hbs),
282
+
resolver: Arc::new(resolver),
283
+
handle_cache: auth::HandleCache::new(),
284
+
app_config: AppConfig::new(),
158
285
};
159
286
160
287
// Rate limiting
161
288
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
162
-
let create_session_governor_conf = GovernorConfigBuilder::default()
289
+
let captcha_governor_conf = GovernorConfigBuilder::default()
163
290
.per_second(60)
164
291
.burst_size(5)
292
+
.key_extractor(SmartIpKeyExtractor)
165
293
.finish()
166
-
.expect("failed to create governor config. this should not happen and is a bug");
294
+
.expect("failed to create governor config for create session. this should not happen and is a bug");
167
295
168
296
// Create a second config with the same settings for the other endpoint
169
297
let sign_in_governor_conf = GovernorConfigBuilder::default()
170
298
.per_second(60)
171
299
.burst_size(5)
300
+
.key_extractor(SmartIpKeyExtractor)
172
301
.finish()
173
-
.expect("failed to create governor config. this should not happen and is a bug");
302
+
.expect(
303
+
"failed to create governor config for sign in. this should not happen and is a bug",
304
+
);
174
305
175
-
let create_session_governor_limiter = create_session_governor_conf.limiter().clone();
306
+
let create_account_limiter_time: Option<String> =
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();
176
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
+
177
341
let interval = Duration::from_secs(60);
178
342
// a separate background task to clean up
179
343
std::thread::spawn(move || {
180
344
loop {
181
345
std::thread::sleep(interval);
182
-
create_session_governor_limiter.retain_recent();
346
+
captcha_governor_limiter.retain_recent();
183
347
sign_in_governor_limiter.retain_recent();
348
+
create_account_governor_limiter.retain_recent();
184
349
}
185
350
});
186
351
···
189
354
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
190
355
.allow_headers(Any);
191
356
192
-
let app = Router::new()
357
+
let mut app = Router::new()
193
358
.route("/", get(root_handler))
359
+
.route("/xrpc/com.atproto.server.getSession", get(get_session))
194
360
.route(
195
-
"/xrpc/com.atproto.server.getSession",
196
-
get(get_session).layer(ax_middleware::from_fn(middleware::extract_did)),
361
+
"/xrpc/com.atproto.server.describeServer",
362
+
get(describe_server),
197
363
)
198
364
.route(
199
365
"/xrpc/com.atproto.server.updateEmail",
···
201
367
)
202
368
.route(
203
369
"/@atproto/oauth-provider/~api/sign-in",
204
-
post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)),
370
+
post(sign_in).layer(sign_in_governor_layer.clone()),
205
371
)
206
372
.route(
207
373
"/xrpc/com.atproto.server.createSession",
208
-
post(create_session.layer(GovernorLayer::new(create_session_governor_conf))),
374
+
post(create_session.layer(sign_in_governor_layer)),
209
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
210
389
.layer(CompressionLayer::new())
211
390
.layer(cors)
212
391
.with_state(state);
213
392
214
-
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
393
+
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
215
394
let port: u16 = env::var("GATEKEEPER_PORT")
216
395
.ok()
217
396
.and_then(|s| s.parse().ok())
+73
-39
src/middleware.rs
+73
-39
src/middleware.rs
···
12
12
#[derive(Clone, Debug)]
13
13
pub struct Did(pub Option<String>);
14
14
15
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16
+
pub enum AuthScheme {
17
+
Bearer,
18
+
DPoP,
19
+
}
20
+
15
21
#[derive(Serialize, Deserialize)]
16
22
pub struct TokenClaims {
17
23
pub sub: String,
18
24
}
19
25
20
26
pub async fn extract_did(mut req: Request, next: Next) -> impl IntoResponse {
21
-
let token = extract_bearer(req.headers());
27
+
let auth = extract_auth(req.headers());
22
28
23
-
match token {
24
-
Ok(token) => {
25
-
match token {
29
+
match auth {
30
+
Ok(auth_opt) => {
31
+
match auth_opt {
26
32
None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
27
33
.expect("Error creating an error response"),
28
-
Some(token) => {
29
-
let token = UntrustedToken::new(&token);
30
-
if token.is_err() {
31
-
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
32
-
.expect("Error creating an error response");
33
-
}
34
-
let parsed_token = token.expect("Already checked for error");
35
-
let claims: Result<Claims<TokenClaims>, ValidationError> =
36
-
parsed_token.deserialize_claims_unchecked();
37
-
if claims.is_err() {
38
-
return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
39
-
.expect("Error creating an error response");
40
-
}
34
+
Some((scheme, token_str)) => {
35
+
// For Bearer, validate JWT and extract DID from `sub`.
36
+
// For DPoP, we currently only pass through and do not validate here; insert None DID.
37
+
match scheme {
38
+
AuthScheme::Bearer => {
39
+
let token = UntrustedToken::new(&token_str);
40
+
if token.is_err() {
41
+
return json_error_response(
42
+
StatusCode::BAD_REQUEST,
43
+
"TokenRequired",
44
+
"",
45
+
)
46
+
.expect("Error creating an error response");
47
+
}
48
+
let parsed_token = token.expect("Already checked for error");
49
+
let claims: Result<Claims<TokenClaims>, ValidationError> =
50
+
parsed_token.deserialize_claims_unchecked();
51
+
if claims.is_err() {
52
+
return json_error_response(
53
+
StatusCode::BAD_REQUEST,
54
+
"TokenRequired",
55
+
"",
56
+
)
57
+
.expect("Error creating an error response");
58
+
}
41
59
42
-
let key = Hs256Key::new(
43
-
env::var("PDS_JWT_SECRET").expect("PDS_JWT_SECRET not set in the pds.env"),
44
-
);
45
-
let token: Result<Token<TokenClaims>, ValidationError> =
46
-
Hs256.validator(&key).validate(&parsed_token);
47
-
if token.is_err() {
48
-
return json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "")
49
-
.expect("Error creating an error response");
60
+
let key = Hs256Key::new(
61
+
env::var("PDS_JWT_SECRET")
62
+
.expect("PDS_JWT_SECRET not set in the pds.env"),
63
+
);
64
+
let token: Result<Token<TokenClaims>, ValidationError> =
65
+
Hs256.validator(&key).validate(&parsed_token);
66
+
if token.is_err() {
67
+
return json_error_response(
68
+
StatusCode::BAD_REQUEST,
69
+
"InvalidToken",
70
+
"",
71
+
)
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
+
}
78
+
AuthScheme::DPoP => {
79
+
//Not going to worry about oauth email update for now, just always forward to the PDS
80
+
req.extensions_mut().insert(Did(None));
81
+
}
50
82
}
51
-
let token = token.expect("Already checked for error,");
52
-
//Not going to worry about expiration since it still goes to the PDS
53
-
req.extensions_mut()
54
-
.insert(Did(Some(token.claims().custom.sub.clone())));
83
+
55
84
next.run(req).await
56
85
}
57
86
}
···
64
93
}
65
94
}
66
95
67
-
fn extract_bearer(headers: &HeaderMap) -> Result<Option<String>, String> {
96
+
fn extract_auth(headers: &HeaderMap) -> Result<Option<(AuthScheme, String)>, String> {
68
97
match headers.get(axum::http::header::AUTHORIZATION) {
69
98
None => Ok(None),
70
-
Some(hv) => match hv.to_str() {
71
-
Err(_) => Err("Authorization header is not valid".into()),
72
-
Ok(s) => {
73
-
// Accept forms like: "Bearer <token>" (case-sensitive for the scheme here)
74
-
let mut parts = s.splitn(2, ' ');
75
-
match (parts.next(), parts.next()) {
76
-
(Some("Bearer"), Some(tok)) if !tok.is_empty() => Ok(Some(tok.to_string())),
77
-
_ => Err("Authorization header must be in format 'Bearer <token>'".into()),
99
+
Some(hv) => {
100
+
match hv.to_str() {
101
+
Err(_) => Err("Authorization header is not valid".into()),
102
+
Ok(s) => {
103
+
// Accept forms like: "Bearer <token>" or "DPoP <token>" (case-sensitive for the scheme here)
104
+
let mut parts = s.splitn(2, ' ');
105
+
match (parts.next(), parts.next()) {
106
+
(Some("Bearer"), Some(tok)) if !tok.is_empty() =>
107
+
Ok(Some((AuthScheme::Bearer, tok.to_string()))),
108
+
(Some("DPoP"), Some(tok)) if !tok.is_empty() =>
109
+
Ok(Some((AuthScheme::DPoP, tok.to_string()))),
110
+
_ => Err("Authorization header must be in format 'Bearer <token>' or 'DPoP <token>'".into()),
111
+
}
78
112
}
79
113
}
80
-
},
114
+
}
81
115
}
82
116
}
+4
-6
src/oauth_provider.rs
+4
-6
src/oauth_provider.rs
···
13
13
pub struct SignInRequest {
14
14
pub username: String,
15
15
pub password: String,
16
-
pub remember: bool,
16
+
#[serde(skip_serializing_if = "Option::is_none")]
17
+
pub remember: Option<bool>,
17
18
pub locale: String,
18
19
#[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")]
19
20
pub email_otp: Option<String>,
···
36
37
"Invalid identifier or password",
37
38
),
38
39
AuthResult::TwoFactorRequired(masked_email) => {
39
-
// Email sending step can be handled here if needed in the future.
40
-
41
-
// {"error":"second_authentication_factor_required","error_description":"emailOtp authentication factor required (hint: 2***0@p***m)","type":"emailOtp","hint":"2***0@p***m"}
42
40
let body_str = match serde_json::to_string(&serde_json::json!({
43
41
"error": "second_authentication_factor_required",
44
42
"error_description": format!("emailOtp authentication factor required (hint: {})", masked_email),
···
59
57
//No 2FA or already passed
60
58
let uri = format!(
61
59
"{}{}",
62
-
state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
+
state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
63
61
);
64
62
65
63
let mut req = axum::http::Request::post(uri);
···
97
95
},
98
96
Err(err) => {
99
97
log::error!(
100
-
"Error during pre-auth check. This happens on the create_session endpoint when trying to decide if the user has access:\n {err}"
98
+
"Error during pre-auth check. This happens on the oauth signin endpoint when trying to decide if the user has access:\n {err}"
101
99
);
102
100
oauth_json_error_response(
103
101
StatusCode::BAD_REQUEST,
+394
-53
src/xrpc/com_atproto_server.rs
+394
-53
src/xrpc/com_atproto_server.rs
···
1
1
use crate::AppState;
2
2
use crate::helpers::{
3
-
AuthResult, ProxiedResult, TokenCheckError, json_error_response, preauth_check, proxy_get_json,
3
+
AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response,
4
+
preauth_check, proxy_get_json, verify_gate_token, verify_service_auth,
4
5
};
5
6
use crate::middleware::Did;
6
-
use axum::body::Body;
7
+
use axum::body::{Body, to_bytes};
7
8
use axum::extract::State;
8
-
use axum::http::{HeaderMap, StatusCode};
9
+
use axum::http::{HeaderMap, StatusCode, header};
9
10
use axum::response::{IntoResponse, Response};
10
11
use axum::{Extension, Json, debug_handler, extract, extract::Request};
12
+
use chrono::{Duration, Utc};
13
+
use jacquard_common::types::did::Did as JacquardDid;
11
14
use serde::{Deserialize, Serialize};
12
15
use serde_json;
13
16
use tracing::log;
···
61
64
allow_takendown: Option<bool>,
62
65
}
63
66
67
+
#[derive(Deserialize, Serialize, Debug)]
68
+
#[serde(rename_all = "camelCase")]
69
+
pub struct CreateAccountRequest {
70
+
handle: String,
71
+
#[serde(skip_serializing_if = "Option::is_none")]
72
+
email: Option<String>,
73
+
#[serde(skip_serializing_if = "Option::is_none")]
74
+
password: Option<String>,
75
+
#[serde(skip_serializing_if = "Option::is_none")]
76
+
did: Option<String>,
77
+
#[serde(skip_serializing_if = "Option::is_none")]
78
+
invite_code: Option<String>,
79
+
#[serde(skip_serializing_if = "Option::is_none")]
80
+
verification_code: Option<String>,
81
+
#[serde(skip_serializing_if = "Option::is_none")]
82
+
plc_op: Option<serde_json::Value>,
83
+
}
84
+
85
+
#[derive(Deserialize, Serialize, Debug, Clone)]
86
+
#[serde(rename_all = "camelCase")]
87
+
pub struct DescribeServerContact {
88
+
#[serde(skip_serializing_if = "Option::is_none")]
89
+
email: Option<String>,
90
+
}
91
+
92
+
#[derive(Deserialize, Serialize, Debug, Clone)]
93
+
#[serde(rename_all = "camelCase")]
94
+
pub struct DescribeServerLinks {
95
+
#[serde(skip_serializing_if = "Option::is_none")]
96
+
privacy_policy: Option<String>,
97
+
#[serde(skip_serializing_if = "Option::is_none")]
98
+
terms_of_service: Option<String>,
99
+
}
100
+
101
+
#[derive(Deserialize, Serialize, Debug, Clone)]
102
+
#[serde(rename_all = "camelCase")]
103
+
pub struct DescribeServerResponse {
104
+
#[serde(skip_serializing_if = "Option::is_none")]
105
+
invite_code_required: Option<bool>,
106
+
#[serde(skip_serializing_if = "Option::is_none")]
107
+
phone_verification_required: Option<bool>,
108
+
#[serde(skip_serializing_if = "Option::is_none")]
109
+
available_user_domains: Option<Vec<String>>,
110
+
#[serde(skip_serializing_if = "Option::is_none")]
111
+
links: Option<DescribeServerLinks>,
112
+
#[serde(skip_serializing_if = "Option::is_none")]
113
+
contact: Option<DescribeServerContact>,
114
+
#[serde(skip_serializing_if = "Option::is_none")]
115
+
did: Option<String>,
116
+
}
117
+
64
118
pub async fn create_session(
65
119
State(state): State<AppState>,
66
120
headers: HeaderMap,
···
87
141
)
88
142
}
89
143
AuthResult::ProxyThrough => {
90
-
log::info!("Proxying through");
91
144
//No 2FA or already passed
92
145
let uri = format!(
93
146
"{}{}",
94
-
state.pds_base_url, "/xrpc/com.atproto.server.createSession"
147
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession"
95
148
);
96
149
97
150
let mut req = axum::http::Request::post(uri);
···
148
201
//If email auth is set it is to either turn on or off 2fa
149
202
let email_auth_update = payload.email_auth_factor.unwrap_or(false);
150
203
151
-
// Email update asked for
152
-
if email_auth_update {
153
-
let email = payload.email.clone();
154
-
let email_confirmed = sqlx::query_as::<_, (String,)>(
155
-
"SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?",
156
-
)
157
-
.bind(&email)
158
-
.fetch_optional(&state.account_pool)
159
-
.await
160
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
161
-
162
-
//Since the email is already confirmed we can enable 2fa
163
-
return match email_confirmed {
164
-
None => Err(StatusCode::BAD_REQUEST),
165
-
Some(did_row) => {
166
-
let _ = sqlx::query(
167
-
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1",
168
-
)
169
-
.bind(&did_row.0)
170
-
.execute(&state.pds_gatekeeper_pool)
171
-
.await
172
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
173
-
174
-
Ok(StatusCode::OK.into_response())
175
-
}
176
-
};
177
-
}
204
+
//This means the middleware successfully extracted a did from the request, if not it just needs to be forward to the PDS
205
+
//This is also empty if it is an oauth request, which is not supported by gatekeeper turning on 2fa since the dpop stuff needs to be implemented
206
+
let did_is_not_empty = did.0.is_some();
178
207
179
-
// User wants auth turned off
180
-
if !email_auth_update && !email_auth_not_set {
181
-
//User wants auth turned off and has a token
182
-
if let Some(token) = &payload.token {
183
-
let token_found = sqlx::query_as::<_, (String,)>(
184
-
"SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'",
208
+
if did_is_not_empty {
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 = ?",
185
214
)
186
-
.bind(token)
187
-
.bind(&did.0)
215
+
.bind(&email)
188
216
.fetch_optional(&state.account_pool)
189
217
.await
190
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
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
+
};
191
225
192
-
if token_found.is_some() {
193
-
let _ = sqlx::query(
194
-
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0",
226
+
//Since the email is already confirmed we can enable 2fa
227
+
return match email_confirmed {
228
+
None => Err(StatusCode::BAD_REQUEST),
229
+
Some(did_row) => {
230
+
let _ = sqlx::query(
231
+
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1",
232
+
)
233
+
.bind(&did_row.0)
234
+
.execute(&state.pds_gatekeeper_pool)
235
+
.await
236
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
237
+
238
+
Ok(StatusCode::OK.into_response())
239
+
}
240
+
};
241
+
}
242
+
243
+
// User wants auth turned off
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'",
195
249
)
196
-
.bind(&did.0)
197
-
.execute(&state.pds_gatekeeper_pool)
198
-
.await
199
-
.map_err(|_| StatusCode::BAD_REQUEST)?;
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
+
};
200
260
201
-
return Ok(StatusCode::OK.into_response());
202
-
} else {
203
-
return Err(StatusCode::BAD_REQUEST);
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 {
279
+
Err(StatusCode::BAD_REQUEST)
280
+
};
204
281
}
205
282
}
206
283
}
207
-
208
284
// Updating the actual email address by sending it on to the PDS
209
285
let uri = format!(
210
286
"{}{}",
211
-
state.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
287
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
212
288
);
213
289
let mut req = axum::http::Request::post(uri);
214
290
if let Some(req_headers) = req.headers_mut() {
···
260
336
ProxiedResult::Passthrough(resp) => Ok(resp),
261
337
}
262
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
+
}