From a02065604d439dee7f89ec913360dc337e78950a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kuras?= Date: Thu, 28 May 2026 12:20:20 +0200 Subject: [PATCH 1/3] feat(auth): slide auth_sessions.expires_at on every successful auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the only path to recover from an expired `auth_sessions` row was `tracevault login`. Tokens hard-expire at 30 days regardless of how active the user is — daily users hit the cliff just as easily as inactive ones, with no signal until a push gets blocked. Replace the SELECT-only validity check in both `AuthUser` and `OrgAuth` extractors with a single `UPDATE auth_sessions SET expires_at = NOW() + INTERVAL '30 days' WHERE token_hash = AND expires_at > NOW() RETURNING user_id`. Atomic: one round-trip, no race between the validity check and the extension. Net effect: * Active users (anything that talks to TV once per 30 days) never see a token expiry again — the window slides forward on every hit. * Inactive users still hit the 30-day expiry cliff and must re-login, which is the right security tradeoff for genuinely-dormant tokens. * No new endpoint, no client change, no breaking-change to existing callers. Three integration tests against a real Postgres pool exercise the SQL directly: extend-on-hit, no-revive-of-already-expired, no-op-on- unknown-token. --- crates/tracevault-server/src/extractors.rs | 23 +++- .../tests/sliding_session_test.rs | 116 ++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 crates/tracevault-server/tests/sliding_session_test.rs diff --git a/crates/tracevault-server/src/extractors.rs b/crates/tracevault-server/src/extractors.rs index 4b924943..2776c19d 100644 --- a/crates/tracevault-server/src/extractors.rs +++ b/crates/tracevault-server/src/extractors.rs @@ -32,9 +32,19 @@ impl FromRequestParts for AuthUser { let token_hash = sha256_hex(header); - // Try auth_sessions first + // Sliding session window: on every successful auth, bump expires_at + // to NOW() + 30 days. As long as the user touches TV at least once + // per 30 days, the token never expires from their perspective. + // Inactive users still hit the 30-day cliff and must re-login. + // + // Using UPDATE..RETURNING means we atomically (a) verify the token + // is still valid (the WHERE filters expired rows out), (b) extend + // it, (c) read back the user_id — one round-trip, no race. let session_row = sqlx::query_as::<_, (Uuid,)>( - "SELECT user_id FROM auth_sessions WHERE token_hash = $1 AND expires_at > NOW()", + "UPDATE auth_sessions + SET expires_at = NOW() + INTERVAL '30 days' + WHERE token_hash = $1 AND expires_at > NOW() + RETURNING user_id", ) .bind(&token_hash) .fetch_optional(&state.pool) @@ -120,9 +130,14 @@ impl FromRequestParts for OrgAuth { let org_id = org_row.0; - // Try session auth + // Try session auth — sliding window: same pattern as AuthUser, see + // the comment there. UPDATE..RETURNING atomically bumps expires_at + // and returns the user_id only when the token is still valid. let session_row = sqlx::query_as::<_, (Uuid,)>( - "SELECT user_id FROM auth_sessions WHERE token_hash = $1 AND expires_at > NOW()", + "UPDATE auth_sessions + SET expires_at = NOW() + INTERVAL '30 days' + WHERE token_hash = $1 AND expires_at > NOW() + RETURNING user_id", ) .bind(&token_hash) .fetch_optional(&state.pool) diff --git a/crates/tracevault-server/tests/sliding_session_test.rs b/crates/tracevault-server/tests/sliding_session_test.rs new file mode 100644 index 00000000..cf2fc612 --- /dev/null +++ b/crates/tracevault-server/tests/sliding_session_test.rs @@ -0,0 +1,116 @@ +//! Verifies the sliding-session-window behavior added to the auth +//! extractors. On every successful auth, the matching `auth_sessions` row +//! has its `expires_at` bumped to NOW() + 30 days. +//! +//! The extractor is wired into HTTP handlers and not trivially callable in +//! isolation, but the SQL pattern it uses is `UPDATE auth_sessions SET +//! expires_at = NOW() + INTERVAL '30 days' WHERE token_hash = $1 AND +//! expires_at > NOW() RETURNING user_id`. These tests exercise that +//! statement directly against a real Postgres pool, which is the precise +//! source of the behavior we want to lock in. + +mod common; + +use chrono::{Duration, Utc}; +use tracevault_server::auth::sha256_hex; +use uuid::Uuid; + +async fn seed_session_with_expiry( + pool: &sqlx::PgPool, + user_id: Uuid, + token_hash: &str, + expires_at: chrono::DateTime, +) -> Uuid { + sqlx::query_scalar::<_, Uuid>( + "INSERT INTO auth_sessions (user_id, token_hash, expires_at) \ + VALUES ($1, $2, $3) RETURNING id", + ) + .bind(user_id) + .bind(token_hash) + .bind(expires_at) + .fetch_one(pool) + .await + .unwrap() +} + +/// Run the sliding-window UPDATE used by the extractor and return the +/// updated `expires_at` for the session row (or None if the row was +/// expired / not found). +async fn run_sliding_update( + pool: &sqlx::PgPool, + token_hash: &str, +) -> Option> { + sqlx::query_as::<_, (chrono::DateTime,)>( + "UPDATE auth_sessions + SET expires_at = NOW() + INTERVAL '30 days' + WHERE token_hash = $1 AND expires_at > NOW() + RETURNING expires_at", + ) + .bind(token_hash) + .fetch_optional(pool) + .await + .unwrap() + .map(|(ts,)| ts) +} + +#[sqlx::test(migrations = "./migrations")] +async fn auth_extends_expires_at_on_each_hit(pool: sqlx::PgPool) { + let user_id = common::seed_user(&pool).await; + let token = format!("tok_{}", Uuid::new_v4()); + let token_hash = sha256_hex(&token); + + // Seed an "almost expired" session: 2 minutes left. + let initial_expires = Utc::now() + Duration::minutes(2); + seed_session_with_expiry(&pool, user_id, &token_hash, initial_expires).await; + + let bumped = run_sliding_update(&pool, &token_hash) + .await + .expect("valid (not-yet-expired) session should be extended"); + + // After the bump, expires_at should be ~30 days in the future — at + // minimum, more than 29 days from now. We don't pin it exactly because + // NOW() in Postgres is evaluated server-side and clock skew between + // test machine and server is real (postgres in Docker, test in host). + let min_expected = Utc::now() + Duration::days(29); + assert!( + bumped > min_expected, + "sliding window should extend expires_at to ~30 days; got {bumped}" + ); +} + +#[sqlx::test(migrations = "./migrations")] +async fn auth_does_not_extend_already_expired_session(pool: sqlx::PgPool) { + let user_id = common::seed_user(&pool).await; + let token = format!("tok_{}", Uuid::new_v4()); + let token_hash = sha256_hex(&token); + + // Seed a session that expired an hour ago. + let expired_at = Utc::now() - Duration::hours(1); + seed_session_with_expiry(&pool, user_id, &token_hash, expired_at).await; + + let result = run_sliding_update(&pool, &token_hash).await; + assert!( + result.is_none(), + "an already-expired session must NOT be revived by a sliding-window update; got {result:?}" + ); + + // Belt-and-braces: confirm the row's expires_at is still in the past. + let row_expires_at = sqlx::query_scalar::<_, chrono::DateTime>( + "SELECT expires_at FROM auth_sessions WHERE token_hash = $1", + ) + .bind(&token_hash) + .fetch_one(&pool) + .await + .unwrap(); + assert!( + row_expires_at < Utc::now(), + "row's expires_at should remain in the past after a no-op sliding update" + ); +} + +#[sqlx::test(migrations = "./migrations")] +async fn auth_sliding_update_is_a_noop_for_unknown_tokens(pool: sqlx::PgPool) { + let unknown_hash = sha256_hex("definitely-not-a-real-token"); + let result = run_sliding_update(&pool, &unknown_hash).await; + assert!(result.is_none()); +} From effdbe48ad6a23d6fe059061976af4c2903c3970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kuras?= Date: Thu, 28 May 2026 12:20:31 +0200 Subject: [PATCH 2/3] fix(cli): actionable error messages on tracevault check failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-push hook runs `tracevault check` and exits 1 on any failure. Previously that 1 came with an opaque message — typically "Stream failed (500 Internal Server Error)" or "Server returned 401: Invalid or expired token". Both an agent and a human reader were left guessing what to do. Adds `connectivity_message()` which sniffs the raw error string and appends a specific next step: * 401 / Unauthorized: "Session token may be expired. Run `tracevault login --server-url=` to refresh." * 403 / Forbidden: org-membership hint (not a re-login). * DNS / connect / timeout / connection refused: network + server_url hint. * 5xx Server Error / Internal: "team has been paged, retry shortly." * Unknown shapes: pass through the raw error, no fake advice. All connectivity-class errors still propagate as Err so the pre-push hook exits non-zero — a TV-tracked repo MUST be evaluated by TV on every push, that contract doesn't change. The fix here is purely the quality of the message the user/agent reads after the block. Six unit tests pin the message shapes for the common error patterns the api_client surfaces today. --- crates/tracevault-cli/src/commands/check.rs | 122 +++++++++++++++++--- 1 file changed, 107 insertions(+), 15 deletions(-) diff --git a/crates/tracevault-cli/src/commands/check.rs b/crates/tracevault-cli/src/commands/check.rs index 68aec742..b9b858a2 100644 --- a/crates/tracevault-cli/src/commands/check.rs +++ b/crates/tracevault-cli/src/commands/check.rs @@ -135,31 +135,38 @@ fn collect_session_data(session_dir: &Path) -> Option { pub async fn check_policies(project_root: &Path) -> Result<(), Box> { let (server_url, token) = resolve_credentials(project_root); - let server_url = match server_url { - Some(url) => url, - None => { - return Err("No server URL configured. Run 'tracevault login' first.".into()); - } - }; + let server_url = server_url + .ok_or("No server URL configured. Run `tracevault login --server-url=` to set one.")?; if token.is_none() { - return Err("Not logged in. Run 'tracevault login' to check policies.".into()); + return Err( + "Not logged in. Run `tracevault login --server-url=` to authenticate." + .into(), + ); } let org_slug = TracevaultConfig::load(project_root) .and_then(|c| c.org_slug) - .ok_or("No org_slug in config. Run 'tracevault init' first.")?; + .ok_or("No org_slug in config. Run `tracevault init` first.")?; let client = ApiClient::new(&server_url, token.as_deref()); - // Resolve repo_id by name + // Resolve repo_id by name. + // + // Connectivity errors here (auth expired, server down, network + // unreachable) propagate so the pre-push hook exits non-zero — if a + // repo is opted into TraceVault, every push must be evaluated, full + // stop. Letting pushes slip when TV is unreachable would defeat the + // point of enforcement. We attach an actionable next step to each + // error so the user (or agent) knows the recovery command without + // guessing — see `connectivity_message` below. let repo_name = git_repo_name(project_root); - let repos = client.list_repos(&org_slug).await?; + let repos = client + .list_repos(&org_slug) + .await + .map_err(|e| connectivity_message(&e.to_string()))?; let repo = repos.iter().find(|r| r.name == repo_name).ok_or_else(|| { - format!( - "Repo '{}' not found on server. Run 'tracevault sync' first.", - repo_name - ) + format!("Repo '{repo_name}' not found on server. Run `tracevault sync` first.") })?; // Collect session data from unpushed sessions @@ -200,7 +207,8 @@ pub async fn check_policies(project_root: &Path) -> Result<(), Box Result<(), Box String { + let lower = raw.to_ascii_lowercase(); + let action = if lower.contains("401") || lower.contains("unauthorized") { + Some("Session token may be expired. Run `tracevault login --server-url=` to refresh.") + } else if lower.contains("403") || lower.contains("forbidden") { + Some("Your token lacks access to this repo's policies. Check your org membership and rerun `tracevault login`.") + } else if lower.contains("dns") + || lower.contains("connect") + || lower.contains("timed out") + || lower.contains("timeout") + || lower.contains("connection refused") + { + Some("Could not reach the TraceVault server. Check network and `server_url` in .tracevault/config.toml.") + } else if lower.contains("5") && (lower.contains("server error") || lower.contains("internal")) + { + Some("TraceVault server returned an error. The team has likely been paged; retry shortly.") + } else { + None + }; + + match action { + Some(a) => format!("Policy check could not run: {raw}.\n → {a}"), + None => format!("Policy check could not run: {raw}."), + } +} + +#[cfg(test)] +mod tests { + use super::connectivity_message; + + #[test] + fn connectivity_message_suggests_login_on_401() { + let m = connectivity_message("Stream failed (401 Unauthorized): bad token"); + assert!( + m.contains("tracevault login"), + "401 errors must surface the login hint; got: {m}" + ); + } + + #[test] + fn connectivity_message_suggests_login_on_unauthorized_text() { + let m = connectivity_message("Server returned: unauthorized request"); + assert!(m.contains("tracevault login")); + } + + #[test] + fn connectivity_message_suggests_network_check_on_dns_error() { + let m = connectivity_message("error sending request for url: dns error"); + assert!( + m.to_lowercase().contains("network") || m.to_lowercase().contains("server_url"), + "DNS errors must surface a network hint; got: {m}" + ); + } + + #[test] + fn connectivity_message_suggests_network_check_on_connection_refused() { + let m = connectivity_message("connection refused"); + assert!(m.to_lowercase().contains("network")); + } + + #[test] + fn connectivity_message_falls_back_when_unrecognized() { + let m = connectivity_message("some weird new error shape we have not seen before"); + // No suggestion — but the raw error must still be in the message so + // the user can debug. + assert!(m.contains("some weird new error shape")); + assert!(!m.contains("→")); // no action arrow + } + + #[test] + fn connectivity_message_does_not_collide_on_403() { + let m = connectivity_message("403 Forbidden"); + // 403 should suggest org membership, NOT a re-login. + assert!( + m.to_lowercase().contains("membership"), + "403 should mention membership; got: {m}" + ); + } +} From fc1ba1bb4130c8b2ec2cfe46a1193a9889c84c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kuras?= Date: Thu, 28 May 2026 12:20:37 +0200 Subject: [PATCH 3/3] chore: sync Cargo.lock after v0.16.0 dep churn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same lockfile regeneration as PR #211 carried — release-plz updated deps in v0.16.0 that this branch picks up. CI runs `cargo check --locked`, so commit the regenerated lock. --- Cargo.lock | 471 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 456 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f97a7c3..5ad62a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,12 +370,24 @@ dependencies = [ "syn", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -439,6 +451,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "built" version = "0.8.0" @@ -772,6 +793,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -843,8 +876,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -861,13 +904,37 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", "quote", "syn", ] @@ -924,6 +991,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "derive_builder" version = "0.20.2" @@ -939,7 +1016,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", "syn", @@ -1023,6 +1100,26 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1057,6 +1154,27 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1189,6 +1307,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1403,6 +1531,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1509,6 +1638,17 @@ dependencies = [ "web-time", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.14" @@ -1521,7 +1661,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1539,6 +1679,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1742,6 +1888,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.7", ] [[package]] @@ -1976,6 +2123,17 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -2062,6 +2220,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2547,6 +2714,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -2615,6 +2788,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.17", + "http", + "rand 0.8.6", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.9", + "thiserror 1.0.69", + "url", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2666,6 +2859,37 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openidconnect" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2" +dependencies = [ + "base64 0.21.7", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac 0.12.1", + "http", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.6", + "rsa", + "serde", + "serde-value", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2 0.10.9", + "subtle", + "thiserror 1.0.69", + "url", +] + [[package]] name = "openssl" version = "0.10.80" @@ -2721,6 +2945,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ort" version = "2.0.0-rc.9" @@ -2745,6 +2978,30 @@ dependencies = [ "ureq", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "parking" version = "2.2.1" @@ -2935,6 +3192,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2954,6 +3217,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3164,7 +3436,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.14.0", "libc", "libfuzzer-sys", "log", @@ -3230,7 +3502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" dependencies = [ "either", - "itertools", + "itertools 0.14.0", "rayon", ] @@ -3273,6 +3545,26 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.12.3" @@ -3327,6 +3619,8 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", @@ -3334,6 +3628,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3343,6 +3638,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 1.0.7", ] [[package]] @@ -3385,6 +3681,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rgb" version = "0.8.53" @@ -3560,12 +3866,50 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -3605,6 +3949,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3649,6 +4003,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3661,6 +4024,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3852,7 +4247,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -4196,6 +4591,37 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -4234,7 +4660,7 @@ dependencies = [ "derive_builder", "esaxx-rs", "getrandom 0.3.4", - "itertools", + "itertools 0.14.0", "log", "macro_rules_attribute", "monostate", @@ -4363,7 +4789,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -4469,8 +4895,22 @@ dependencies = [ name = "tracevault-enterprise" version = "0.1.0" dependencies = [ + "aes-gcm", "async-trait", + "base64 0.22.1", + "chrono", + "ed25519-dalek", + "glob-match", + "hex", + "openidconnect", + "rand 0.8.6", + "reqwest 0.13.2", + "serde", + "serde_json", + "sha2 0.11.0", "tracevault-core", + "tracing", + "uuid", ] [[package]] @@ -4780,6 +5220,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -4966,7 +5407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -4992,7 +5433,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -5508,7 +5949,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn", "wasm-metadata", @@ -5539,7 +5980,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -5558,7 +5999,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde",