From d9666e0c7159c5ab83d010b4d2f5ff0d742a67c4 Mon Sep 17 00:00:00 2001 From: Clinton Bowen Date: Fri, 2 Jan 2026 18:06:46 -0800 Subject: [PATCH] Adding an auth module with all the scopes, handles, and did checking goodies that can be used as middleware layers in axum inspired from my attribute macros elsewhere. example ``` // Multi-community endpoint .route("/xrpc/community.shared.moderation.report", post(report).layer(from_fn_with_state( with_rules(AuthRules::All(vec![ AuthRules::HandleEndsWithAny(vec![ ".blacksky.team".into(), ".bsky.team".into(), ".mod.social".into(), ]), AuthRules::ScopeEquals("atproto".into()), ]), &state), auth_middleware ))) ``` --- AUTH.md | 279 ++++++++ Cargo.lock | 1364 ++++++++++++++++++++++------------------ Cargo.toml | 20 +- src/auth/cache.rs | 101 +++ src/auth/middleware.rs | 456 ++++++++++++++ src/auth/mod.rs | 16 + src/auth/rules.rs | 694 ++++++++++++++++++++ src/main.rs | 3 + 8 files changed, 2319 insertions(+), 614 deletions(-) create mode 100644 AUTH.md create mode 100644 src/auth/cache.rs create mode 100644 src/auth/middleware.rs create mode 100644 src/auth/mod.rs create mode 100644 src/auth/rules.rs diff --git a/AUTH.md b/AUTH.md new file mode 100644 index 0000000..9cefea4 --- /dev/null +++ b/AUTH.md @@ -0,0 +1,279 @@ +# PDS Gatekeeper Authentication Middleware + +This document describes the authentication middleware system for pds-gatekeeper, which provides flexible authorization rules based on DIDs, handles, and OAuth scopes. + +## Overview + +The auth middleware validates incoming requests by: + +1. **Extracting** the DID and scopes from a JWT Bearer token +2. **Resolving** the DID to a handle using jacquard-identity +3. **Validating** against configured authorization rules +4. **Returning** appropriate HTTP errors (401/403) on failure + +## Quick Start + +```rust +use axum::middleware::from_fn_with_state; +use crate::auth::{auth_middleware, handle_ends_with, scope_equals, with_rules, AuthRules}; + +let app = Router::new() + // Simple: require handle from specific domain + .route("/xrpc/community.blacksky.feed.get", + get(handler).layer(from_fn_with_state( + handle_ends_with(".blacksky.team", &state), + auth_middleware + ))) + + // Simple: require specific OAuth scope + .route("/xrpc/com.atproto.repo.createRecord", + post(handler).layer(from_fn_with_state( + scope_equals("repo:app.bsky.feed.post", &state), + auth_middleware + ))) + + .with_state(state); +``` + +## ATProto OAuth Scopes Reference + +| Scope | Description | +|-------|-------------| +| `atproto` | Base scope, required for all OAuth clients | +| `transition:generic` | Full repository access (equivalent to app passwords) | +| `repo:` | Access to specific collection (e.g., `repo:app.bsky.feed.post`) | +| `identity:handle` | Permits handle changes | +| `identity:*` | Full DID document control | +| `account:email` | Read email addresses | +| `account:repo?action=manage` | Import repository data | +| `blob:*/*` | Upload any blob type | +| `blob?accept=image/*` | Upload only images | + +See [Marvin's Guide to OAuth Scopes](https://marvins-guide.leaflet.pub/3mbfvey7sok26) for complete details. + +## Helper Functions + +### Identity Helpers + +| Function | Description | +|----------|-------------| +| `handle_ends_with(suffix, state)` | Handle must end with suffix | +| `handle_ends_with_any(suffixes, state)` | Handle must end with any suffix (OR) | +| `did_equals(did, state)` | DID must match exactly | +| `did_equals_any(dids, state)` | DID must match any value (OR) | + +### Scope Helpers + +| Function | Description | +|----------|-------------| +| `scope_equals(scope, state)` | Must have specific scope | +| `scope_any(scopes, state)` | Must have any of the scopes (OR) | +| `scope_all(scopes, state)` | Must have all scopes (AND) | + +### Combined Helpers (Identity + Scope) + +| Function | Description | +|----------|-------------| +| `handle_ends_with_and_scope(suffix, scope, state)` | Handle suffix AND scope | +| `handle_ends_with_and_scopes(suffix, scopes, state)` | Handle suffix AND all scopes | +| `did_with_scope(did, scope, state)` | DID match AND scope | +| `did_with_scopes(did, scopes, state)` | DID match AND all scopes | + +### Custom Rules + +For complex authorization logic, use `with_rules()`: + +```rust +with_rules(AuthRules::Any(vec![ + AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()), + AuthRules::All(vec![ + AuthRules::HandleEndsWith(".mod.team".into()), + AuthRules::ScopeEquals("account:email".into()), + ]), +]), &state) +``` + +## Realistic PDS Endpoint Examples + +### Admin Endpoints + +Based on `com.atproto.admin.*` endpoints from the ATProto PDS: + +```rust +// com.atproto.admin.deleteAccount +// Admin-only: specific DID with full access scope +.route("/xrpc/com.atproto.admin.deleteAccount", + post(delete_account).layer(from_fn_with_state( + did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "transition:generic", &state), + auth_middleware + ))) + +// com.atproto.admin.getAccountInfo +// Either admin DID OR (moderator handle + account scope) +.route("/xrpc/com.atproto.admin.getAccountInfo", + get(get_account_info).layer(from_fn_with_state( + with_rules(AuthRules::Any(vec![ + AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()), + AuthRules::All(vec![ + AuthRules::HandleEndsWith(".mod.team".into()), + AuthRules::ScopeEquals("account:email".into()), + ]), + ]), &state), + auth_middleware + ))) + +// com.atproto.admin.updateAccountEmail +// Admin DID with account management scope +.route("/xrpc/com.atproto.admin.updateAccountEmail", + post(update_email).layer(from_fn_with_state( + did_with_scopes( + "did:plc:rnpkyqnmsw4ipey6eotbdnnf", + ["account:email", "account:repo?action=manage"], + &state + ), + auth_middleware + ))) + +// com.atproto.admin.updateAccountHandle +// Admin with identity control +.route("/xrpc/com.atproto.admin.updateAccountHandle", + post(update_handle).layer(from_fn_with_state( + did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "identity:*", &state), + auth_middleware + ))) +``` + +### Repository Endpoints + +```rust +// com.atproto.repo.createRecord +// Scoped write access to specific collection +.route("/xrpc/com.atproto.repo.createRecord", + post(create_record).layer(from_fn_with_state( + scope_equals("repo:app.bsky.feed.post", &state), + auth_middleware + ))) + +// com.atproto.repo.putRecord +// Either specific collection scope OR full access +.route("/xrpc/com.atproto.repo.putRecord", + post(put_record).layer(from_fn_with_state( + scope_any(["repo:app.bsky.feed.post", "transition:generic"], &state), + auth_middleware + ))) + +// com.atproto.repo.uploadBlob +// Blob upload with media type restriction (scope-based) +.route("/xrpc/com.atproto.repo.uploadBlob", + post(upload_blob).layer(from_fn_with_state( + scope_any(["blob:*/*", "blob?accept=image/*", "transition:generic"], &state), + auth_middleware + ))) +``` + +### Community/Custom Endpoints + +```rust +// Community feed generator - restricted to team members with full access +.route("/xrpc/community.blacksky.feed.generator", + post(generator).layer(from_fn_with_state( + handle_ends_with_and_scope(".blacksky.team", "transition:generic", &state), + auth_middleware + ))) + +// Multi-community endpoint +.route("/xrpc/community.shared.moderation.report", + post(report).layer(from_fn_with_state( + with_rules(AuthRules::All(vec![ + AuthRules::HandleEndsWithAny(vec![ + ".blacksky.team".into(), + ".bsky.team".into(), + ".mod.social".into(), + ]), + AuthRules::ScopeEquals("atproto".into()), + ]), &state), + auth_middleware + ))) + +// VIP access - specific DIDs only +.route("/xrpc/community.blacksky.vip.access", + get(vip_handler).layer(from_fn_with_state( + did_equals_any([ + "did:plc:rnpkyqnmsw4ipey6eotbdnnf", + "did:plc:abc123def456ghi789jklmno", + "did:plc:xyz987uvw654rst321qponml", + ], &state), + auth_middleware + ))) +``` + +## Building Complex Authorization Rules + +The `AuthRules` enum supports arbitrary nesting: + +```rust +// Complex: Admin OR (Team member with write scope) OR (Moderator with read-only) +let rules = AuthRules::Any(vec![ + // Admin bypass + AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()), + + // Team member with write access + AuthRules::All(vec![ + AuthRules::HandleEndsWith(".blacksky.team".into()), + AuthRules::ScopeEquals("transition:generic".into()), + ]), + + // Moderator with limited scope + AuthRules::All(vec![ + AuthRules::HandleEndsWith(".mod.team".into()), + AuthRules::ScopeEqualsAny(vec![ + "account:email".into(), + "atproto".into(), + ]), + ]), +]); +``` + +## Error Responses + +| Status | Error Code | Description | +|--------|------------|-------------| +| `401` | `AuthRequired` | No Authorization header provided | +| `401` | `InvalidToken` | JWT validation failed (expired, invalid signature, malformed) | +| `403` | `AccessDenied` | Valid authentication but authorization rules rejected | +| `500` | `ResolutionError` | Failed to resolve DID to handle | + +Response format: +```json +{ + "error": "AccessDenied", + "message": "Access denied by authorization rules" +} +``` + +## JWT Token Format + +The middleware expects JWT tokens with these claims: + +```json +{ + "sub": "did:plc:rnpkyqnmsw4ipey6eotbdnnf", + "scope": "atproto transition:generic repo:app.bsky.feed.post", + "iat": 1704067200, + "exp": 1704153600 +} +``` + +- `sub` (required): The user's DID +- `scope` (optional): Space-separated OAuth scopes per [RFC 6749](https://tools.ietf.org/html/rfc6749) + +## Handle Resolution + +DIDs are resolved to handles using the jacquard-identity `PublicResolver`: + +1. Check the `HandleCache` for a cached result +2. If miss, resolve the DID document via PLC directory +3. Extract handle from `alsoKnownAs` field (format: `at://handle.example.com`) +4. Cache the result (1 hour TTL default) + +This allows rules like `HandleEndsWith(".blacksky.team")` to work even though the JWT only contains the DID. diff --git a/Cargo.lock b/Cargo.lock index 78826d9..1485c5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,15 +21,6 @@ dependencies = [ "nom 7.1.3", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -50,9 +41,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -80,23 +71,30 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" dependencies = [ - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] @@ -107,7 +105,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -119,6 +117,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -133,9 +140,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.3" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -144,11 +151,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.30.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" dependencies = [ - "bindgen", "cc", "cmake", "dunce", @@ -157,9 +163,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "axum-macros", @@ -177,8 +183,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -192,9 +197,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -203,7 +208,6 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -218,7 +222,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -230,22 +234,7 @@ dependencies = [ "axum", "handlebars", "serde", - "thiserror 2.0.14", -] - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", + "thiserror 2.0.17", ] [[package]] @@ -278,40 +267,17 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.105", - "which", -] +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -345,7 +311,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -359,9 +325,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "serde", @@ -393,9 +359,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -405,9 +371,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -423,10 +389,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.32" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -441,20 +408,11 @@ dependencies = [ "slab", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -473,7 +431,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -538,25 +496,42 @@ dependencies = [ ] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "cmake" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ - "glob", - "libc", - "libloading", + "cc", ] [[package]] -name = "cmake" -version = "0.1.54" +name = "cobs" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "cc", + "thiserror 2.0.17", ] +[[package]] +name = "compression-codecs" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +dependencies = [ + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -578,6 +553,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -614,9 +599,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -636,6 +621,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -671,9 +662,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -710,7 +701,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -724,7 +715,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -735,7 +726,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -746,7 +737,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -786,7 +777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -828,7 +819,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -838,9 +829,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.105", + "syn 2.0.112", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + [[package]] name = "digest" version = "0.10.7" @@ -861,7 +879,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -941,6 +959,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -958,9 +988,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1004,6 +1034,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "flate2" version = "1.1.5" @@ -1022,7 +1058,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "spin", + "spin 0.9.8", ] [[package]] @@ -1037,6 +1073,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1054,9 +1096,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1077,6 +1119,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures-buffered" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.0", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1121,6 +1176,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1129,7 +1197,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -1167,6 +1235,21 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1205,36 +1288,24 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "globset" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.13", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "governor" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19" +checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8" dependencies = [ "cfg-if", "dashmap", @@ -1242,7 +1313,7 @@ dependencies = [ "futures-timer", "futures-util", "getrandom 0.3.4", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "nonzero_ext", "parking_lot", "portable-atomic", @@ -1276,7 +1347,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -1285,19 +1356,20 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -1307,7 +1379,16 @@ dependencies = [ "rust-embed", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.17", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", ] [[package]] @@ -1334,7 +1415,18 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -1346,6 +1438,20 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1390,11 +1496,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1408,12 +1514,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1454,13 +1559,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -1468,6 +1574,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1487,7 +1594,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -1505,9 +1612,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", @@ -1531,9 +1638,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1555,9 +1662,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1568,9 +1675,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1581,11 +1688,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1596,42 +1702,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1647,9 +1749,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1679,13 +1781,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] @@ -1715,17 +1818,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "ipld-core" version = "0.4.2" @@ -1745,34 +1837,25 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jacquard-api" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbfd6e2b10fa1731f4d4e40c8f791956b0d4f804fb3efef891afec903f20597" +checksum = "4979fb1848c1dd7ac8fd12745bc71f56f6da61374407d5f9b06005467a954e5a" dependencies = [ "bon", "bytes", @@ -1782,16 +1865,17 @@ dependencies = [ "miette", "rustversion", "serde", + "serde_bytes", "serde_ipld_dagcbor", - "thiserror 2.0.14", + "thiserror 2.0.17", "unicode-segmentation", ] [[package]] name = "jacquard-common" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df86cb117d9f1c2b0251ba67c3f0e3f963fd22abc6cf8de0e02a7fc846c288ca" +checksum = "1751921e0bdae5e0077afade6161545e9ef7698306c868f800916e99ecbcaae9" dependencies = [ "base64", "bon", @@ -1809,17 +1893,19 @@ dependencies = [ "multihash", "ouroboros", "p256", + "postcard", "rand 0.9.2", "regex", "regex-lite", "reqwest", "serde", + "serde_bytes", "serde_html_form", "serde_ipld_dagcbor", "serde_json", "signature", "smol_str", - "thiserror 2.0.14", + "thiserror 2.0.17", "tokio", "tokio-util", "trait-variant", @@ -1828,22 +1914,22 @@ dependencies = [ [[package]] name = "jacquard-derive" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ca61a69dc7aa8fb2d7163416514ff7df5d79f2e8b22e269f4610afa85572fe" +checksum = "9c8d73dfee07943fdab93569ed1c28b06c6921ed891c08b415c4a323ff67e593" dependencies = [ "heck 0.5.0", "jacquard-lexicon", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "jacquard-identity" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef714cacebfca486558a9f8e205daf466bfba0466c4d0c450fd6d0252400a53" +checksum = "e7aaefa819fa4213cf59f180dba932f018a7cd0599582fd38474ee2a38c16cf2" dependencies = [ "bon", "bytes", @@ -1852,12 +1938,13 @@ dependencies = [ "jacquard-common", "jacquard-lexicon", "miette", + "n0-future", "percent-encoding", "reqwest", "serde", "serde_html_form", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.17", "tokio", "trait-variant", "url", @@ -1866,9 +1953,9 @@ dependencies = [ [[package]] name = "jacquard-lexicon" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de87f2c938faea1b1f1b32d5b9e0c870e7b5bb5efbf96e3692ae2d8f6b2beb7a" +checksum = "8411aff546569b0a1e0ef669bed2380cec1c00d48f02f3fcd57a71545321b3d8" dependencies = [ "cid", "dashmap", @@ -1886,16 +1973,16 @@ dependencies = [ "serde_repr", "serde_with", "sha2", - "syn 2.0.105", - "thiserror 2.0.14", + "syn 2.0.112", + "thiserror 2.0.17", "unicode-segmentation", ] [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.4", "libc", @@ -1914,15 +2001,15 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror 2.0.14", + "thiserror 2.0.17", "time", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1979,20 +2066,14 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.8", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lettre" -version = "0.11.18" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" +checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" dependencies = [ "async-trait", "base64", @@ -2013,24 +2094,14 @@ dependencies = [ "tokio", "tokio-rustls", "url", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] name = "libc" -version = "0.2.175" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" @@ -2040,13 +2111,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", - "redox_syscall", + "redox_syscall 0.7.0", ] [[package]] @@ -2060,33 +2131,39 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loom" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] [[package]] name = "lru-slab" @@ -2107,11 +2184,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -2132,9 +2209,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miette" @@ -2155,7 +2232,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -2182,13 +2259,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2214,6 +2291,27 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "n0-future" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + [[package]] name = "nom" version = "7.1.3" @@ -2247,21 +2345,19 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -2325,9 +2421,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -2361,7 +2457,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -2397,15 +2493,9 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.105", + "syn 2.0.112", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -2426,9 +2516,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2436,15 +2526,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2477,6 +2567,7 @@ dependencies = [ "axum", "axum-template", "chrono", + "dashmap", "dotenvy", "handlebars", "hex", @@ -2498,6 +2589,7 @@ dependencies = [ "sha2", "sqlx", "tokio", + "tower", "tower-http", "tower_governor", "tracing", @@ -2516,26 +2608,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", - "thiserror 2.0.14", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -2543,22 +2634,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", "sha2", @@ -2581,7 +2672,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -2625,15 +2716,28 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "postcard" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2655,12 +2759,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -2698,9 +2802,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -2713,17 +2817,18 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", "version_check", "yansi", ] [[package]] name = "psm" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" dependencies = [ + "ar_archive_writer", "cc", ] @@ -2753,10 +2858,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2", - "thiserror 2.0.14", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -2773,11 +2878,11 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.14", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -2799,9 +2904,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2885,18 +2990,27 @@ checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" [[package]] name = "raw-cpuid" -version = "11.5.0" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags", ] @@ -2918,7 +3032,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -2929,17 +3043,8 @@ checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.13", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2950,7 +3055,7 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -2961,28 +3066,20 @@ checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "async-compression", "base64", "bytes", "encoding_rs", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -3004,16 +3101,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -3042,9 +3137,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -3062,9 +3157,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -3073,40 +3168,28 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.105", + "syn 2.0.112", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" dependencies = [ "globset", "sha2", "walkdir", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -3114,23 +3197,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "0.38.44" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", + "semver", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "aws-lc-rs", "log", @@ -3144,9 +3223,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -3154,9 +3233,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -3172,9 +3251,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "salsa20" @@ -3208,9 +3287,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -3218,6 +3297,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3268,6 +3353,18 @@ dependencies = [ "cc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.228" @@ -3305,7 +3402,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3315,7 +3412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" dependencies = [ "form_urlencoded", - "indexmap 2.10.0", + "indexmap 2.12.1", "itoa", "ryu", "serde_core", @@ -3335,26 +3432,27 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.1", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -3365,7 +3463,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3382,17 +3480,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -3401,14 +3499,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3450,10 +3548,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3469,9 +3568,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" @@ -3500,12 +3599,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3517,6 +3616,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "spinning_top" version = "0.3.0" @@ -3568,7 +3673,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.12.1", "log", "memchr", "once_cell", @@ -3578,7 +3683,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.14", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -3596,7 +3701,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3619,7 +3724,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.105", + "syn 2.0.112", "tokio", "url", ] @@ -3662,7 +3767,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.14", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -3700,7 +3805,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.14", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -3725,22 +3830,22 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.14", + "thiserror 2.0.17", "tracing", "url", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", @@ -3765,7 +3870,7 @@ dependencies = [ "quote", "serde", "sha2", - "syn 2.0.105", + "syn 2.0.112", "thiserror 1.0.69", ] @@ -3811,9 +3916,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.105" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ "proc-macro2", "quote", @@ -3837,7 +3942,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3872,11 +3977,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.14", + "thiserror-impl 2.0.17", ] [[package]] @@ -3887,18 +3992,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -3943,9 +4048,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -3953,9 +4058,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -3968,39 +4073,36 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -4026,15 +4128,16 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] [[package]] name = "tonic" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", "axum", @@ -4067,7 +4170,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.10.0", + "indexmap 2.12.1", "pin-project-lite", "slab", "sync_wrapper", @@ -4080,9 +4183,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "bitflags", @@ -4091,6 +4194,7 @@ dependencies = [ "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", "tokio", @@ -4123,7 +4227,7 @@ dependencies = [ "governor", "http", "pin-project", - "thiserror 2.0.14", + "thiserror 2.0.17", "tonic", "tower", "tracing", @@ -4131,9 +4235,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4143,20 +4247,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4175,14 +4279,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -4199,7 +4303,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -4210,9 +4314,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -4228,24 +4332,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -4259,6 +4363,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsigned-varint" version = "0.8.0" @@ -4279,9 +4389,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -4367,35 +4477,22 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.105", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -4406,9 +4503,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4416,44 +4513,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.105", - "wasm-bindgen-backend", + "syn 2.0.112", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -4475,30 +4559,18 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "whoami" version = "1.6.1" @@ -4527,11 +4599,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -4542,45 +4614,39 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -4589,31 +4655,31 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -4643,6 +4709,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4667,13 +4751,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4686,6 +4787,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4698,6 +4805,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4710,12 +4823,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4728,6 +4853,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4740,6 +4871,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4752,6 +4889,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4764,6 +4907,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -4772,9 +4921,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yansi" @@ -4784,11 +4933,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4796,34 +4944,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] @@ -4843,35 +4991,35 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4880,9 +5028,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4891,15 +5039,21 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 2.0.112", ] +[[package]] +name = "zmij" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd" + [[package]] name = "zstd" version = "0.13.3" @@ -4920,9 +5074,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 9c4e19f..e811f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,35 +5,37 @@ edition = "2024" license = "MIT" [dependencies] -axum = { version = "0.8.4", features = ["macros", "json"] } -tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "signal"] } +axum = { version = "0.8.8", features = ["macros", "json"] } +tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal"] } sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] } dotenvy = "0.15.7" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } -hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] } +hyper-util = { version = "0.1.19", features = ["client", "client-legacy"] } tower-http = { version = "0.6", features = ["cors", "compression-zstd"] } tower_governor = { version = "0.8.0", features = ["axum", "tracing"] } hex = "0.4" jwt-compact = { version = "0.8.0", features = ["es256k"] } scrypt = "0.11" #Leaveing these two cause I think it is needed by the email crate for ssl -aws-lc-rs = "1.13.0" +aws-lc-rs = "1.15.2" rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] } lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] } -handlebars = { version = "6.3.2", features = ["rust-embed"] } -rust-embed = "8.7.2" +handlebars = { version = "6.4.0", features = ["rust-embed"] } +rust-embed = "8.9.0" axum-template = { version = "3.0.0", features = ["handlebars"] } rand = "0.9.2" -anyhow = "1.0.99" +anyhow = "1.0.100" chrono = { version = "0.4.42", features = ["default", "serde"] } sha2 = "0.10" -jacquard-common = "0.9.2" -jacquard-identity = "0.9.2" +jacquard-common = "0.9.5" +jacquard-identity = "0.9.5" multibase = "0.9.2" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } urlencoding = "2.1" html-escape = "0.2.13" josekit = "0.10.3" +dashmap = "6.1" +tower = "0.5" diff --git a/src/auth/cache.rs b/src/auth/cache.rs new file mode 100644 index 0000000..ff217fe --- /dev/null +++ b/src/auth/cache.rs @@ -0,0 +1,101 @@ +use dashmap::DashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +struct CachedHandle { + handle: String, + cached_at: Instant, +} + +/// A thread-safe cache for DID-to-handle resolutions with TTL expiration. +#[derive(Clone)] +pub struct HandleCache { + cache: Arc>, + ttl: Duration, +} + +impl Default for HandleCache { + fn default() -> Self { + Self::new() + } +} + +impl HandleCache { + /// Creates a new HandleCache with a default TTL of 1 hour. + pub fn new() -> Self { + Self::with_ttl(Duration::from_secs(3600)) + } + + /// Creates a new HandleCache with a custom TTL. + pub fn with_ttl(ttl: Duration) -> Self { + Self { + cache: Arc::new(DashMap::new()), + ttl, + } + } + + /// Gets a cached handle for the given DID, if it exists and hasn't expired. + pub fn get(&self, did: &str) -> Option { + let entry = self.cache.get(did)?; + if entry.cached_at.elapsed() > self.ttl { + drop(entry); + self.cache.remove(did); + return None; + } + Some(entry.handle.clone()) + } + + /// Inserts a DID-to-handle mapping into the cache. + pub fn insert(&self, did: String, handle: String) { + self.cache.insert( + did, + CachedHandle { + handle, + cached_at: Instant::now(), + }, + ); + } + + /// Removes expired entries from the cache. + /// Call this periodically to prevent memory growth. + pub fn cleanup(&self) { + self.cache.retain(|_, v| v.cached_at.elapsed() <= self.ttl); + } + + /// Returns the number of entries in the cache. + pub fn len(&self) -> usize { + self.cache.len() + } + + /// Returns true if the cache is empty. + pub fn is_empty(&self) -> bool { + self.cache.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cache_insert_and_get() { + let cache = HandleCache::new(); + cache.insert("did:plc:test".into(), "test.handle.com".into()); + assert_eq!(cache.get("did:plc:test"), Some("test.handle.com".into())); + } + + #[test] + fn test_cache_miss() { + let cache = HandleCache::new(); + assert_eq!(cache.get("did:plc:nonexistent"), None); + } + + #[test] + fn test_cache_expiration() { + let cache = HandleCache::with_ttl(Duration::from_millis(1)); + cache.insert("did:plc:test".into(), "test.handle.com".into()); + std::thread::sleep(Duration::from_millis(10)); + assert_eq!(cache.get("did:plc:test"), None); + } +} diff --git a/src/auth/middleware.rs b/src/auth/middleware.rs new file mode 100644 index 0000000..30d113d --- /dev/null +++ b/src/auth/middleware.rs @@ -0,0 +1,456 @@ +use super::{AuthRules, HandleCache, SessionData}; +use crate::helpers::json_error_response; +use crate::AppState; +use axum::extract::{Request, State}; +use axum::http::{HeaderMap, StatusCode}; +use axum::middleware::Next; +use axum::response::{IntoResponse, Response}; +use jacquard_identity::resolver::IdentityResolver; +use jacquard_identity::PublicResolver; +use jwt_compact::alg::{Hs256, Hs256Key}; +use jwt_compact::{AlgorithmExt, Claims, Token, UntrustedToken, ValidationError}; +use serde::{Deserialize, Serialize}; +use std::env; +use std::sync::Arc; +use tracing::log; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AuthScheme { + Bearer, + DPoP, +} + +#[derive(Serialize, Deserialize)] +pub struct TokenClaims { + pub sub: String, + /// OAuth scopes as space-separated string (per OAuth 2.0 spec) + #[serde(default)] + pub scope: Option, +} + +/// State passed to the auth middleware containing both AppState and auth rules. +#[derive(Clone)] +pub struct AuthMiddlewareState { + pub app_state: AppState, + pub rules: AuthRules, +} + +/// Core middleware function that validates authentication and applies auth rules. +/// +/// Use this with `axum::middleware::from_fn_with_state`: +/// ```ignore +/// use axum::middleware::from_fn_with_state; +/// +/// let mw_state = AuthMiddlewareState { +/// app_state: state.clone(), +/// rules: AuthRules::HandleEndsWith(".blacksky.team".into()), +/// }; +/// +/// .route("/protected", get(handler).layer(from_fn_with_state(mw_state, auth_middleware))) +/// ``` +pub async fn auth_middleware( + State(mw_state): State, + req: Request, + next: Next, +) -> Response { + let AuthMiddlewareState { app_state, rules } = mw_state; + + // 1. Extract DID and scopes from JWT (Bearer token) + let extracted = match extract_auth_from_request(req.headers()) { + Ok(Some(auth)) => auth, + Ok(None) => { + return json_error_response(StatusCode::UNAUTHORIZED, "AuthRequired", "Authentication required") + .unwrap_or_else(|_| StatusCode::UNAUTHORIZED.into_response()); + } + Err(e) => { + log::error!("Token extraction error: {}", e); + return json_error_response(StatusCode::UNAUTHORIZED, "InvalidToken", &e) + .unwrap_or_else(|_| StatusCode::UNAUTHORIZED.into_response()); + } + }; + + // 2. Resolve DID to handle (check cache first) + let handle = match resolve_did_to_handle(&app_state.resolver, &app_state.handle_cache, &extracted.did).await { + Ok(handle) => handle, + Err(e) => { + log::error!("Failed to resolve DID {} to handle: {}", extracted.did, e); + return json_error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "ResolutionError", + "Failed to resolve identity", + ) + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); + } + }; + + // 3. Build session data and validate rules + let session = SessionData { + did: extracted.did, + handle, + scopes: extracted.scopes, + }; + + if !rules.validate(&session) { + return json_error_response(StatusCode::FORBIDDEN, "AccessDenied", "Access denied by authorization rules") + .unwrap_or_else(|_| StatusCode::FORBIDDEN.into_response()); + } + + // 4. Pass through on success + next.run(req).await +} + +/// Extracted authentication data from JWT +struct ExtractedAuth { + did: String, + scopes: Vec, +} + +/// Extracts the DID and scopes from the Authorization header (Bearer JWT). +fn extract_auth_from_request(headers: &HeaderMap) -> Result, String> { + let auth = extract_auth(headers)?; + + match auth { + None => Ok(None), + Some((scheme, token_str)) => { + match scheme { + AuthScheme::Bearer => { + let token = UntrustedToken::new(&token_str) + .map_err(|_| "Invalid token format".to_string())?; + + let _claims: Claims = token + .deserialize_claims_unchecked() + .map_err(|_| "Failed to parse token claims".to_string())?; + + let key = Hs256Key::new( + env::var("PDS_JWT_SECRET") + .map_err(|_| "PDS_JWT_SECRET not configured".to_string())?, + ); + + let validated: Token = Hs256 + .validator(&key) + .validate(&token) + .map_err(|e: ValidationError| format!("Token validation failed: {:?}", e))?; + + let custom = &validated.claims().custom; + + // Parse scopes from space-separated string (OAuth 2.0 spec) + let scopes: Vec = custom.scope + .as_ref() + .map(|s| s.split_whitespace().map(|s| s.to_string()).collect()) + .unwrap_or_default(); + + Ok(Some(ExtractedAuth { + did: custom.sub.clone(), + scopes, + })) + } + AuthScheme::DPoP => { + // DPoP tokens are not validated here; pass through without auth data + Ok(None) + } + } + } + } +} + +/// Extracts the authentication scheme and token from the Authorization header. +fn extract_auth(headers: &HeaderMap) -> Result, String> { + match headers.get(axum::http::header::AUTHORIZATION) { + None => Ok(None), + Some(hv) => { + let s = hv + .to_str() + .map_err(|_| "Authorization header is not valid UTF-8".to_string())?; + + let mut parts = s.splitn(2, ' '); + match (parts.next(), parts.next()) { + (Some("Bearer"), Some(tok)) if !tok.is_empty() => { + Ok(Some((AuthScheme::Bearer, tok.to_string()))) + } + (Some("DPoP"), Some(tok)) if !tok.is_empty() => { + Ok(Some((AuthScheme::DPoP, tok.to_string()))) + } + _ => Err( + "Authorization header must be in format 'Bearer ' or 'DPoP '" + .to_string(), + ), + } + } + } +} + +/// Resolves a DID to its handle using the PublicResolver, with caching. +async fn resolve_did_to_handle( + resolver: &Arc, + cache: &HandleCache, + did: &str, +) -> Result { + // Check cache first + if let Some(handle) = cache.get(did) { + return Ok(handle); + } + + // Parse the DID + let did_parsed = jacquard_common::types::did::Did::new(did) + .map_err(|e| format!("Invalid DID: {:?}", e))?; + + // Resolve the DID document + let did_doc_response = resolver + .resolve_did_doc(&did_parsed) + .await + .map_err(|e| format!("DID resolution failed: {:?}", e))?; + + let doc = did_doc_response + .parse() + .map_err(|e| format!("Failed to parse DID document: {:?}", e))?; + + // Extract handle from alsoKnownAs field + // Format is typically: ["at://handle.example.com"] + let handle: String = doc + .also_known_as + .as_ref() + .and_then(|aka| { + aka.iter() + .find(|uri| uri.starts_with("at://")) + .map(|uri| uri.strip_prefix("at://").unwrap_or(uri.as_ref()).to_string()) + }) + .ok_or_else(|| "No ATProto handle found in DID document".to_string())?; + + // Cache the result + cache.insert(did.to_string(), handle.clone()); + + Ok(handle) +} + +// ============================================================================ +// Helper Functions for Creating Middleware State +// ============================================================================ + +/// Creates an `AuthMiddlewareState` for requiring the handle to end with a specific suffix. +/// +/// # Example +/// ```ignore +/// use axum::middleware::from_fn_with_state; +/// use crate::auth::{auth_middleware, handle_ends_with}; +/// +/// .route("/protected", get(handler).layer( +/// from_fn_with_state(handle_ends_with(".blacksky.team", &state), auth_middleware) +/// )) +/// ``` +pub fn handle_ends_with(suffix: impl Into, state: &AppState) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::HandleEndsWith(suffix.into()), + } +} + +/// Creates an `AuthMiddlewareState` for requiring the handle to end with any of the specified suffixes. +pub fn handle_ends_with_any(suffixes: I, state: &AppState) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::HandleEndsWithAny(suffixes.into_iter().map(|s| s.into()).collect()), + } +} + +/// Creates an `AuthMiddlewareState` for requiring the DID to equal a specific value. +pub fn did_equals(did: impl Into, state: &AppState) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::DidEquals(did.into()), + } +} + +/// Creates an `AuthMiddlewareState` for requiring the DID to be one of the specified values. +pub fn did_equals_any(dids: I, state: &AppState) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::DidEqualsAny(dids.into_iter().map(|d| d.into()).collect()), + } +} + +/// Creates an `AuthMiddlewareState` with custom auth rules. +pub fn with_rules(rules: AuthRules, state: &AppState) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules, + } +} + +// ============================================================================ +// Scope Helper Functions +// ============================================================================ + +/// Creates an `AuthMiddlewareState` requiring a specific OAuth scope. +/// +/// # Example +/// ```ignore +/// .route("/xrpc/com.atproto.repo.createRecord", +/// post(handler).layer(from_fn_with_state( +/// scope_equals("repo:app.bsky.feed.post", &state), +/// auth_middleware +/// ))) +/// ``` +pub fn scope_equals(scope: impl Into, state: &AppState) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::ScopeEquals(scope.into()), + } +} + +/// Creates an `AuthMiddlewareState` requiring ANY of the specified scopes (OR logic). +/// +/// # Example +/// ```ignore +/// .route("/xrpc/com.atproto.repo.putRecord", +/// post(handler).layer(from_fn_with_state( +/// scope_any(["repo:app.bsky.feed.post", "transition:generic"], &state), +/// auth_middleware +/// ))) +/// ``` +pub fn scope_any(scopes: I, state: &AppState) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::ScopeEqualsAny(scopes.into_iter().map(|s| s.into()).collect()), + } +} + +/// Creates an `AuthMiddlewareState` requiring ALL of the specified scopes (AND logic). +/// +/// # Example +/// ```ignore +/// .route("/xrpc/com.atproto.admin.updateAccount", +/// post(handler).layer(from_fn_with_state( +/// scope_all(["account:email", "account:repo?action=manage"], &state), +/// auth_middleware +/// ))) +/// ``` +pub fn scope_all(scopes: I, state: &AppState) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()), + } +} + +// ============================================================================ +// Combined Rule Helpers (Identity + Scope) +// ============================================================================ + +/// Creates an `AuthMiddlewareState` requiring handle to end with suffix AND have a specific scope. +/// +/// # Example +/// ```ignore +/// .route("/xrpc/community.blacksky.feed.generator", +/// post(handler).layer(from_fn_with_state( +/// handle_ends_with_and_scope(".blacksky.team", "transition:generic", &state), +/// auth_middleware +/// ))) +/// ``` +pub fn handle_ends_with_and_scope( + suffix: impl Into, + scope: impl Into, + state: &AppState, +) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::All(vec![ + AuthRules::HandleEndsWith(suffix.into()), + AuthRules::ScopeEquals(scope.into()), + ]), + } +} + +/// Creates an `AuthMiddlewareState` requiring handle to end with suffix AND have ALL specified scopes. +/// +/// # Example +/// ```ignore +/// .route("/xrpc/community.blacksky.admin.manage", +/// post(handler).layer(from_fn_with_state( +/// handle_ends_with_and_scopes(".blacksky.team", ["transition:generic", "identity:*"], &state), +/// auth_middleware +/// ))) +/// ``` +pub fn handle_ends_with_and_scopes( + suffix: impl Into, + scopes: I, + state: &AppState, +) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::All(vec![ + AuthRules::HandleEndsWith(suffix.into()), + AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()), + ]), + } +} + +/// Creates an `AuthMiddlewareState` requiring DID to equal value AND have a specific scope. +/// +/// # Example +/// ```ignore +/// .route("/xrpc/com.atproto.admin.deleteAccount", +/// post(handler).layer(from_fn_with_state( +/// did_with_scope("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "transition:generic", &state), +/// auth_middleware +/// ))) +/// ``` +pub fn did_with_scope( + did: impl Into, + scope: impl Into, + state: &AppState, +) -> AuthMiddlewareState { + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::All(vec![ + AuthRules::DidEquals(did.into()), + AuthRules::ScopeEquals(scope.into()), + ]), + } +} + +/// Creates an `AuthMiddlewareState` requiring DID to equal value AND have ALL specified scopes. +/// +/// # Example +/// ```ignore +/// .route("/xrpc/com.atproto.admin.fullAccess", +/// post(handler).layer(from_fn_with_state( +/// did_with_scopes("did:plc:rnpkyqnmsw4ipey6eotbdnnf", ["transition:generic", "identity:*"], &state), +/// auth_middleware +/// ))) +/// ``` +pub fn did_with_scopes( + did: impl Into, + scopes: I, + state: &AppState, +) -> AuthMiddlewareState +where + I: IntoIterator, + T: Into, +{ + AuthMiddlewareState { + app_state: state.clone(), + rules: AuthRules::All(vec![ + AuthRules::DidEquals(did.into()), + AuthRules::ScopeEqualsAll(scopes.into_iter().map(|s| s.into()).collect()), + ]), + } +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..8ec1362 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,16 @@ +mod cache; +mod middleware; +mod rules; + +pub use cache::HandleCache; +pub use middleware::{ + // Core middleware + auth_middleware, with_rules, AuthMiddlewareState, + // Identity helpers + did_equals, did_equals_any, handle_ends_with, handle_ends_with_any, + // Scope helpers + scope_all, scope_any, scope_equals, + // Combined helpers (identity + scope) + did_with_scope, did_with_scopes, handle_ends_with_and_scope, handle_ends_with_and_scopes, +}; +pub use rules::{AuthRules, SessionData}; diff --git a/src/auth/rules.rs b/src/auth/rules.rs new file mode 100644 index 0000000..fc0aa9d --- /dev/null +++ b/src/auth/rules.rs @@ -0,0 +1,694 @@ +/// Authentication rules that can be validated against session data +#[derive(Debug, Clone, PartialEq)] +pub enum AuthRules { + /// Handle must end with the specified suffix + HandleEndsWith(String), + /// Handle must end with any of the specified suffixes (OR logic) + HandleEndsWithAny(Vec), + /// DID must exactly match the specified value + DidEquals(String), + /// DID must match any of the specified values (OR logic) + DidEqualsAny(Vec), + /// Session must have the specified OAuth scope + ScopeEquals(String), + /// Session must have ANY of the specified scopes (OR logic) + ScopeEqualsAny(Vec), + /// Session must have ALL of the specified scopes (AND logic) + ScopeEqualsAll(Vec), + /// All nested rules must be satisfied (AND logic) + All(Vec), + /// At least one nested rule must be satisfied (OR logic) + Any(Vec), +} + +/// Session data used for authentication validation +#[derive(Debug, Clone)] +pub struct SessionData { + /// The user's DID + pub did: String, + /// The user's handle + pub handle: String, + /// OAuth 2.0 scopes granted to this session + pub scopes: Vec, +} + +impl AuthRules { + /// Validates if the given session data meets the authentication requirements + pub fn validate(&self, session_data: &SessionData) -> bool { + match self { + AuthRules::HandleEndsWith(suffix) => session_data.handle.ends_with(suffix), + AuthRules::HandleEndsWithAny(suffixes) => { + suffixes.iter().any(|s| session_data.handle.ends_with(s)) + } + AuthRules::DidEquals(did) => session_data.did == *did, + AuthRules::DidEqualsAny(dids) => dids.iter().any(|d| session_data.did == *d), + AuthRules::ScopeEquals(scope) => has_scope(&session_data.scopes, scope), + AuthRules::ScopeEqualsAny(scopes) => has_any_scope(&session_data.scopes, scopes), + AuthRules::ScopeEqualsAll(scopes) => has_all_scopes(&session_data.scopes, scopes), + AuthRules::All(rules) => rules.iter().all(|r| r.validate(session_data)), + AuthRules::Any(rules) => rules.iter().any(|r| r.validate(session_data)), + } + } +} + +/// Checks if the session has a specific scope +pub fn has_scope(scopes: &[String], required_scope: &str) -> bool { + scopes.iter().any(|s| s == required_scope) +} + +/// Checks if the session has ANY of the required scopes (OR logic) +pub fn has_any_scope(scopes: &[String], required_scopes: &[String]) -> bool { + required_scopes.iter().any(|req| has_scope(scopes, req)) +} + +/// Checks if the session has ALL of the required scopes (AND logic) +pub fn has_all_scopes(scopes: &[String], required_scopes: &[String]) -> bool { + required_scopes.iter().all(|req| has_scope(scopes, req)) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_session(did: &str, handle: &str, scopes: Vec<&str>) -> SessionData { + SessionData { + did: did.to_string(), + handle: handle.to_string(), + scopes: scopes.into_iter().map(|s| s.to_string()).collect(), + } + } + + #[test] + fn test_handle_ends_with() { + let rules = AuthRules::HandleEndsWith(".blacksky.team".into()); + + let valid = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]); + assert!(rules.validate(&valid)); + + let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_handle_ends_with_any() { + let rules = AuthRules::HandleEndsWithAny(vec![".blacksky.team".into(), ".bsky.team".into()]); + + let valid1 = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]); + assert!(rules.validate(&valid1)); + + let valid2 = test_session("did:plc:123", "bob.bsky.team", vec!["atproto"]); + assert!(rules.validate(&valid2)); + + let invalid = test_session("did:plc:123", "charlie.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_did_equals() { + let rules = AuthRules::DidEquals("did:plc:alice".into()); + + let valid = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]); + assert!(rules.validate(&valid)); + + let invalid = test_session("did:plc:bob", "bob.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_any_combinator() { + let rules = AuthRules::Any(vec![ + AuthRules::DidEquals("did:plc:admin".into()), + AuthRules::HandleEndsWith(".blacksky.team".into()), + ]); + + // First condition met + let valid1 = test_session("did:plc:admin", "admin.bsky.social", vec!["atproto"]); + assert!(rules.validate(&valid1)); + + // Second condition met + let valid2 = test_session("did:plc:user", "user.blacksky.team", vec!["atproto"]); + assert!(rules.validate(&valid2)); + + // Neither condition met + let invalid = test_session("did:plc:user", "user.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_all_combinator() { + let rules = AuthRules::All(vec![ + AuthRules::HandleEndsWith(".blacksky.team".into()), + AuthRules::DidEqualsAny(vec!["did:plc:alice".into(), "did:plc:bob".into()]), + ]); + + // Both conditions met + let valid = test_session("did:plc:alice", "alice.blacksky.team", vec!["atproto"]); + assert!(rules.validate(&valid)); + + // Handle wrong + let invalid1 = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid1)); + + // DID wrong + let invalid2 = test_session("did:plc:charlie", "charlie.blacksky.team", vec!["atproto"]); + assert!(!rules.validate(&invalid2)); + } + + // ======================================================================== + // Scope Tests + // ======================================================================== + + #[test] + fn test_scope_equals() { + let rules = AuthRules::ScopeEquals("transition:generic".into()); + + let valid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]); + assert!(rules.validate(&valid)); + + let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_scope_any() { + let rules = AuthRules::ScopeEqualsAny(vec![ + "transition:generic".into(), + "repo:app.bsky.feed.post".into(), + ]); + + // Has first scope + let valid1 = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]); + assert!(rules.validate(&valid1)); + + // Has second scope + let valid2 = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "repo:app.bsky.feed.post"]); + assert!(rules.validate(&valid2)); + + // Has neither + let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + #[test] + fn test_scope_all() { + let rules = AuthRules::ScopeEqualsAll(vec![ + "atproto".into(), + "transition:generic".into(), + ]); + + // Has both scopes + let valid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto", "transition:generic"]); + assert!(rules.validate(&valid)); + + // Missing one scope + let invalid = test_session("did:plc:123", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + } + + // ======================================================================== + // Combined Rules Tests (Identity + Scope) + // ======================================================================== + + #[test] + fn test_handle_ends_with_and_scope() { + let rules = AuthRules::All(vec![ + AuthRules::HandleEndsWith(".blacksky.team".into()), + AuthRules::ScopeEquals("transition:generic".into()), + ]); + + // Both conditions met + let valid = test_session( + "did:plc:123", + "alice.blacksky.team", + vec!["atproto", "transition:generic"], + ); + assert!(rules.validate(&valid)); + + // Handle correct, scope wrong + let invalid1 = test_session("did:plc:123", "alice.blacksky.team", vec!["atproto"]); + assert!(!rules.validate(&invalid1)); + + // Scope correct, handle wrong + let invalid2 = test_session( + "did:plc:123", + "alice.bsky.social", + vec!["atproto", "transition:generic"], + ); + assert!(!rules.validate(&invalid2)); + } + + #[test] + fn test_did_with_scope() { + let rules = AuthRules::All(vec![ + AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()), + AuthRules::ScopeEquals("transition:generic".into()), + ]); + + // Both conditions met + let valid = test_session( + "did:plc:rnpkyqnmsw4ipey6eotbdnnf", + "admin.bsky.social", + vec!["atproto", "transition:generic"], + ); + assert!(rules.validate(&valid)); + + // DID correct, scope wrong + let invalid1 = test_session( + "did:plc:rnpkyqnmsw4ipey6eotbdnnf", + "admin.bsky.social", + vec!["atproto"], + ); + assert!(!rules.validate(&invalid1)); + + // Scope correct, DID wrong + let invalid2 = test_session( + "did:plc:wrongdid", + "admin.bsky.social", + vec!["atproto", "transition:generic"], + ); + assert!(!rules.validate(&invalid2)); + } + + #[test] + fn test_complex_admin_or_moderator_rule() { + // Admin DID OR (moderator handle + scope) + let rules = AuthRules::Any(vec![ + AuthRules::DidEquals("did:plc:rnpkyqnmsw4ipey6eotbdnnf".into()), + AuthRules::All(vec![ + AuthRules::HandleEndsWith(".mod.team".into()), + AuthRules::ScopeEquals("account:email".into()), + ]), + ]); + + // Admin DID (doesn't need scope) + let admin = test_session("did:plc:rnpkyqnmsw4ipey6eotbdnnf", "admin.bsky.social", vec!["atproto"]); + assert!(rules.validate(&admin)); + + // Moderator with correct handle and scope + let mod_valid = test_session( + "did:plc:somemod", + "alice.mod.team", + vec!["atproto", "account:email"], + ); + assert!(rules.validate(&mod_valid)); + + // Moderator handle but missing scope + let mod_no_scope = test_session("did:plc:somemod", "alice.mod.team", vec!["atproto"]); + assert!(!rules.validate(&mod_no_scope)); + + // Has scope but not moderator handle + let not_mod = test_session( + "did:plc:someuser", + "alice.bsky.social", + vec!["atproto", "account:email"], + ); + assert!(!rules.validate(¬_mod)); + } + + // ======================================================================== + // Additional Coverage Tests + // ======================================================================== + + #[test] + fn test_did_equals_any() { + let rules = AuthRules::DidEqualsAny(vec![ + "did:plc:alice123456789012345678".into(), + "did:plc:bob12345678901234567890".into(), + "did:plc:charlie12345678901234567".into(), + ]); + + // First DID matches + let valid1 = test_session("did:plc:alice123456789012345678", "alice.bsky.social", vec!["atproto"]); + assert!(rules.validate(&valid1)); + + // Second DID matches + let valid2 = test_session("did:plc:bob12345678901234567890", "bob.bsky.social", vec!["atproto"]); + assert!(rules.validate(&valid2)); + + // Third DID matches + let valid3 = test_session("did:plc:charlie12345678901234567", "charlie.bsky.social", vec!["atproto"]); + assert!(rules.validate(&valid3)); + + // DID not in list + let invalid = test_session("did:plc:unknown1234567890123456", "unknown.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&invalid)); + + // Partial match should fail (prefix) + let partial = test_session("did:plc:alice", "alice.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&partial)); + } + + // ======================================================================== + // Empty Combinator Edge Cases + // ======================================================================== + + #[test] + fn test_empty_any_returns_false() { + // Any with empty rules should return false (no rule can be satisfied) + let rules = AuthRules::Any(vec![]); + let session = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]); + assert!(!rules.validate(&session)); + } + + #[test] + fn test_empty_all_returns_true() { + // All with empty rules should return true (vacuous truth - all zero rules are satisfied) + let rules = AuthRules::All(vec![]); + let session = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]); + assert!(rules.validate(&session)); + } + + // ======================================================================== + // Handle Edge Cases + // ======================================================================== + + #[test] + fn test_handle_exact_suffix_match() { + // Handle that IS exactly the suffix should still match + let rules = AuthRules::HandleEndsWith(".blacksky.team".into()); + + // Handle is exactly the suffix + let exact = test_session("did:plc:test12345678901234567", ".blacksky.team", vec!["atproto"]); + assert!(rules.validate(&exact)); + + // Normal case - handle with prefix + let normal = test_session("did:plc:test12345678901234567", "alice.blacksky.team", vec!["atproto"]); + assert!(rules.validate(&normal)); + + // Empty handle should not match + let empty = test_session("did:plc:test12345678901234567", "", vec!["atproto"]); + assert!(!rules.validate(&empty)); + } + + // ======================================================================== + // Deeply Nested Combinators + // ======================================================================== + + #[test] + fn test_deeply_nested_rules() { + // Complex nested rule: Any(All(Any(...), ...), All(...)) + // Scenario: (Admin OR VIP) AND (has scope OR team member) + // OR + // Specific moderator DID + let rules = AuthRules::Any(vec![ + // Branch 1: Complex nested condition + AuthRules::All(vec![ + // Must be admin or VIP + AuthRules::Any(vec![ + AuthRules::DidEquals("did:plc:admin123456789012345".into()), + AuthRules::HandleEndsWith(".vip.social".into()), + ]), + // AND must have scope or be team member + AuthRules::Any(vec![ + AuthRules::ScopeEquals("transition:generic".into()), + AuthRules::HandleEndsWith(".team.internal".into()), + ]), + ]), + // Branch 2: Specific moderator bypass + AuthRules::DidEquals("did:plc:moderator12345678901".into()), + ]); + + // Admin with required scope - should pass via Branch 1 + let admin_with_scope = test_session( + "did:plc:admin123456789012345", + "admin.bsky.social", + vec!["atproto", "transition:generic"], + ); + assert!(rules.validate(&admin_with_scope)); + + // VIP with required scope - should pass via Branch 1 + let vip_with_scope = test_session( + "did:plc:somevip1234567890123", + "alice.vip.social", + vec!["atproto", "transition:generic"], + ); + assert!(rules.validate(&vip_with_scope)); + + // Moderator bypass - should pass via Branch 2 + let moderator = test_session( + "did:plc:moderator12345678901", + "mod.bsky.social", + vec!["atproto"], + ); + assert!(rules.validate(&moderator)); + + // Admin without scope and not team member - should fail + let admin_no_scope = test_session( + "did:plc:admin123456789012345", + "admin.bsky.social", + vec!["atproto"], + ); + assert!(!rules.validate(&admin_no_scope)); + + // Random user - should fail + let random = test_session( + "did:plc:random12345678901234", + "random.bsky.social", + vec!["atproto", "transition:generic"], + ); + assert!(!rules.validate(&random)); + } + + // ======================================================================== + // ATProto Scope Specification Tests + // ======================================================================== + + #[test] + fn test_scope_with_query_params() { + // ATProto scopes can have query parameters + // These are treated as literal strings (exact matching) + + // blob scope with accept parameter + let blob_rules = AuthRules::ScopeEquals("blob?accept=image/*".into()); + let has_blob = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "blob?accept=image/*"], + ); + assert!(blob_rules.validate(&has_blob)); + + // Different query param should not match + let wrong_blob = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "blob?accept=video/*"], + ); + assert!(!blob_rules.validate(&wrong_blob)); + + // account:repo with action parameter + let account_rules = AuthRules::ScopeEquals("account:repo?action=manage".into()); + let has_account = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "account:repo?action=manage"], + ); + assert!(account_rules.validate(&has_account)); + + // Multiple query params + let multi_param_rules = AuthRules::ScopeEquals("blob?accept=image/*&accept=video/*".into()); + let has_multi = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "blob?accept=image/*&accept=video/*"], + ); + assert!(multi_param_rules.validate(&has_multi)); + } + + #[test] + fn test_scope_exact_matching_no_wildcards() { + // ATProto spec: Wildcards do NOT work for collections + // repo:app.bsky.feed.post should NOT match repo:app.bsky.feed.* + // This test verifies our exact matching is correct + + let rules = AuthRules::ScopeEquals("repo:app.bsky.feed.post".into()); + + // Exact match works + let exact = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "repo:app.bsky.feed.post"], + ); + assert!(rules.validate(&exact)); + + // Wildcard scope in JWT should NOT satisfy specific requirement + // (user has repo:app.bsky.feed.* but endpoint requires repo:app.bsky.feed.post) + let wildcard = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "repo:app.bsky.feed.*"], + ); + assert!(!rules.validate(&wildcard)); + + // Different collection should not match + let different = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "repo:app.bsky.feed.like"], + ); + assert!(!rules.validate(&different)); + + // Prefix should not match + let prefix = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "repo:app.bsky.feed"], + ); + assert!(!rules.validate(&prefix)); + + // repo:* should not match specific collection (exact matching) + let full_wildcard = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "repo:*"], + ); + assert!(!rules.validate(&full_wildcard)); + } + + #[test] + fn test_scope_case_sensitivity() { + // OAuth scopes are case-sensitive per RFC 6749 + + let rules = AuthRules::ScopeEquals("atproto".into()); + + // Exact case matches + let exact = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["atproto"]); + assert!(rules.validate(&exact)); + + // UPPERCASE should NOT match + let upper = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["ATPROTO"]); + assert!(!rules.validate(&upper)); + + // Mixed case should NOT match + let mixed = test_session("did:plc:test12345678901234567", "test.bsky.social", vec!["AtProto"]); + assert!(!rules.validate(&mixed)); + + // Test with namespaced scope + let ns_rules = AuthRules::ScopeEquals("transition:generic".into()); + let ns_upper = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["TRANSITION:GENERIC"], + ); + assert!(!ns_rules.validate(&ns_upper)); + } + + #[test] + fn test_scope_with_wildcards_exact_match() { + // Wildcard scopes like blob:*/* and identity:* are stored as literal strings + // The middleware does exact matching, so JWT must contain the exact string + + // blob:*/* - full blob wildcard + let blob_rules = AuthRules::ScopeEquals("blob:*/*".into()); + let has_blob_wildcard = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "blob:*/*"], + ); + assert!(blob_rules.validate(&has_blob_wildcard)); + + // Specific blob type should NOT match blob:*/* requirement + let has_specific_blob = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "blob:image/png"], + ); + assert!(!blob_rules.validate(&has_specific_blob)); + + // identity:* - full identity wildcard + let identity_rules = AuthRules::ScopeEquals("identity:*".into()); + let has_identity_wildcard = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "identity:*"], + ); + assert!(identity_rules.validate(&has_identity_wildcard)); + + // Specific identity scope should NOT match identity:* requirement + let has_specific_identity = test_session( + "did:plc:test12345678901234567", + "test.bsky.social", + vec!["atproto", "identity:handle"], + ); + assert!(!identity_rules.validate(&has_specific_identity)); + } + + // ======================================================================== + // Scope Helper Function Tests + // ======================================================================== + + #[test] + fn test_has_scope_helper() { + let scopes: Vec = vec![ + "atproto".to_string(), + "transition:generic".to_string(), + "repo:app.bsky.feed.post".to_string(), + ]; + + // Present scopes + assert!(has_scope(&scopes, "atproto")); + assert!(has_scope(&scopes, "transition:generic")); + assert!(has_scope(&scopes, "repo:app.bsky.feed.post")); + + // Absent scopes + assert!(!has_scope(&scopes, "identity:*")); + assert!(!has_scope(&scopes, "")); + assert!(!has_scope(&scopes, "ATPROTO")); // Case sensitive + + // Empty scopes list + let empty: Vec = vec![]; + assert!(!has_scope(&empty, "atproto")); + } + + #[test] + fn test_has_any_scope_helper() { + let scopes: Vec = vec![ + "atproto".to_string(), + "repo:app.bsky.feed.post".to_string(), + ]; + + // Has one of the required scopes + let required1 = vec!["transition:generic".to_string(), "atproto".to_string()]; + assert!(has_any_scope(&scopes, &required1)); + + // Has none of the required scopes + let required2 = vec!["transition:generic".to_string(), "identity:*".to_string()]; + assert!(!has_any_scope(&scopes, &required2)); + + // Empty required list - should return false (no scope to match) + let empty_required: Vec = vec![]; + assert!(!has_any_scope(&scopes, &empty_required)); + + // Empty scopes list + let empty_scopes: Vec = vec![]; + assert!(!has_any_scope(&empty_scopes, &required1)); + } + + #[test] + fn test_has_all_scopes_helper() { + let scopes: Vec = vec![ + "atproto".to_string(), + "transition:generic".to_string(), + "repo:app.bsky.feed.post".to_string(), + ]; + + // Has all required scopes + let required1 = vec!["atproto".to_string(), "transition:generic".to_string()]; + assert!(has_all_scopes(&scopes, &required1)); + + // Missing one required scope + let required2 = vec!["atproto".to_string(), "identity:*".to_string()]; + assert!(!has_all_scopes(&scopes, &required2)); + + // Empty required list - should return true (vacuously all zero required are present) + let empty_required: Vec = vec![]; + assert!(has_all_scopes(&scopes, &empty_required)); + + // Empty scopes list with requirements + let empty_scopes: Vec = vec![]; + assert!(!has_all_scopes(&empty_scopes, &required1)); + + // Single scope requirement + let single = vec!["atproto".to_string()]; + assert!(has_all_scopes(&scopes, &single)); + } +} diff --git a/src/main.rs b/src/main.rs index b6f731c..502d6c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ use tower_http::{ use tracing::log; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; +mod auth; mod gate; pub mod helpers; mod middleware; @@ -153,6 +154,7 @@ pub struct AppState { mailer: AsyncSmtpTransport, template_engine: Engine>, resolver: Arc, + handle_cache: auth::HandleCache, app_config: AppConfig, } @@ -278,6 +280,7 @@ async fn main() -> Result<(), Box> { mailer, template_engine: Engine::from(hbs), resolver: Arc::new(resolver), + handle_cache: auth::HandleCache::new(), app_config: AppConfig::new(), }; -- 2.43.0