Microservice to bring 2FA to self hosted PDSes

Compare changes

Choose any two refs to compare.

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