From e4605f94e3473fef8664025c85b8b17dca0f6e46 Mon Sep 17 00:00:00 2001 From: Zied Yousfi Date: Sat, 7 Mar 2026 12:38:36 +0100 Subject: [PATCH 1/4] fix(docs): make doctest examples compile cleanly --- src/core/credentials/mod.rs | 20 +++---- src/core/hash/argon2.rs | 6 +-- src/core/oauth/manager.rs | 39 +++++++++----- src/core/oauth/store.rs | 72 +++++++++++++++++++++----- src/core/password/argon2.rs | 8 +-- src/core/password/mod.rs | 8 +-- src/core/policy/mod.rs | 2 +- src/core/token/jwt.rs | 7 +-- src/core/token/mod.rs | 22 ++++---- src/core/user/mod.rs | 2 +- src/core/user/persistence/in_memory.rs | 3 +- src/lib.rs | 2 +- 12 files changed, 128 insertions(+), 63 deletions(-) diff --git a/src/core/credentials/mod.rs b/src/core/credentials/mod.rs index 016cdd4..26a244c 100644 --- a/src/core/credentials/mod.rs +++ b/src/core/credentials/mod.rs @@ -21,22 +21,22 @@ //! //! ## Example //! -//! ```rust -//! use crate::core::credentials::{Credentials, PlainPassword}; -//! use crate::core::password::SecurePasswordManager; +//! ```rust,no_run +//! use authen::core::credentials::{Credentials, PlainPassword}; +//! use authen::core::password::SecurePasswordManager; +//! use authen::error::AuthError; //! # struct DummyManager; //! # #[async_trait::async_trait] //! # impl SecurePasswordManager for DummyManager { -//! # async fn hash_password(&self, password: &str) -> Result { Ok(password.to_owned()) } -//! # async fn verify_password(&self, password: &str, hash: &str) -> Result { Ok(password == hash) } +//! # async fn hash_password(&self, password: &str) -> Result { Ok(password.to_owned()) } +//! # async fn verify_password(&self, password: &str, hash: &str) -> Result { Ok(password == hash) } //! # } -//! # #[tokio::main] -//! # async fn main() { +//! # tokio::runtime::Runtime::new().unwrap().block_on(async { //! let manager = DummyManager; -//! let plain = PlainPassword::new("my_password").unwrap(); +//! let plain = PlainPassword::new("my_password".to_string()); //! let creds = Credentials::from_plain_password(&manager, "user-1".to_string(), "user@example.com".to_string(), plain).await.unwrap(); -//! assert!(creds.verify_password(&manager, &PlainPassword::new("my_password").unwrap()).await.unwrap()); -//! # } +//! assert!(creds.verify_password(&manager, &PlainPassword::new("my_password".to_string())).await.unwrap()); +//! # }); //! ``` pub mod plain_password; diff --git a/src/core/hash/argon2.rs b/src/core/hash/argon2.rs index f5b65ee..69f2a95 100644 --- a/src/core/hash/argon2.rs +++ b/src/core/hash/argon2.rs @@ -7,11 +7,11 @@ //! //! ```rust //! use authen::core::hash::argon2::Argon2Hasher; -//! use argon2::password_hash::SaltString; +//! use authen::core::hash::generate_secure_salt; //! //! let hasher = Argon2Hasher::new(); //! let password = b"mysecret"; -//! let salt = SaltString::generate(&mut rand::thread_rng()); +//! let salt = generate_secure_salt().unwrap(); //! let hash = hasher.hash(password, Some(&salt)).unwrap(); //! assert!(hasher.verify(password, &hash).unwrap()); //! ``` @@ -36,7 +36,7 @@ impl Argon2Hasher { /// /// # Examples /// - /// ```rust + /// ```rust,no_run /// use authen::core::hash::argon2::Argon2Hasher; /// let hasher = Argon2Hasher::new(); /// ``` diff --git a/src/core/oauth/manager.rs b/src/core/oauth/manager.rs index 3cffa1e..7f4d2bb 100644 --- a/src/core/oauth/manager.rs +++ b/src/core/oauth/manager.rs @@ -25,12 +25,19 @@ //! //! ## Example Usage //! -//! ```rust -//! use crate::core::oauth::{OAuth2Manager, OAuth2Provider, OAuth2Config}; +//! ```rust,no_run +//! use authen::core::oauth::{manager::OAuth2Manager, store::{OAuth2Config, OAuth2Provider}}; //! use std::collections::HashMap; //! //! let mut configs = HashMap::new(); -//! configs.insert(OAuth2Provider::Google, OAuth2Config::default_google()); +//! configs.insert(OAuth2Provider::Google, OAuth2Config { +//! app_name: "example-app".to_string(), +//! client_id: "client-id".to_string(), +//! client_secret: "client-secret".to_string(), +//! redirect_callback_uri: "http://localhost:3000/oauth/google/callback".to_string(), +//! redirect_frontend_uri: "http://localhost:5173/auth/callback".to_string(), +//! additional_scopes: vec!["email".to_string()], +//! }); //! let manager = OAuth2Manager::new(configs); //! ``` @@ -83,11 +90,18 @@ type ConfiguredBasicClient = oauth2::Client< /// - Microsoft /// /// # Example -/// ```rust -/// use crate::core::oauth::{OAuth2Manager, OAuth2Provider, OAuth2Config}; +/// ```rust,no_run +/// use authen::core::oauth::{manager::OAuth2Manager, store::{OAuth2Config, OAuth2Provider}}; /// use std::collections::HashMap; /// let mut configs = HashMap::new(); -/// configs.insert(OAuth2Provider::Google, OAuth2Config::default_google()); +/// configs.insert(OAuth2Provider::Google, OAuth2Config { +/// app_name: "example-app".to_string(), +/// client_id: "client-id".to_string(), +/// client_secret: "client-secret".to_string(), +/// redirect_callback_uri: "http://localhost:3000/oauth/google/callback".to_string(), +/// redirect_frontend_uri: "http://localhost:5173/auth/callback".to_string(), +/// additional_scopes: vec!["email".to_string()], +/// }); /// let manager = OAuth2Manager::new(configs); /// ``` pub struct OAuth2Manager { @@ -121,7 +135,7 @@ impl OAuth2Manager { /// Returns [`AuthError::ConfigError`] if the provider configuration is missing or the client cannot be built. /// /// # Example - /// ```rust + /// ```rust,ignore /// let client = manager.get_http_client(OAuth2Provider::Google)?; /// ``` fn get_http_client(&self, provider: OAuth2Provider) -> Result { @@ -144,7 +158,7 @@ impl OAuth2Manager { /// Returns [`AuthError::ConfigError`] if the provider configuration is missing or contains invalid URLs. /// /// # Example - /// ```rust + /// ```rust,ignore /// let client = manager.get_client(OAuth2Provider::GitHub)?; /// ``` pub fn get_client(&self, provider: OAuth2Provider) -> Result { @@ -199,7 +213,7 @@ impl OAuth2Manager { /// Returns [`OAuth2UserInfo`] on success, or [`AuthError`] if required fields are missing or the response is invalid. /// /// # Example - /// ```rust + /// ```rust,ignore /// let user_info = manager.parse_user_info(OAuth2Provider::Google, json_response).await?; /// ``` pub async fn parse_user_info( @@ -597,7 +611,7 @@ impl OAuth2Manager { /// Returns the frontend redirect URI as a string, or an [`AuthError`] if the provider configuration is missing. /// /// # Example - /// ```rust + /// ```rust,ignore /// let uri = manager.get_redirect_frontend_uri(OAuth2Provider::Discord)?; /// ``` pub fn get_redirect_frontend_uri(&self, provider: OAuth2Provider) -> Result { @@ -619,7 +633,7 @@ impl OAuth2Manager { /// The updated [`OAuth2UserInfo`] with the `user_id` field set. /// /// # Example - /// ```rust + /// ```rust,ignore /// let linked_info = OAuth2Manager::link_to_user(oauth_info, user_id); /// ``` pub fn link_to_user(mut oauth_info: OAuth2UserInfo, user_id: String) -> OAuth2UserInfo { @@ -634,7 +648,8 @@ impl OAuth2Manager { /// This implementation is useful for testing or initializing the manager before loading provider configs. /// /// # Example -/// ```rust +/// ```rust,no_run +/// use authen::core::oauth::manager::OAuth2Manager; /// let manager = OAuth2Manager::default(); /// ``` impl Default for OAuth2Manager { diff --git a/src/core/oauth/store.rs b/src/core/oauth/store.rs index c0b3d09..62153cc 100644 --- a/src/core/oauth/store.rs +++ b/src/core/oauth/store.rs @@ -16,14 +16,14 @@ //! //! ## Example Configuration //! -//! ```rust +//! ```rust,no_run //! use authen::core::oauth::store::OAuth2Config; //! //! let config = OAuth2Config { //! app_name: "My App".to_string(), //! client_id: "your-client-id".to_string(), //! client_secret: "your-client-secret".to_string(), -//! redirect_uri: "https://api.myapp.com/oauth/google/callback".to_string(), +//! redirect_callback_uri: "https://api.myapp.com/oauth/google/callback".to_string(), //! redirect_frontend_uri: "https://myapp.com/auth/callback".to_string(), //! additional_scopes: vec!["profile".to_string()], //! }; @@ -78,7 +78,8 @@ impl OAuth2Provider { /// /// # Examples /// - /// ```rust + /// ```rust,no_run + /// use authen::core::oauth::store::OAuth2Provider; /// let provider = OAuth2Provider::Google; /// assert_eq!(provider.display_name(), "Google"); /// ``` @@ -95,7 +96,8 @@ impl OAuth2Provider { /// /// # Examples /// - /// ```rust + /// ```rust,no_run + /// use authen::core::oauth::store::OAuth2Provider; /// let scopes = OAuth2Provider::Google.default_scopes(); /// assert!(scopes.contains(&"email")); /// ``` @@ -139,8 +141,17 @@ impl OAuth2Token { /// /// # Examples /// - /// ```rust - /// let token = OAuth2Token { /* ... */ }; + /// ```rust,no_run + /// use authen::core::oauth::store::{OAuth2Provider, OAuth2Token}; + /// let token = OAuth2Token { + /// access_token: "access".to_string(), + /// refresh_token: Some("refresh".to_string()), + /// expires_at: Some(chrono::Utc::now().naive_utc()), + /// token_type: "Bearer".to_string(), + /// scope: None, + /// provider: OAuth2Provider::Google, + /// created_at: chrono::Utc::now().naive_utc(), + /// }; /// let expired = token.is_expired(); /// ``` pub fn is_expired(&self) -> bool { @@ -160,8 +171,17 @@ impl OAuth2Token { /// /// # Examples /// - /// ```rust - /// let token = OAuth2Token { /* ... */ }; + /// ```rust,no_run + /// use authen::core::oauth::store::{OAuth2Provider, OAuth2Token}; + /// let token = OAuth2Token { + /// access_token: "access".to_string(), + /// refresh_token: Some("refresh".to_string()), + /// expires_at: Some(chrono::Utc::now().naive_utc()), + /// token_type: "Bearer".to_string(), + /// scope: None, + /// provider: OAuth2Provider::Google, + /// created_at: chrono::Utc::now().naive_utc(), + /// }; /// let soon = token.expires_soon(60); /// ``` pub fn expires_soon(&self, threshold_secs: u64) -> bool { @@ -241,8 +261,16 @@ impl OAuth2Config { /// /// # Examples /// - /// ```rust - /// let config = OAuth2Config { /* ... */ }; + /// ```rust,no_run + /// use authen::core::oauth::store::{OAuth2Config, OAuth2Provider}; + /// let config = OAuth2Config { + /// app_name: "My App".to_string(), + /// client_id: "your-client-id".to_string(), + /// client_secret: "your-client-secret".to_string(), + /// redirect_callback_uri: "https://api.myapp.com/oauth/google/callback".to_string(), + /// redirect_frontend_uri: "https://myapp.com/auth/callback".to_string(), + /// additional_scopes: vec!["profile".to_string()], + /// }; /// let url = config.auth_url(OAuth2Provider::Google); /// ``` pub fn auth_url(&self, provider: OAuth2Provider) -> &'static str { @@ -268,8 +296,16 @@ impl OAuth2Config { /// /// # Examples /// - /// ```rust - /// let config = OAuth2Config { /* ... */ }; + /// ```rust,no_run + /// use authen::core::oauth::store::{OAuth2Config, OAuth2Provider}; + /// let config = OAuth2Config { + /// app_name: "My App".to_string(), + /// client_id: "your-client-id".to_string(), + /// client_secret: "your-client-secret".to_string(), + /// redirect_callback_uri: "https://api.myapp.com/oauth/google/callback".to_string(), + /// redirect_frontend_uri: "https://myapp.com/auth/callback".to_string(), + /// additional_scopes: vec!["profile".to_string()], + /// }; /// let url = config.token_url(OAuth2Provider::Google); /// ``` pub fn token_url(&self, provider: OAuth2Provider) -> &'static str { @@ -295,8 +331,16 @@ impl OAuth2Config { /// /// # Examples /// - /// ```rust - /// let config = OAuth2Config { /* ... */ }; + /// ```rust,no_run + /// use authen::core::oauth::store::{OAuth2Config, OAuth2Provider}; + /// let config = OAuth2Config { + /// app_name: "My App".to_string(), + /// client_id: "your-client-id".to_string(), + /// client_secret: "your-client-secret".to_string(), + /// redirect_callback_uri: "https://api.myapp.com/oauth/google/callback".to_string(), + /// redirect_frontend_uri: "https://myapp.com/auth/callback".to_string(), + /// additional_scopes: vec!["profile".to_string()], + /// }; /// let url = config.user_info_url(OAuth2Provider::Google); /// ``` pub fn user_info_url(&self, provider: OAuth2Provider) -> &'static str { diff --git a/src/core/password/argon2.rs b/src/core/password/argon2.rs index 64f3483..60f26c8 100644 --- a/src/core/password/argon2.rs +++ b/src/core/password/argon2.rs @@ -5,10 +5,10 @@ //! //! # Example //! -//! ```rust -//! use crate::core::password::argon2::Argon2PasswordManager; -//! use crate::core::password::manager::SecurePasswordManager; -//! # tokio_test::block_on(async { +//! ```rust,no_run +//! use authen::core::password::argon2::Argon2PasswordManager; +//! use authen::core::password::manager::SecurePasswordManager; +//! # tokio::runtime::Runtime::new().unwrap().block_on(async { //! let manager = Argon2PasswordManager::default(); //! let password = "mysecret"; //! let hash = manager.hash_password(password).await.unwrap(); diff --git a/src/core/password/mod.rs b/src/core/password/mod.rs index 23507cb..58a7c80 100644 --- a/src/core/password/mod.rs +++ b/src/core/password/mod.rs @@ -15,13 +15,15 @@ //! //! # Example //! -//! ```rust +//! ```rust,no_run //! use authen::core::password::{Argon2PasswordManager, SecurePasswordManager}; //! +//! # tokio::runtime::Runtime::new().unwrap().block_on(async { //! let manager = Argon2PasswordManager::default(); //! let password = "mysecret"; -//! let hash = manager.hash_password(password)?; -//! assert!(manager.verify_password(password, &hash)?); +//! let hash = manager.hash_password(password).await.unwrap(); +//! assert!(manager.verify_password(password, &hash).await.unwrap()); +//! # }); //! ``` //! //! # Security diff --git a/src/core/policy/mod.rs b/src/core/policy/mod.rs index e34bcca..364756a 100644 --- a/src/core/policy/mod.rs +++ b/src/core/policy/mod.rs @@ -61,7 +61,7 @@ impl PasswordPolicy { /// * `Err(AuthError)` with a descriptive message if any requirement is not met. /// /// # Example - /// ```rust + /// ```rust,no_run /// use authen::core::policy::PasswordPolicy; /// let policy = PasswordPolicy::default(); /// assert!(policy.validate_password("Str0ng!Passw0rd").is_ok()); diff --git a/src/core/token/jwt.rs b/src/core/token/jwt.rs index a4d7939..5c7a597 100644 --- a/src/core/token/jwt.rs +++ b/src/core/token/jwt.rs @@ -9,8 +9,8 @@ //! - Custom error handling for token operations //! //! # Example -//! ```rust -//! use crate::core::token::jwt::JwtTokenService; +//! ```rust,no_run +//! use authen::core::token::jwt::JwtTokenService; //! let jwt_service = JwtTokenService::new("mysecret", 3600, 86400); //! ``` @@ -46,7 +46,8 @@ impl JwtTokenService { /// * `refresh_token_duration` - Refresh token validity duration in seconds. /// /// # Example - /// ```rust + /// ```rust,no_run + /// use authen::core::token::jwt::JwtTokenService; /// let service = JwtTokenService::new("mysecret", 3600, 86400); /// ``` pub fn new(secret: &str, access_token_duration: u64, refresh_token_duration: u64) -> Self { diff --git a/src/core/token/mod.rs b/src/core/token/mod.rs index 3ca20ab..466fa76 100644 --- a/src/core/token/mod.rs +++ b/src/core/token/mod.rs @@ -14,14 +14,15 @@ //! //! # Example //! -//! ```rust -//! use crate::core::token::{TokenService, TokenPair}; +//! ```rust,no_run +//! use authen::core::token::{TokenPair, TokenService}; +//! use authen::error::AuthError; //! # struct MyTokenService; //! # #[async_trait::async_trait] //! # impl TokenService for MyTokenService { -//! # async fn generate_token_pair(&self, user_id: &str) -> Result { todo!() } -//! # async fn validate_access_token(&self, token: &str) -> Result, crate::error::AuthError> { todo!() } -//! # async fn refresh_access_token(&self, refresh_token: &str) -> Result { todo!() } +//! # async fn generate_token_pair(&self, user_id: &str) -> Result { todo!() } +//! # async fn validate_access_token(&self, token: &str) -> Result, AuthError> { todo!() } +//! # async fn refresh_access_token(&self, refresh_token: &str) -> Result { todo!() } //! # } //! # async fn example() { //! let service = MyTokenService; @@ -60,14 +61,15 @@ pub struct TokenPair { /// /// # Example /// -/// ```rust -/// # use crate::core::token::{TokenService, TokenPair}; +/// ```rust,no_run +/// # use authen::core::token::{TokenPair, TokenService}; +/// # use authen::error::AuthError; /// # struct MyTokenService; /// # #[async_trait::async_trait] /// # impl TokenService for MyTokenService { -/// # async fn generate_token_pair(&self, user_id: &str) -> Result { todo!() } -/// # async fn validate_access_token(&self, token: &str) -> Result, crate::error::AuthError> { todo!() } -/// # async fn refresh_access_token(&self, refresh_token: &str) -> Result { todo!() } +/// # async fn generate_token_pair(&self, user_id: &str) -> Result { todo!() } +/// # async fn validate_access_token(&self, token: &str) -> Result, AuthError> { todo!() } +/// # async fn refresh_access_token(&self, refresh_token: &str) -> Result { todo!() } /// # } /// ``` #[async_trait::async_trait] diff --git a/src/core/user/mod.rs b/src/core/user/mod.rs index fce57d5..defa215 100644 --- a/src/core/user/mod.rs +++ b/src/core/user/mod.rs @@ -8,7 +8,7 @@ //! # Examples //! //! Creating a user with already hashed credentials: -//! ```rust +//! ```rust,no_run //! use authen::core::user::User; //! use authen::core::credentials::Credentials; //! let credentials = Credentials::default(); diff --git a/src/core/user/persistence/in_memory.rs b/src/core/user/persistence/in_memory.rs index 2daf481..da764ab 100644 --- a/src/core/user/persistence/in_memory.rs +++ b/src/core/user/persistence/in_memory.rs @@ -24,7 +24,8 @@ impl InMemoryUserRepo { /// /// # Examples /// - /// ``` + /// ```rust,no_run + /// use authen::core::user::persistence::InMemoryUserRepo; /// let repo = InMemoryUserRepo::new(); /// ``` pub fn new() -> Self { diff --git a/src/lib.rs b/src/lib.rs index 8f2cce1..fc06318 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ //! - `web`: Enables Axum web server integration for HTTP APIs. //! //! ## Example -//! ```rust +//! ```rust,no_run //! use authen::{AuthService, AuthenUser}; //! // ... //! ``` From 5afd4ae27aa567e7b94957c0928a0d4e4a400d3d Mon Sep 17 00:00:00 2001 From: Zied Yousfi Date: Sat, 7 Mar 2026 12:44:48 +0100 Subject: [PATCH 2/4] refactor(axum): modularize routes and auth context --- src/web_axum.rs | 1141 ------------------------------- src/web_axum/app.rs | 80 +++ src/web_axum/handlers/auth.rs | 48 ++ src/web_axum/handlers/mod.rs | 4 + src/web_axum/handlers/oauth.rs | 132 ++++ src/web_axum/handlers/system.rs | 13 + src/web_axum/handlers/token.rs | 48 ++ src/web_axum/middleware.rs | 125 ++++ src/web_axum/mod.rs | 59 ++ src/web_axum/models.rs | 197 ++++++ src/web_axum/response.rs | 67 ++ 11 files changed, 773 insertions(+), 1141 deletions(-) delete mode 100644 src/web_axum.rs create mode 100644 src/web_axum/app.rs create mode 100644 src/web_axum/handlers/auth.rs create mode 100644 src/web_axum/handlers/mod.rs create mode 100644 src/web_axum/handlers/oauth.rs create mode 100644 src/web_axum/handlers/system.rs create mode 100644 src/web_axum/handlers/token.rs create mode 100644 src/web_axum/middleware.rs create mode 100644 src/web_axum/mod.rs create mode 100644 src/web_axum/models.rs create mode 100644 src/web_axum/response.rs diff --git a/src/web_axum.rs b/src/web_axum.rs deleted file mode 100644 index c908d23..0000000 --- a/src/web_axum.rs +++ /dev/null @@ -1,1141 +0,0 @@ -//! Web server and HTTP API integration for Authen using Axum. -//! -//! This module provides the Axum-based web server and HTTP API endpoints for authentication, -//! user management, token operations, and OAuth2 integration. It exposes routes for signup, -//! login, health check, token refresh, token validation, OAuth2 authorization, and OAuth2 -//! callbacks, connecting them to the core authentication service. -//! -//! All endpoints expect and return JSON. Error responses are also JSON-encoded. -//! -//! # Features -//! - Only compiled and available with the `web` feature enabled. -//! - Designed for use with the `AuthService` abstraction. -//! - Full OAuth2 support for Google, GitHub, Discord, and Microsoft. -//! -//! # API Endpoints Reference -//! -//! ## Authentication Endpoints -//! -//! ### POST `/signup` -//! Create a new user account with username and password. -//! -//! **Request:** -//! ```json -//! { -//! "username": "john_doe", -//! "password": "secure_password123" -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "id": "user-uuid-here", -//! "identifier": "john_doe" -//! } -//! ``` -//! -//! **Error Response (400/500):** -//! ```json -//! { -//! "error": "Username already exists" -//! } -//! ``` -//! -//! ### POST `/login` -//! Authenticate user and receive access and refresh tokens. -//! -//! **Request:** -//! ```json -//! { -//! "username": "john_doe", -//! "password": "secure_password123" -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "id": "user-uuid-here", -//! "identifier": "john_doe", -//! "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", -//! "refresh_token": "refresh-token-string-here" -//! } -//! ``` -//! -//! **Error Response (401/400):** -//! ```json -//! { -//! "error": "Invalid credentials" -//! } -//! ``` -//! -//! ## Token Management -//! -//! ### POST `/token/refresh` -//! Refresh an expired access token using a valid refresh token. -//! -//! **Request:** -//! ```json -//! { -//! "refresh_token": "refresh-token-string-here" -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "access_token": "new-access-token-here", -//! "refresh_token": "new-or-same-refresh-token" -//! } -//! ``` -//! -//! ### POST `/token/validate` -//! Validate an access token and get user claims. -//! -//! **Request:** -//! ```json -//! { -//! "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "valid": true, -//! "claims": { -//! "sub": "user-uuid-here", -//! "exp": 1640995200 -//! } -//! } -//! ``` -//! -//! ## OAuth2 Endpoints -//! -//! ### GET `/oauth/{provider}/auth` -//! Generate OAuth2 authorization URL for the specified provider. -//! Supported providers: `google`, `github`, `discord`, `microsoft` -//! -//! **Query Parameters:** -//! - `state` (required): CSRF protection state parameter -//! - `scopes` (optional): Comma-separated additional scopes -//! -//! **Example Request:** -//! ``` -//! GET /oauth/google/auth?state=random-csrf-token&scopes=openid,email,profile -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=..." -//! } -//! ``` -//! -//! ### GET `/oauth/{provider}/callback` -//! OAuth2 callback endpoint (usually called by the provider after user authorization). -//! This endpoint automatically redirects users to the configured frontend URI with tokens -//! included in the URL fragment for security. -//! -//! **Query Parameters:** -//! - `code` (required): Authorization code from provider -//! - `state` (required): State parameter for CSRF verification -//! -//! **Example Request:** -//! ``` -//! GET /oauth/google/callback?code=auth-code-from-provider&state=random-csrf-token -//! ``` -//! -//! **Success Response:** -//! HTTP 302 Redirect to `{redirect_frontend_uri}#access_token=...&refresh_token=...&user_id=...&token_type=Bearer&expires_in=3600` -//! -//! **Error Response:** -//! HTTP 302 Redirect to `{redirect_frontend_uri}#error=authentication_failed&error_description=...` -//! -//! -//! ### POST `/oauth/signup` -//! Create a new user account using OAuth2 authorization code. -//! -//! **Request:** -//! ```json -//! { -//! "provider": "google", -//! "code": "authorization-code-from-provider", -//! "state": "random-csrf-token" -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "id": "user-uuid-here", -//! "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", -//! "refresh_token": "refresh-token-string-here", -//! "oauth_info": { -//! "provider": "google", -//! "email": "user@example.com", -//! "name": "John Doe" -//! } -//! } -//! ``` -//! -//! ### POST `/oauth/login` -//! Login with an existing OAuth2 account. -//! -//! **Request:** -//! ```json -//! { -//! "provider": "google", -//! "code": "authorization-code-from-provider", -//! "state": "random-csrf-token" -//! } -//! ``` -//! -//! **Success Response (200):** -//! ```json -//! { -//! "id": "user-uuid-here", -//! "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", -//! "refresh_token": "refresh-token-string-here" -//! } -//! ``` -//! -//! ## Health Check -//! -//! ### GET `/health` -//! Simple health check endpoint. -//! -//! **Response (200):** -//! ``` -//! OK -//! ``` -//! -//! # Client Usage Examples -//! -//! ## Basic Authentication Flow (JavaScript/TypeScript) -//! -//! ```javascript -//! // 1. Sign up a new user -//! const signupResponse = await fetch('http://localhost:3000/signup', { -//! method: 'POST', -//! headers: { 'Content-Type': 'application/json' }, -//! body: JSON.stringify({ -//! username: 'john_doe', -//! password: 'secure_password123' -//! }) -//! }); -//! const userData = await signupResponse.json(); -//! -//! // 2. Login and get tokens -//! const loginResponse = await fetch('http://localhost:3000/login', { -//! method: 'POST', -//! headers: { 'Content-Type': 'application/json' }, -//! body: JSON.stringify({ -//! username: 'john_doe', -//! password: 'secure_password123' -//! }) -//! }); -//! const { access_token, refresh_token } = await loginResponse.json(); -//! -//! // 3. Use access token for authenticated requests -//! const protectedResponse = await fetch('http://your-api.com/protected', { -//! headers: { 'Authorization': `Bearer ${access_token}` } -//! }); -//! -//! // 4. Refresh token when needed -//! const refreshResponse = await fetch('http://localhost:3000/token/refresh', { -//! method: 'POST', -//! headers: { 'Content-Type': 'application/json' }, -//! body: JSON.stringify({ refresh_token }) -//! }); -//! const { access_token: newAccessToken } = await refreshResponse.json(); -//! ``` -//! -//! ## OAuth2 Flow (JavaScript/TypeScript) -//! -//! ```javascript -//! // 1. Generate authorization URL -//! const state = crypto.randomUUID(); // Generate CSRF token -//! const authResponse = await fetch( -//! `http://localhost:3000/oauth/google/auth?state=${state}&scopes=openid,email,profile` -//! ); -//! const { authorization_url } = await authResponse.json(); -//! -//! // 2. Redirect user to authorization URL -//! window.location.href = authorization_url; -//! -//! // 3. Handle callback automatically via redirect -//! // The OAuth2 callback endpoint will automatically redirect the user back to your -//! // frontend application with tokens in the URL fragment. Your frontend should -//! // handle this redirect and extract tokens from the URL fragment: -//! -//! // Example frontend redirect handler (e.g., at your redirect_frontend_uri) -//! function handleOAuthCallback() { -//! const fragment = window.location.hash.substring(1); // Remove the '#' -//! const params = new URLSearchParams(fragment); -//! -//! if (params.has('error')) { -//! const error = params.get('error'); -//! const errorDescription = params.get('error_description'); -//! console.error('OAuth error:', error, errorDescription); -//! // Handle error -//! } else { -//! const accessToken = params.get('access_token'); -//! const refreshToken = params.get('refresh_token'); -//! const userId = params.get('user_id'); -//! -//! // Store tokens and proceed with authentication -//! localStorage.setItem('accessToken', accessToken); -//! localStorage.setItem('refreshToken', refreshToken); -//! localStorage.setItem('userId', userId); -//! -//! // Redirect to your app's main page or show success -//! window.location.href = '/dashboard'; -//! } -//! } -//! ``` -//! -//! ## cURL Examples -//! -//! ```bash -//! # Sign up -//! curl -X POST http://localhost:3000/signup \ -//! -H "Content-Type: application/json" \ -//! -d '{"username":"john_doe","password":"secure_password123"}' -//! -//! # Login -//! curl -X POST http://localhost:3000/login \ -//! -H "Content-Type: application/json" \ -//! -d '{"username":"john_doe","password":"secure_password123"}' -//! -//! # Refresh token -//! curl -X POST http://localhost:3000/token/refresh \ -//! -H "Content-Type: application/json" \ -//! -d '{"refresh_token":"your-refresh-token-here"}' -//! -//! # Validate token -//! curl -X POST http://localhost:3000/token/validate \ -//! -H "Content-Type: application/json" \ -//! -d '{"token":"your-access-token-here"}' -//! -//! # Get OAuth authorization URL -//! curl "http://localhost:3000/oauth/google/auth?state=csrf-token" -//! -//! # OAuth signup -//! curl -X POST http://localhost:3000/oauth/signup \ -//! -H "Content-Type: application/json" \ -//! -d '{"provider":"google","code":"auth-code","state":"csrf-token"}' -//! ``` -//! -//! # Error Handling -//! -//! All error responses follow this format: -//! ```json -//! { -//! "error": "Descriptive error message" -//! } -//! ``` -//! -//! Common HTTP status codes: -//! - `200`: Success -//! - `400`: Bad Request (invalid JSON, missing fields) -//! - `401`: Unauthorized (invalid credentials, expired token) -//! - `409`: Conflict (username already exists) -//! - `500`: Internal Server Error -//! -//! # Server Setup Example -//! ```no_run -//! use authen::auth_service::AuthService; -//! use authen::web_axum::start_server; -//! use std::sync::Arc; -//! # async fn run(auth_service: Arc) { -//! start_server(auth_service, None).await; -//! # } -//! ``` -use crate::auth_service::AuthService; -#[cfg(feature = "axum")] -use axum::{ - Json, Router, - extract::{Path, Query, State}, - response::{IntoResponse, Response}, -}; -use serde::Deserialize; -use std::sync::Arc; - -/// Simple URL encoding function for OAuth2 callback parameters -fn url_encode(s: &str) -> String { - s.bytes() - .map(|b| match b { - b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { - (b as char).to_string() - } - _ => format!("%{b:02X}"), - }) - .collect() -} - -#[cfg(feature = "axum")] -/// Starts the Axum web server with all authentication routes. -/// -/// Binds to `0.0.0.0:3000` and serves the API endpoints for signup, login, health check, -/// token refresh, token validation, and OAuth2 integration (authorization URLs, callbacks, -/// OAuth login/signup). This function does not return unless the server fails. -/// -/// # Arguments -/// * `auth_service` - An `Arc` to the shared `AuthService` instance used for all authentication logic. -/// -/// # Panics -/// Panics if the TCP listener cannot bind or the server fails to start. -pub async fn start_server( - auth_service: Arc, - address: impl Into>, -) { - use axum::serve; - use tokio::net::TcpListener; - let app = get_authen_axum_router(auth_service.clone()); - - let addr = address - .into() - .unwrap_or_else(|| std::net::SocketAddr::from(([0, 0, 0, 0], 3000))); - log::info!("Axum server running at http://{addr}"); - let listener = TcpListener::bind(addr).await.unwrap(); - serve(listener, app).await.unwrap(); -} - -#[cfg(feature = "axum")] -/// Returns an Axum `Router` with all Authen authentication routes registered. -/// -/// This is useful for integrating Authen's API into an existing Axum application or for testing. -/// Includes all authentication endpoints: credentials-based signup/login, token operations, -/// and OAuth2 integration with support for Google, GitHub, Discord, and Microsoft. -/// -/// # Arguments -/// * `auth_service` - An `Arc` to the shared `AuthService` instance. -/// -/// # Returns -/// An Axum `Router` with all authentication, token, and OAuth2 endpoints. -pub fn get_authen_axum_router(auth_service: Arc) -> Router { - use axum::routing::{get, post}; - Router::new() - .route("/signup", post(signup_handler)) - .route("/login", post(login_handler)) - .route("/health", get(health_handler).post(health_handler)) - .route("/token/refresh", post(refresh_token_handler)) - .route("/token/validate", post(validate_token_handler)) - .route("/oauth/{provider}/auth", get(oauth_auth_handler)) - .route("/oauth/{provider}/callback", get(oauth_callback_handler)) - .route("/oauth/signup", post(oauth_signup_handler)) - .route("/oauth/login", post(oauth_login_handler)) - .with_state(auth_service) -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/signup` endpoint. -/// -/// Accepts a JSON body with `username` and `password` fields, creates a new user, -/// and returns the user ID and identifier on success. On error, returns a JSON error message. -/// -/// # Request JSON -/// ```json -/// { "username": "string", "password": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "id": "...", "identifier": "..." }` -/// - Error: `{ "error": "..." }` -async fn signup_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /signup request: {_body}"); - #[derive(Deserialize)] - struct SignupRequest { - username: String, - password: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(signup) => { - log::info!( - "Attempting signup for user: {username}", - username = signup.username - ); - // Use the new unified signup method with credentials - match _auth - .signup(crate::auth_service::SignupMethod::Credentials { - identifier: signup.username, - password: signup.password, - }) - .await - { - Ok((user, _tokens)) => { - log::info!("Signup successful for user_id: {id}", id = user.id); - serde_json::json!({ - "id": user.id, - "identifier": user.credentials.as_ref().map(|c| &c.identifier).unwrap_or(&"".to_string()) - }) - .to_string().into_response() - } - Err(e) => { - log::error!("Signup failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid signup request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {e}") - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/login` endpoint. -/// -/// Accepts a JSON body with `username` and `password` fields, authenticates the user, -/// and returns user info and tokens on success. On error, returns a JSON error message. -/// -/// # Request JSON -/// ```json -/// { "username": "string", "password": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "id": "...", "identifier": "...", "access_token": "...", "refresh_token": "..." }` -/// - Error: `{ "error": "..." }` -async fn login_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /login request: {_body}"); - #[derive(Deserialize)] - struct LoginRequest { - username: String, - password: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(login) => { - log::info!( - "Attempting login for user: {username}", - username = login.username - ); - match _auth - .login(crate::auth_service::LoginMethod::Credentials { - identifier: login.username, - password: login.password, - }) - .await - { - Ok((user, tokens)) => { - log::info!("Login successful for user_id: {id}", id = user.id); - serde_json::json!({ - "id": user.id, - "identifier": user.credentials.as_ref().map(|c| &c.identifier).unwrap_or(&"".to_string()), - "access_token": tokens.access_token, - "refresh_token": tokens.refresh_token - }) - .to_string().into_response() - } - Err(e) => { - log::error!("Login failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid login request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {e}") - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/health` endpoint. -/// -/// Returns a simple "OK" string for health checks. -async fn health_handler() -> String { - log::info!("Received /health request"); - "OK".to_string() -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/token/refresh` endpoint. -/// -/// Accepts a JSON body with a `refresh_token` field, and returns new access and refresh tokens -/// if the refresh token is valid. On error, returns a JSON error message. -/// -/// # Request JSON -/// ```json -/// { "refresh_token": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "access_token": "...", "refresh_token": "..." }` -/// - Error: `{ "error": "..." }` -async fn refresh_token_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /token/refresh request: {_body}"); - #[derive(Deserialize)] - struct RefreshRequest { - refresh_token: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(refresh) => { - log::info!("Attempting token refresh"); - match _auth.refresh_access_token(&refresh.refresh_token).await { - Ok(tokens) => { - log::info!("Token refresh successful"); - serde_json::json!({ - "access_token": tokens.access_token, - "refresh_token": tokens.refresh_token - }) - .to_string() - .into_response() - } - Err(e) => { - log::error!("Token refresh failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid refresh token request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {}", e) - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/token/validate` endpoint. -/// -/// Accepts a JSON body with a `token` field, validates the access token, and returns -/// claims information if valid. On error, returns a JSON error message. -/// -/// # Request JSON -/// ```json -/// { "token": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "valid": true, "subject": "...", "expiration": ... }` -/// - Error: `{ "valid": false, "error": "..." }` -async fn validate_token_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /token/validate request: {_body}"); - #[derive(Deserialize)] - struct ValidateRequest { - token: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(validate) => { - log::info!("Validating access token"); - match _auth.validate_access_token(&validate.token).await { - Ok(claims) => { - log::info!( - "Token valid for subject: {subject}", - subject = claims.get_subject() - ); - serde_json::json!({ - "valid": true, - "subject": claims.get_subject(), - "expiration": claims.get_expiration() - }) - .to_string() - .into_response() - } - Err(e) => { - log::error!("Token validation failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "valid": false, - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid validate token request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {}", e) - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/oauth/{provider}/auth` endpoint. -/// -/// Generates an OAuth2 authorization URL for the specified provider. -/// Accepts query parameters for state and optional scopes. -/// -/// # Query Parameters -/// - `state`: Required state parameter for CSRF protection -/// - `scopes`: Optional comma-separated list of additional scopes -/// -/// # Response JSON -/// - Success: `{ "auth_url": "..." }` -/// - Error: `{ "error": "..." }` -async fn oauth_auth_handler( - State(_auth): State>, - Path(provider_str): Path, - Query(params): Query>, -) -> Response { - log::info!("Received /oauth/{provider_str}/auth request with params: {params:?}"); - // Parse the provider from the path parameter - let provider = match provider_str.to_lowercase().as_str() { - "google" => crate::core::oauth::store::OAuth2Provider::Google, - "github" => crate::core::oauth::store::OAuth2Provider::GitHub, - "discord" => crate::core::oauth::store::OAuth2Provider::Discord, - "microsoft" => crate::core::oauth::store::OAuth2Provider::Microsoft, - _ => { - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Unsupported OAuth2 provider: {}", provider_str) - }) - .to_string(), - ) - .into_response(); - } - }; - - // Get the state parameter (required) - let state = match params.get("state") { - Some(state) => state, - None => { - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": "Missing required 'state' parameter" - }) - .to_string(), - ) - .into_response(); - } - }; - - // Parse optional scopes parameter - let scopes = params - .get("scopes") - .map(|s| s.split(',').map(|scope| scope.trim().to_string()).collect()); - - // Generate the OAuth2 authorization URL - match _auth - .generate_oauth2_auth_url(provider, state, scopes) - .await - { - Ok(auth_url) => { - log::info!("Generated OAuth2 auth URL for provider: {provider_str}"); - serde_json::json!({ - "auth_url": auth_url - }) - .to_string() - .into_response() - } - Err(e) => { - log::error!("OAuth2 auth URL generation failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/oauth/{provider}/callback` endpoint. -/// -/// Handles OAuth2 callback from providers. This endpoint would typically be called -/// by the OAuth2 provider after user authorization. Redirects user to the frontend -/// application with tokens included in the URL fragment. -/// -/// # Query Parameters -/// - `code`: The authorization code from the provider -/// - `state`: The state parameter for CSRF verification -/// -/// # Response -/// - Success: HTTP 302 redirect to frontend URI with tokens in URL fragment -/// - Error: JSON error response -async fn oauth_callback_handler( - State(_auth): State>, - Path(provider_str): Path, - Query(params): Query>, -) -> Response { - log::info!("Received /oauth/{provider_str}/callback request with params: {params:?}"); - // Parse the provider from the path parameter - let provider = match provider_str.to_lowercase().as_str() { - "google" => crate::core::oauth::store::OAuth2Provider::Google, - "github" => crate::core::oauth::store::OAuth2Provider::GitHub, - "discord" => crate::core::oauth::store::OAuth2Provider::Discord, - "microsoft" => crate::core::oauth::store::OAuth2Provider::Microsoft, - _ => { - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Unsupported OAuth2 provider: {}", provider_str) - }) - .to_string(), - ) - .into_response(); - } - }; - - // Get required parameters - let code = match params.get("code") { - Some(code) => code, - None => { - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": "Missing required 'code' parameter" - }) - .to_string(), - ) - .into_response(); - } - }; - - let state = match params.get("state") { - Some(state) => state, - None => { - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": "Missing required 'state' parameter" - }) - .to_string(), - ) - .into_response(); - } - }; - - // Get the frontend redirect URI for this provider - let frontend_uri = match _auth.get_oauth2_redirect_frontend_uri(provider).await { - Ok(uri) => uri, - Err(e) => { - log::error!("Failed to get frontend redirect URI: {e}"); - return ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - serde_json::json!({ - "error": format!("Configuration error: {e}") - }) - .to_string(), - ) - .into_response(); - } - }; - - // Use the login method with OAuth2 - match _auth - .login(crate::auth_service::LoginMethod::OAuth2 { - provider, - code: code.clone(), - state: state.clone(), - }) - .await - { - Ok((user, tokens)) => { - log::info!( - "OAuth2 callback login successful for user_id: {id}", - id = user.id - ); - - // Build the redirect URL with tokens in the fragment - let redirect_url = format!( - "{}#access_token={}&refresh_token={}&user_id={}&token_type=Bearer&expires_in=3600", - frontend_uri, - url_encode(&tokens.access_token), - url_encode(&tokens.refresh_token), - url_encode(&user.id) - ); - - log::info!("Redirecting user to frontend: {redirect_url}"); - - // Return HTTP 302 redirect - axum::response::Redirect::permanent(&redirect_url).into_response() - } - Err(e) => { - log::error!("OAuth2 callback login failed: {e}"); - - // Redirect to frontend with error - let error_redirect_url = format!( - "{}#error={}&error_description={}", - frontend_uri, - url_encode("authentication_failed"), - url_encode(&e.to_string()) - ); - - log::info!("Redirecting user to frontend with error: {error_redirect_url}"); - axum::response::Redirect::permanent(&error_redirect_url).into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/oauth/signup` endpoint. -/// -/// Handles OAuth2 signup/registration using an authorization code. -/// Creates a new user account or links to existing account. -/// -/// # Request JSON -/// ```json -/// { "provider": "google|github|discord|microsoft", "code": "string", "state": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "id": "...", "access_token": "...", "refresh_token": "..." }` -/// - Error: `{ "error": "..." }` -async fn oauth_signup_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /oauth/signup request: {_body}"); - #[derive(Deserialize)] - struct OAuth2SignupRequest { - provider: String, - code: String, - state: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(signup) => { - log::info!( - "Attempting OAuth2 signup for provider: {provider}", - provider = signup.provider - ); - // Parse the provider - let provider = match signup.provider.to_lowercase().as_str() { - "google" => crate::core::oauth::store::OAuth2Provider::Google, - "github" => crate::core::oauth::store::OAuth2Provider::GitHub, - "discord" => crate::core::oauth::store::OAuth2Provider::Discord, - "microsoft" => crate::core::oauth::store::OAuth2Provider::Microsoft, - _ => { - log::error!( - "Unsupported OAuth2 provider: {provider}", - provider = signup.provider - ); - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Unsupported OAuth2 provider: {}", signup.provider) - }) - .to_string(), - ) - .into_response(); - } - }; - - // Use the unified signup method with OAuth2 - match _auth - .signup(crate::auth_service::SignupMethod::OAuth2 { - provider, - code: signup.code, - state: signup.state, - }) - .await - { - Ok((user, tokens)) => { - log::info!("OAuth2 signup successful for user_id: {id}", id = user.id); - serde_json::json!({ - "id": user.id, - "access_token": tokens.access_token, - "refresh_token": tokens.refresh_token - }) - .to_string() - .into_response() - } - Err(e) => { - log::error!("OAuth2 signup failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid OAuth2 signup request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {}", e) - }) - .to_string(), - ) - .into_response() - } - } -} - -#[cfg(feature = "axum")] -/// HTTP handler for the `/oauth/login` endpoint. -/// -/// Handles OAuth2 login using an authorization code. -/// Authenticates existing user or creates account if needed. -/// -/// # Request JSON -/// ```json -/// { "provider": "google|github|discord|microsoft", "code": "string", "state": "string" } -/// ``` -/// -/// # Response JSON -/// - Success: `{ "id": "...", "access_token": "...", "refresh_token": "..." }` -/// - Error: `{ "error": "..." }` -async fn oauth_login_handler( - State(_auth): State>, - Json(_body): Json, -) -> Response { - log::info!("Received /oauth/login request: {_body}"); - #[derive(Deserialize)] - struct OAuth2LoginRequest { - provider: String, - code: String, - state: String, - } - - let req: Result = serde_json::from_value(_body); - match req { - Ok(login) => { - log::info!( - "Attempting OAuth2 login for provider: {provider}", - provider = login.provider - ); - // Parse the provider - let provider = match login.provider.to_lowercase().as_str() { - "google" => crate::core::oauth::store::OAuth2Provider::Google, - "github" => crate::core::oauth::store::OAuth2Provider::GitHub, - "discord" => crate::core::oauth::store::OAuth2Provider::Discord, - "microsoft" => crate::core::oauth::store::OAuth2Provider::Microsoft, - _ => { - log::error!( - "Unsupported OAuth2 provider: {provider}", - provider = login.provider - ); - return ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Unsupported OAuth2 provider: {}", login.provider) - }) - .to_string(), - ) - .into_response(); - } - }; - - // Use the unified login method with OAuth2 - match _auth - .login(crate::auth_service::LoginMethod::OAuth2 { - provider, - code: login.code, - state: login.state, - }) - .await - { - Ok((user, tokens)) => { - log::info!("OAuth2 login successful for user_id: {id}", id = user.id); - serde_json::json!({ - "id": user.id, - "access_token": tokens.access_token, - "refresh_token": tokens.refresh_token - }) - .to_string() - .into_response() - } - Err(e) => { - log::error!("OAuth2 login failed: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": e.to_string() - }) - .to_string(), - ) - .into_response() - } - } - } - Err(e) => { - log::error!("Invalid OAuth2 login request body: {e}"); - ( - axum::http::StatusCode::BAD_REQUEST, - serde_json::json!({ - "error": format!("Invalid request body: {}", e) - }) - .to_string(), - ) - .into_response() - } - } -} diff --git a/src/web_axum/app.rs b/src/web_axum/app.rs new file mode 100644 index 0000000..3be7212 --- /dev/null +++ b/src/web_axum/app.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use axum::{ + Router, middleware, + routing::{get, post}, +}; + +use crate::auth_service::AuthService; + +use super::{handlers, middleware::auth_context_middleware}; + +/// Starts the Axum web server with all Authen routes registered. +/// +/// The server binds to `0.0.0.0:3000` by default and serves credential-based +/// authentication, token utilities, OAuth2 endpoints, and the authenticated +/// user endpoint powered by auth-context middleware. +/// +/// # Arguments +/// - `auth_service`: Shared authentication service used by all handlers. +/// - `address`: Optional socket address override. When omitted, the server uses +/// `0.0.0.0:3000`. +/// +/// # Panics +/// Panics if the TCP listener cannot bind or the Axum server fails to start. +pub async fn start_server( + auth_service: Arc, + address: impl Into>, +) { + use axum::serve; + use tokio::net::TcpListener; + + let app = get_authen_axum_router(auth_service.clone()); + let addr = address + .into() + .unwrap_or_else(|| std::net::SocketAddr::from(([0, 0, 0, 0], 3000))); + + log::info!("Axum server running at http://{addr}"); + + let listener = TcpListener::bind(addr).await.unwrap(); + serve(listener, app).await.unwrap(); +} + +/// Returns an Axum router with Authen's HTTP API mounted. +/// +/// The returned router includes the full authentication surface as well as the +/// auth-context middleware that resolves a bearer token into [`super::AuthenticatedUser`] +/// and makes it available to downstream handlers through request extensions. +/// +/// # Registered routes +/// - `POST /signup` +/// - `POST /login` +/// - `GET|POST /health` +/// - `GET /me` +/// - `POST /token/refresh` +/// - `POST /token/validate` +/// - `GET /oauth/{provider}/auth` +/// - `GET /oauth/{provider}/callback` +/// - `POST /oauth/signup` +/// - `POST /oauth/login` +pub fn get_authen_axum_router(auth_service: Arc) -> Router { + let auth_context_layer = + middleware::from_fn_with_state(auth_service.clone(), auth_context_middleware); + + Router::new() + .route("/signup", post(handlers::auth::signup)) + .route("/login", post(handlers::auth::login)) + .route( + "/health", + get(handlers::system::health).post(handlers::system::health), + ) + .route("/me", get(handlers::system::me)) + .route("/token/refresh", post(handlers::token::refresh)) + .route("/token/validate", post(handlers::token::validate)) + .route("/oauth/{provider}/auth", get(handlers::oauth::authorize)) + .route("/oauth/{provider}/callback", get(handlers::oauth::callback)) + .route("/oauth/signup", post(handlers::oauth::signup)) + .route("/oauth/login", post(handlers::oauth::login)) + .layer(auth_context_layer) + .with_state(auth_service) +} diff --git a/src/web_axum/handlers/auth.rs b/src/web_axum/handlers/auth.rs new file mode 100644 index 0000000..f421f4a --- /dev/null +++ b/src/web_axum/handlers/auth.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use axum::{ + Json, + extract::{State, rejection::JsonRejection}, +}; + +use crate::{ + auth_service::{AuthService, LoginMethod, SignupMethod}, + web_axum::{ + models::{CredentialsRequest, LoginResponse, UserResponse}, + response::{ApiError, ApiResult}, + }, +}; + +pub(crate) async fn signup( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + + let (user, _) = auth_service + .signup(SignupMethod::Credentials { + identifier: payload.username, + password: payload.password, + }) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(UserResponse::from(user))) +} + +pub(crate) async fn login( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + + let (user, tokens) = auth_service + .login(LoginMethod::Credentials { + identifier: payload.username, + password: payload.password, + }) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(LoginResponse::from_user_and_tokens(user, tokens))) +} diff --git a/src/web_axum/handlers/mod.rs b/src/web_axum/handlers/mod.rs new file mode 100644 index 0000000..5cf57be --- /dev/null +++ b/src/web_axum/handlers/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod auth; +pub(crate) mod oauth; +pub(crate) mod system; +pub(crate) mod token; diff --git a/src/web_axum/handlers/oauth.rs b/src/web_axum/handlers/oauth.rs new file mode 100644 index 0000000..36d3df0 --- /dev/null +++ b/src/web_axum/handlers/oauth.rs @@ -0,0 +1,132 @@ +use std::sync::Arc; + +use axum::{ + Json, + extract::{ + Path, Query, State, + rejection::{JsonRejection, QueryRejection}, + }, + response::Redirect, +}; + +use crate::{ + auth_service::{AuthService, LoginMethod, SignupMethod}, + web_axum::{ + models::{ + LoginResponse, OAuthAuthQuery, OAuthAuthUrlResponse, OAuthCallbackQuery, + OAuthCodeRequest, TokenPairResponse, parse_oauth_provider, + }, + response::{ApiError, ApiResult}, + }, +}; + +pub(crate) async fn authorize( + State(auth_service): State>, + Path(provider): Path, + query: Result, QueryRejection>, +) -> ApiResult { + let Query(query) = query.map_err(ApiError::from_query_rejection)?; + let provider = parse_oauth_provider(&provider)?; + let scopes = query.scopes(); + + let auth_url = auth_service + .generate_oauth2_auth_url(provider, &query.state, scopes) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(OAuthAuthUrlResponse { auth_url })) +} + +pub(crate) async fn callback( + State(auth_service): State>, + Path(provider): Path, + query: Result, QueryRejection>, +) -> Result { + let Query(query) = query.map_err(ApiError::from_query_rejection)?; + let provider = parse_oauth_provider(&provider)?; + let frontend_uri = auth_service + .get_oauth2_redirect_frontend_uri(provider) + .await + .map_err(ApiError::internal)?; + + match auth_service + .login(LoginMethod::OAuth2 { + provider, + code: query.code, + state: query.state, + }) + .await + { + Ok((user, tokens)) => { + let redirect_url = format!( + "{}#access_token={}&refresh_token={}&user_id={}&token_type=Bearer&expires_in=3600", + frontend_uri, + url_encode(&tokens.access_token), + url_encode(&tokens.refresh_token), + url_encode(&user.id) + ); + + Ok(Redirect::permanent(&redirect_url)) + } + Err(error) => { + let redirect_url = format!( + "{}#error={}&error_description={}", + frontend_uri, + url_encode("authentication_failed"), + url_encode(&error.to_string()) + ); + + Ok(Redirect::permanent(&redirect_url)) + } + } +} + +pub(crate) async fn signup( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + let provider = parse_oauth_provider(&payload.provider)?; + + let (user, tokens) = auth_service + .signup(SignupMethod::OAuth2 { + provider, + code: payload.code, + state: payload.state, + }) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(TokenPairResponse::from_user_and_tokens(user, tokens))) +} + +pub(crate) async fn login( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + let provider = parse_oauth_provider(&payload.provider)?; + + let (user, tokens) = auth_service + .login(LoginMethod::OAuth2 { + provider, + code: payload.code, + state: payload.state, + }) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(LoginResponse::from_user_and_tokens(user, tokens))) +} + +fn url_encode(value: &str) -> String { + value + .bytes() + .map(|byte| match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + (byte as char).to_string() + } + _ => format!("%{byte:02X}"), + }) + .collect() +} diff --git a/src/web_axum/handlers/system.rs b/src/web_axum/handlers/system.rs new file mode 100644 index 0000000..f31ab05 --- /dev/null +++ b/src/web_axum/handlers/system.rs @@ -0,0 +1,13 @@ +use axum::Json; + +use crate::web_axum::{ + middleware::AuthenticatedUser, models::UserProfileResponse, response::ApiResult, +}; + +pub(crate) async fn health() -> &'static str { + "OK" +} + +pub(crate) async fn me(user: AuthenticatedUser) -> ApiResult { + Ok(Json(UserProfileResponse::from(user.0))) +} diff --git a/src/web_axum/handlers/token.rs b/src/web_axum/handlers/token.rs new file mode 100644 index 0000000..92e8513 --- /dev/null +++ b/src/web_axum/handlers/token.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use axum::{ + Json, + extract::{State, rejection::JsonRejection}, +}; + +use crate::{ + auth_service::AuthService, + web_axum::{ + models::{ + RefreshTokenRequest, TokenPairResponse, ValidateTokenRequest, ValidateTokenResponse, + }, + response::{ApiError, ApiResult}, + }, +}; + +pub(crate) async fn refresh( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + + let tokens = auth_service + .refresh_access_token(&payload.refresh_token) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(TokenPairResponse::from(tokens))) +} + +pub(crate) async fn validate( + State(auth_service): State>, + payload: Result, JsonRejection>, +) -> ApiResult { + let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; + + let claims = auth_service + .validate_access_token(&payload.token) + .await + .map_err(ApiError::bad_request)?; + + Ok(Json(ValidateTokenResponse { + valid: true, + subject: claims.get_subject().to_string(), + expiration: claims.get_expiration(), + })) +} diff --git a/src/web_axum/middleware.rs b/src/web_axum/middleware.rs new file mode 100644 index 0000000..956a6d9 --- /dev/null +++ b/src/web_axum/middleware.rs @@ -0,0 +1,125 @@ +use std::{ops::Deref, sync::Arc}; + +use axum::{ + extract::{FromRequestParts, Request, State}, + http::{HeaderMap, StatusCode, header}, + middleware::Next, + response::Response, +}; + +use crate::{auth_service::AuthService, core::user::User}; + +use super::response::ApiError; + +/// Extractor for a required authenticated user. +/// +/// The value is attached by [`auth_context_middleware`] after reading an +/// `Authorization: Bearer ` header and resolving the user through the +/// [`AuthService`]. Handlers that include this extractor automatically return a +/// `401 Unauthorized` response when authentication is missing or invalid. +#[derive(Clone, Debug)] +pub struct AuthenticatedUser(pub User); + +/// Extractor for an optional authenticated user. +/// +/// This is useful for mixed public/private routes that can adapt their behavior +/// when a valid bearer token is present. +#[derive(Clone, Debug, Default)] +pub struct OptionalAuthenticatedUser(pub Option); + +#[derive(Clone, Debug)] +struct AuthenticationFailure { + message: String, +} + +impl Deref for AuthenticatedUser { + type Target = User; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromRequestParts for AuthenticatedUser +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts( + parts: &mut axum::http::request::Parts, + _state: &S, + ) -> Result { + if let Some(user) = parts.extensions.get::().cloned() { + return Ok(user); + } + + if let Some(error) = parts.extensions.get::() { + return Err(ApiError::new( + StatusCode::UNAUTHORIZED, + error.message.clone(), + )); + } + + Err(ApiError::unauthorized("Authentication required")) + } +} + +impl FromRequestParts for OptionalAuthenticatedUser +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts( + parts: &mut axum::http::request::Parts, + _state: &S, + ) -> Result { + Ok(Self( + parts + .extensions + .get::() + .cloned() + .map(|user| user.0), + )) + } +} + +/// Middleware that resolves the current authenticated user from a bearer token. +/// +/// When the request includes `Authorization: Bearer `, the middleware +/// attempts to load the matching user with [`AuthService::get_user_from_token`]. +/// On success, an [`AuthenticatedUser`] is inserted into request extensions. +/// On failure, the error is recorded so a later [`AuthenticatedUser`] extractor +/// can return a consistent `401 Unauthorized` response. +pub async fn auth_context_middleware( + State(auth_service): State>, + mut request: Request, + next: Next, +) -> Response { + if let Some(token) = bearer_token(request.headers()) { + match auth_service.get_user_from_token(token).await { + Ok(user) => { + request.extensions_mut().insert(AuthenticatedUser(user)); + } + Err(error) => { + request.extensions_mut().insert(AuthenticationFailure { + message: error.to_string(), + }); + } + } + } + + next.run(request).await +} + +fn bearer_token(headers: &HeaderMap) -> Option<&str> { + let header_value = headers.get(header::AUTHORIZATION)?.to_str().ok()?; + let token = header_value.strip_prefix("Bearer ")?.trim(); + + if token.is_empty() { + return None; + } + + Some(token) +} diff --git a/src/web_axum/mod.rs b/src/web_axum/mod.rs new file mode 100644 index 0000000..3279515 --- /dev/null +++ b/src/web_axum/mod.rs @@ -0,0 +1,59 @@ +//! Web server and HTTP API integration for Authen using Axum. +//! +//! This module provides the Axum-based web server and HTTP API endpoints for +//! authentication, user management, token operations, and OAuth2 integration. +//! It exposes routes for signup, login, health checks, token refresh, token +//! validation, OAuth2 authorization, OAuth2 callbacks, and authenticated user +//! resolution through middleware. +//! +//! All endpoint responses are JSON except for the OAuth callback route, which +//! redirects to the configured frontend URI with authentication details in the +//! URL fragment. +//! +//! # Features +//! - Available when the `axum` feature is enabled. +//! - Designed around the shared [`crate::AuthService`] abstraction. +//! - Includes auth-context middleware for bearer-token user resolution. +//! - Supports OAuth2 flows for Google, GitHub, Discord, and Microsoft. +//! +//! # Routes +//! - `POST /signup`: Register a user with username and password. +//! - `POST /login`: Authenticate a user and issue access and refresh tokens. +//! - `GET|POST /health`: Health check endpoint. +//! - `GET /me`: Return the authenticated user resolved from a bearer token. +//! - `POST /token/refresh`: Refresh an access token with a refresh token. +//! - `POST /token/validate`: Validate an access token and inspect claims. +//! - `GET /oauth/{provider}/auth`: Generate an OAuth2 authorization URL. +//! - `GET /oauth/{provider}/callback`: Complete OAuth login and redirect. +//! - `POST /oauth/signup`: Create a user from an OAuth2 authorization code. +//! - `POST /oauth/login`: Log in with an OAuth2 authorization code. +//! +//! # Middleware usage +//! Use [`AuthenticatedUser`] in a handler signature to require authentication, +//! or [`OptionalAuthenticatedUser`] to read the current user when present. +//! +//! ```no_run +//! use authen::{AuthService, web_axum::{AuthenticatedUser, get_authen_axum_router}}; +//! use axum::{Json, Router, routing::get}; +//! use std::sync::Arc; +//! +//! async fn protected_route(user: AuthenticatedUser) -> Json { +//! Json(user.id.clone()) +//! } +//! +//! # fn build_router() { +//! let auth_service = Arc::new(AuthService::default()); +//! let app: Router = get_authen_axum_router(auth_service) +//! .route("/protected", get(protected_route)); +//! # let _ = app; +//! # } +//! ``` + +mod app; +mod handlers; +mod middleware; +mod models; +mod response; + +pub use app::{get_authen_axum_router, start_server}; +pub use middleware::{AuthenticatedUser, OptionalAuthenticatedUser, auth_context_middleware}; diff --git a/src/web_axum/models.rs b/src/web_axum/models.rs new file mode 100644 index 0000000..21336f3 --- /dev/null +++ b/src/web_axum/models.rs @@ -0,0 +1,197 @@ +use chrono::NaiveDateTime; +use serde::{Deserialize, Serialize}; + +use crate::{ + core::{oauth::store::OAuth2Provider, token::TokenPair, user::User}, + error::AuthError, +}; + +use super::response::ApiError; + +#[derive(Deserialize)] +pub(crate) struct CredentialsRequest { + pub username: String, + pub password: String, +} + +#[derive(Deserialize)] +pub(crate) struct RefreshTokenRequest { + pub refresh_token: String, +} + +#[derive(Deserialize)] +pub(crate) struct ValidateTokenRequest { + pub token: String, +} + +#[derive(Deserialize)] +pub(crate) struct OAuthAuthQuery { + pub state: String, + pub scopes: Option, +} + +impl OAuthAuthQuery { + pub fn scopes(&self) -> Option> { + self.scopes.as_ref().map(|value| { + value + .split(',') + .map(str::trim) + .filter(|scope| !scope.is_empty()) + .map(ToOwned::to_owned) + .collect() + }) + } +} + +#[derive(Deserialize)] +pub(crate) struct OAuthCallbackQuery { + pub code: String, + pub state: String, +} + +#[derive(Deserialize)] +pub(crate) struct OAuthCodeRequest { + pub provider: String, + pub code: String, + pub state: String, +} + +#[derive(Serialize)] +pub(crate) struct UserResponse { + pub id: String, + pub identifier: Option, +} + +impl From for UserResponse { + fn from(user: User) -> Self { + Self { + id: user.id, + identifier: user.credentials.map(|credentials| credentials.identifier), + } + } +} + +#[derive(Serialize)] +pub(crate) struct LoginResponse { + pub id: String, + pub identifier: Option, + pub access_token: String, + pub refresh_token: String, +} + +impl LoginResponse { + pub fn from_user_and_tokens(user: User, tokens: TokenPair) -> Self { + Self { + id: user.id, + identifier: user.credentials.map(|credentials| credentials.identifier), + access_token: tokens.access_token, + refresh_token: tokens.refresh_token, + } + } +} + +#[derive(Serialize)] +pub(crate) struct TokenPairResponse { + pub access_token: String, + pub refresh_token: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, +} + +impl From for TokenPairResponse { + fn from(tokens: TokenPair) -> Self { + Self { + access_token: tokens.access_token, + refresh_token: tokens.refresh_token, + id: None, + } + } +} + +impl TokenPairResponse { + pub fn from_user_and_tokens(user: User, tokens: TokenPair) -> Self { + Self { + access_token: tokens.access_token, + refresh_token: tokens.refresh_token, + id: Some(user.id), + } + } +} + +#[derive(Serialize)] +pub(crate) struct ValidateTokenResponse { + pub valid: bool, + pub subject: String, + pub expiration: usize, +} + +#[derive(Serialize)] +pub(crate) struct OAuthAuthUrlResponse { + pub auth_url: String, +} + +#[derive(Serialize)] +pub(crate) struct UserProfileResponse { + pub id: String, + pub identifier: Option, + pub oauth_accounts: Vec, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +impl From for UserProfileResponse { + fn from(user: User) -> Self { + let mut oauth_accounts: Vec<_> = user + .oauth_accounts + .into_values() + .map(OAuthAccountResponse::from) + .collect(); + + oauth_accounts.sort_by_key(|account| account.provider.display_name()); + + Self { + id: user.id, + identifier: user.credentials.map(|credentials| credentials.identifier), + oauth_accounts, + created_at: user.created_at, + updated_at: user.updated_at, + } + } +} + +#[derive(Serialize)] +pub(crate) struct OAuthAccountResponse { + pub provider: OAuth2Provider, + pub provider_user_id: String, + pub email: Option, + pub name: Option, + pub avatar_url: Option, + pub verified_email: Option, + pub locale: Option, +} + +impl From for OAuthAccountResponse { + fn from(info: crate::core::oauth::store::OAuth2UserInfo) -> Self { + Self { + provider: info.provider, + provider_user_id: info.provider_user_id, + email: info.email, + name: info.name, + avatar_url: info.avatar_url, + verified_email: info.verified_email, + locale: info.locale, + } + } +} + +pub(crate) fn parse_oauth_provider(value: &str) -> Result { + match value.to_lowercase().as_str() { + "google" => Ok(OAuth2Provider::Google), + "github" => Ok(OAuth2Provider::GitHub), + "discord" => Ok(OAuth2Provider::Discord), + "microsoft" => Ok(OAuth2Provider::Microsoft), + _ => Err(ApiError::bad_request(AuthError::InvalidInput(format!( + "Unsupported OAuth2 provider: {value}" + )))), + } +} diff --git a/src/web_axum/response.rs b/src/web_axum/response.rs new file mode 100644 index 0000000..e9bcb98 --- /dev/null +++ b/src/web_axum/response.rs @@ -0,0 +1,67 @@ +use axum::{ + Json, + extract::rejection::{JsonRejection, QueryRejection}, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::Serialize; + +pub(crate) type ApiResult = Result, ApiError>; + +#[derive(Debug)] +pub struct ApiError { + status: StatusCode, + message: String, +} + +impl ApiError { + pub fn new(status: StatusCode, message: impl Into) -> Self { + Self { + status, + message: message.into(), + } + } + + pub fn bad_request(error: impl ToString) -> Self { + Self::new(StatusCode::BAD_REQUEST, error.to_string()) + } + + pub fn unauthorized(message: impl Into) -> Self { + Self::new(StatusCode::UNAUTHORIZED, message) + } + + pub fn internal(error: impl ToString) -> Self { + Self::new( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Configuration error: {}", error.to_string()), + ) + } + + pub fn from_json_rejection(rejection: JsonRejection) -> Self { + Self::bad_request(format!("Invalid request body: {}", rejection.body_text())) + } + + pub fn from_query_rejection(rejection: QueryRejection) -> Self { + Self::bad_request(format!( + "Invalid query parameters: {}", + rejection.body_text() + )) + } +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + ( + self.status, + Json(ErrorResponse { + error: self.message, + }), + ) + .into_response() + } +} + +#[derive(Serialize)] +struct ErrorResponse { + error: String, +} From 8b2be94de05f259c6887972ae80e766a08922a28 Mon Sep 17 00:00:00 2001 From: InayaYousfi Date: Wed, 10 Jun 2026 09:05:27 +0200 Subject: [PATCH 3/4] refactor(axum): tighten auth and OAuth handling - centralize OAuth provider parsing and fragment encoding - map signup/login/token errors to clearer HTTP responses - return server startup errors instead of panicking --- Cargo.lock | 1 + Cargo.toml | 3 +- examples/web_server/src/main.rs | 4 +-- src/core/oauth/manager.rs | 6 ++-- src/core/oauth/store.rs | 14 ++++++++ src/web_axum/app.rs | 10 +++--- src/web_axum/handlers/auth.rs | 16 +++++++-- src/web_axum/handlers/oauth.rs | 58 ++++++++++++++++++++++----------- src/web_axum/handlers/token.rs | 24 +++++++------- src/web_axum/models.rs | 14 +++----- src/web_axum/response.rs | 6 +++- 11 files changed, 103 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9e0f8d..c656545 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,7 @@ dependencies = [ "jsonwebtoken", "log", "oauth2", + "percent-encoding", "rand 0.9.2", "reqwest", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6282a74..3570c41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,11 +35,12 @@ sqlx = { version = "0.8.6", features = [ sqlx-postgres = { version = "0.8.6", optional = true } axum = { version = "0.8.4", optional = true } tokio = { version = "1.47.1", features = ["full"], optional = true } +percent-encoding = { version = "2.3.1", optional = true } [features] bare = [] postgres = ["dep:sqlx", "dep:sqlx-postgres", "tokio"] -axum = ["dep:axum", "tokio"] +axum = ["dep:axum", "dep:percent-encoding", "tokio"] web = ["axum"] db = ["postgres"] full = ["db", "web"] diff --git a/examples/web_server/src/main.rs b/examples/web_server/src/main.rs index fcb564e..55d6349 100644 --- a/examples/web_server/src/main.rs +++ b/examples/web_server/src/main.rs @@ -137,10 +137,10 @@ use std::sync::Arc; /// # Panics /// This function will panic if the Tokio runtime cannot be started. #[tokio::main] -async fn main() { +async fn main() -> std::io::Result<()> { // Initialize logging env_logger::init(); let auth_service = Arc::new(AuthService::default()); - start_server(auth_service, None).await; + start_server(auth_service, None).await } diff --git a/src/core/oauth/manager.rs b/src/core/oauth/manager.rs index 7f4d2bb..4507351 100644 --- a/src/core/oauth/manager.rs +++ b/src/core/oauth/manager.rs @@ -371,7 +371,7 @@ impl OAuth2Service for OAuth2Manager { let client = self.get_client(provider)?; let config = self.configs.get(&provider).unwrap(); - // Dans ton code Rust, assure-toi de dédupliquer les scopes + // Collect default and additional scopes. let mut all_scopes = provider .default_scopes() .into_iter() @@ -383,7 +383,7 @@ impl OAuth2Service for OAuth2Manager { } all_scopes.extend(config.additional_scopes.clone()); - // Déduplication des scopes + // Deduplicate scopes. all_scopes.sort(); all_scopes.dedup(); @@ -595,7 +595,7 @@ impl OAuth2Service for OAuth2Manager { &self, provider: OAuth2Provider, ) -> Result { - self.get_redirect_frontend_uri(provider) + OAuth2Manager::get_redirect_frontend_uri(self, provider) } } diff --git a/src/core/oauth/store.rs b/src/core/oauth/store.rs index 62153cc..48b79f4 100644 --- a/src/core/oauth/store.rs +++ b/src/core/oauth/store.rs @@ -111,6 +111,20 @@ impl OAuth2Provider { } } +impl std::str::FromStr for OAuth2Provider { + type Err = String; + + fn from_str(value: &str) -> Result { + match value.to_lowercase().as_str() { + "google" => Ok(Self::Google), + "github" => Ok(Self::GitHub), + "discord" => Ok(Self::Discord), + "microsoft" => Ok(Self::Microsoft), + _ => Err("expected one of: google, github, discord, microsoft".to_string()), + } + } +} + /// Represents an OAuth2 token, including access and refresh tokens, expiration, and provider info. /// /// This struct holds all relevant information about an OAuth2 token issued by a provider, diff --git a/src/web_axum/app.rs b/src/web_axum/app.rs index 3be7212..06ceea4 100644 --- a/src/web_axum/app.rs +++ b/src/web_axum/app.rs @@ -20,12 +20,12 @@ use super::{handlers, middleware::auth_context_middleware}; /// - `address`: Optional socket address override. When omitted, the server uses /// `0.0.0.0:3000`. /// -/// # Panics -/// Panics if the TCP listener cannot bind or the Axum server fails to start. +/// # Errors +/// Returns an error if the TCP listener cannot bind or the Axum server fails to start. pub async fn start_server( auth_service: Arc, address: impl Into>, -) { +) -> std::io::Result<()> { use axum::serve; use tokio::net::TcpListener; @@ -36,8 +36,8 @@ pub async fn start_server( log::info!("Axum server running at http://{addr}"); - let listener = TcpListener::bind(addr).await.unwrap(); - serve(listener, app).await.unwrap(); + let listener = TcpListener::bind(addr).await?; + serve(listener, app).await } /// Returns an Axum router with Authen's HTTP API mounted. diff --git a/src/web_axum/handlers/auth.rs b/src/web_axum/handlers/auth.rs index f421f4a..164b513 100644 --- a/src/web_axum/handlers/auth.rs +++ b/src/web_axum/handlers/auth.rs @@ -7,6 +7,7 @@ use axum::{ use crate::{ auth_service::{AuthService, LoginMethod, SignupMethod}, + error::AuthError, web_axum::{ models::{CredentialsRequest, LoginResponse, UserResponse}, response::{ApiError, ApiResult}, @@ -25,7 +26,15 @@ pub(crate) async fn signup( password: payload.password, }) .await - .map_err(ApiError::bad_request)?; + .map_err(|error| match error { + AuthError::UserAlreadyExists => ApiError::conflict(error), + AuthError::SignupError(message) + if message.to_lowercase().contains("already exists") => + { + ApiError::conflict(AuthError::SignupError(message)) + } + _ => ApiError::bad_request(error), + })?; Ok(Json(UserResponse::from(user))) } @@ -42,7 +51,10 @@ pub(crate) async fn login( password: payload.password, }) .await - .map_err(ApiError::bad_request)?; + .map_err(|error| match error { + AuthError::InvalidCredentials => ApiError::unauthorized(error.to_string()), + _ => ApiError::bad_request(error), + })?; Ok(Json(LoginResponse::from_user_and_tokens(user, tokens))) } diff --git a/src/web_axum/handlers/oauth.rs b/src/web_axum/handlers/oauth.rs index 36d3df0..e0fce90 100644 --- a/src/web_axum/handlers/oauth.rs +++ b/src/web_axum/handlers/oauth.rs @@ -8,6 +8,38 @@ use axum::{ }, response::Redirect, }; +use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode}; + +const OAUTH_FRAGMENT_ENCODE_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'!') + .add(b'"') + .add(b'#') + .add(b'$') + .add(b'%') + .add(b'&') + .add(b'\'') + .add(b'(') + .add(b')') + .add(b'*') + .add(b'+') + .add(b',') + .add(b'/') + .add(b':') + .add(b';') + .add(b'<') + .add(b'=') + .add(b'>') + .add(b'?') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'`') + .add(b'{') + .add(b'|') + .add(b'}'); use crate::{ auth_service::{AuthService, LoginMethod, SignupMethod}, @@ -61,22 +93,22 @@ pub(crate) async fn callback( let redirect_url = format!( "{}#access_token={}&refresh_token={}&user_id={}&token_type=Bearer&expires_in=3600", frontend_uri, - url_encode(&tokens.access_token), - url_encode(&tokens.refresh_token), - url_encode(&user.id) + utf8_percent_encode(&tokens.access_token, OAUTH_FRAGMENT_ENCODE_SET), + utf8_percent_encode(&tokens.refresh_token, OAUTH_FRAGMENT_ENCODE_SET), + utf8_percent_encode(&user.id, OAUTH_FRAGMENT_ENCODE_SET) ); - Ok(Redirect::permanent(&redirect_url)) + Ok(Redirect::temporary(&redirect_url)) } Err(error) => { let redirect_url = format!( "{}#error={}&error_description={}", frontend_uri, - url_encode("authentication_failed"), - url_encode(&error.to_string()) + utf8_percent_encode("authentication_failed", OAUTH_FRAGMENT_ENCODE_SET), + utf8_percent_encode(&error.to_string(), OAUTH_FRAGMENT_ENCODE_SET) ); - Ok(Redirect::permanent(&redirect_url)) + Ok(Redirect::temporary(&redirect_url)) } } } @@ -118,15 +150,3 @@ pub(crate) async fn login( Ok(Json(LoginResponse::from_user_and_tokens(user, tokens))) } - -fn url_encode(value: &str) -> String { - value - .bytes() - .map(|byte| match byte { - b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { - (byte as char).to_string() - } - _ => format!("%{byte:02X}"), - }) - .collect() -} diff --git a/src/web_axum/handlers/token.rs b/src/web_axum/handlers/token.rs index 92e8513..312641d 100644 --- a/src/web_axum/handlers/token.rs +++ b/src/web_axum/handlers/token.rs @@ -24,7 +24,7 @@ pub(crate) async fn refresh( let tokens = auth_service .refresh_access_token(&payload.refresh_token) .await - .map_err(ApiError::bad_request)?; + .map_err(|error| ApiError::unauthorized(error.to_string()))?; Ok(Json(TokenPairResponse::from(tokens))) } @@ -35,14 +35,16 @@ pub(crate) async fn validate( ) -> ApiResult { let Json(payload) = payload.map_err(ApiError::from_json_rejection)?; - let claims = auth_service - .validate_access_token(&payload.token) - .await - .map_err(ApiError::bad_request)?; - - Ok(Json(ValidateTokenResponse { - valid: true, - subject: claims.get_subject().to_string(), - expiration: claims.get_expiration(), - })) + match auth_service.validate_access_token(&payload.token).await { + Ok(claims) => Ok(Json(ValidateTokenResponse { + valid: true, + subject: claims.get_subject().to_string(), + expiration: claims.get_expiration(), + })), + Err(_) => Ok(Json(ValidateTokenResponse { + valid: false, + subject: String::new(), + expiration: 0, + })), + } } diff --git a/src/web_axum/models.rs b/src/web_axum/models.rs index 21336f3..2781116 100644 --- a/src/web_axum/models.rs +++ b/src/web_axum/models.rs @@ -185,13 +185,9 @@ impl From for OAuthAccountResponse { } pub(crate) fn parse_oauth_provider(value: &str) -> Result { - match value.to_lowercase().as_str() { - "google" => Ok(OAuth2Provider::Google), - "github" => Ok(OAuth2Provider::GitHub), - "discord" => Ok(OAuth2Provider::Discord), - "microsoft" => Ok(OAuth2Provider::Microsoft), - _ => Err(ApiError::bad_request(AuthError::InvalidInput(format!( - "Unsupported OAuth2 provider: {value}" - )))), - } + value.parse::().map_err(|error| { + ApiError::bad_request(AuthError::InvalidInput(format!( + "Unsupported OAuth2 provider: {value}: {error}" + ))) + }) } diff --git a/src/web_axum/response.rs b/src/web_axum/response.rs index e9bcb98..ff54279 100644 --- a/src/web_axum/response.rs +++ b/src/web_axum/response.rs @@ -30,10 +30,14 @@ impl ApiError { Self::new(StatusCode::UNAUTHORIZED, message) } + pub fn conflict(error: impl ToString) -> Self { + Self::new(StatusCode::CONFLICT, error.to_string()) + } + pub fn internal(error: impl ToString) -> Self { Self::new( StatusCode::INTERNAL_SERVER_ERROR, - format!("Configuration error: {}", error.to_string()), + format!("Internal server error: {}", error.to_string()), ) } From 68cf2592553814fc1d25d3fb8eec4564a9d22975 Mon Sep 17 00:00:00 2001 From: InayaYousfi Date: Wed, 10 Jun 2026 09:18:23 +0200 Subject: [PATCH 4/4] refactor(axum): map token validation errors and return startup results - treat invalid, expired, and validation token errors as non-fatal - return web server startup errors from the example instead of panicking - refresh `percent-encoding` and lockfile dependencies --- Cargo.lock | 1337 ++++++++++++++++++------------- Cargo.toml | 2 +- examples/web_server/src/main.rs | 10 +- src/web_axum/handlers/token.rs | 9 +- 4 files changed, 783 insertions(+), 575 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c656545..3e4a61c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,26 +2,11 @@ # It is not intended for manual editing. version = 4 -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[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", ] @@ -32,12 +17,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -55,9 +34,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -70,39 +49,45 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "argon2" version = "0.5.3" @@ -117,9 +102,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -155,13 +140,13 @@ dependencies = [ "log", "oauth2", "percent-encoding", - "rand 0.9.2", + "rand 0.9.4", "reqwest", "serde", "serde_json", "sqlx", "sqlx-postgres", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "uuid", "zeroize", @@ -169,15 +154,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" -version = "0.8.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -194,8 +179,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -209,9 +193,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", @@ -220,28 +204,12 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] -[[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", -] - [[package]] name = "base64" version = "0.22.1" @@ -250,17 +218,17 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -283,9 +251,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -295,9 +263,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cast" @@ -307,18 +275,19 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.29" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ + "find-msvc-tools", "shlex", ] [[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" @@ -328,11 +297,10 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -370,18 +338,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -389,15 +357,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concurrent-queue" @@ -424,6 +392,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -441,18 +419,18 @@ 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", ] [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "criterion" @@ -509,9 +487,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[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", @@ -530,9 +508,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -551,9 +529,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -568,9 +546,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -586,9 +564,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -596,9 +574,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -615,12 +593,12 @@ 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", + "windows-sys 0.61.2", ] [[package]] @@ -636,9 +614,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -647,9 +625,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flume" @@ -691,18 +675,18 @@ 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", ] [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -710,15 +694,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -738,27 +722,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", @@ -766,7 +750,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -782,42 +765,49 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] [[package]] name = "h2" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -834,32 +824,39 @@ 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 = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", ] [[package]] @@ -894,21 +891,20 @@ 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]] name = "http" -version = "1.3.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -949,13 +945,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -970,15 +967,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1003,14 +999,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1019,7 +1014,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1029,9 +1024,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1053,12 +1048,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1066,9 +1062,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1079,11 +1075,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1094,42 +1089,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" 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.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1137,11 +1128,17 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[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", @@ -1150,9 +1147,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1160,46 +1157,27 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", -] - -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", + "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1212,28 +1190,28 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1242,11 +1220,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1274,17 +1253,35 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.8.1", +] [[package]] name = "libsqlite3-sys" @@ -1298,31 +1295,30 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[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.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lru-slab" @@ -1348,9 +1344,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -1358,31 +1354,22 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1407,26 +1394,25 @@ dependencies = [ [[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", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1466,9 +1452,9 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64", "chrono", - "getrandom 0.2.16", + "getrandom 0.2.17", "http", - "rand 0.8.5", + "rand 0.8.6", "reqwest", "serde", "serde_json", @@ -1478,26 +1464,17 @@ dependencies = [ "url", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -1507,15 +1484,14 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1533,15 +1509,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -1557,9 +1533,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", @@ -1567,15 +1543,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]] @@ -1591,12 +1567,12 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64", - "serde", + "serde_core", ] [[package]] @@ -1610,21 +1586,15 @@ 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 = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs1" @@ -1649,30 +1619,36 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1692,20 +1668,30 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -1714,8 +1700,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", - "thiserror 2.0.12", + "socket2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -1723,20 +1709,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -1744,23 +1730,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1771,11 +1757,17 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -1784,12 +1776,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1809,7 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1818,32 +1810,41 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "getrandom 0.3.3", + "bitflags", ] [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1853,9 +1854,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1864,15 +1865,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -1920,7 +1921,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1928,9 +1929,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -1946,36 +1947,30 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "once_cell", "ring", @@ -1987,9 +1982,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -1997,9 +1992,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -2008,15 +2003,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2029,11 +2024,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2044,12 +2039,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2057,28 +2052,44 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2087,24 +2098,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "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]] @@ -2143,16 +2156,17 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -2168,21 +2182,21 @@ dependencies = [ [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", ] [[package]] name = "slab" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2195,22 +2209,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2262,7 +2266,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown", + "hashbrown 0.15.5", "hashlink", "indexmap", "log", @@ -2273,7 +2277,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -2349,7 +2353,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", "sha1", @@ -2357,7 +2361,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -2389,14 +2393,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -2422,7 +2426,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.18", "tracing", "url", "uuid", @@ -2430,9 +2434,9 @@ dependencies = [ [[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 = "stringprep" @@ -2453,9 +2457,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2484,12 +2488,12 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2505,15 +2509,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2527,11 +2531,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -2547,9 +2551,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2558,30 +2562,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2589,9 +2593,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2609,9 +2613,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -2624,29 +2628,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -2665,9 +2666,9 @@ dependencies = [ [[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", @@ -2675,9 +2676,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -2686,9 +2687,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2699,9 +2700,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2715,20 +2716,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -2745,9 +2746,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[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", @@ -2757,9 +2758,9 @@ 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", @@ -2768,9 +2769,9 @@ dependencies = [ [[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", ] @@ -2783,9 +2784,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-bidi" @@ -2795,24 +2796,30 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[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-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -2822,14 +2829,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2846,13 +2854,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -2894,12 +2902,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen 0.51.0", ] [[package]] @@ -2910,48 +2927,32 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" 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", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2959,31 +2960,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -3001,37 +3036,37 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall", + "libredox", "wasite", ] [[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.61.2", ] [[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", @@ -3042,9 +3077,9 @@ dependencies = [ [[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", @@ -3053,9 +3088,9 @@ dependencies = [ [[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", @@ -3064,15 +3099,15 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +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", "windows-result", @@ -3081,18 +3116,18 @@ dependencies = [ [[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", ] [[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", ] @@ -3117,11 +3152,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "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]] @@ -3148,13 +3192,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" @@ -3167,6 +3228,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" @@ -3179,6 +3246,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" @@ -3191,12 +3264,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" @@ -3209,6 +3294,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" @@ -3221,6 +3312,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" @@ -3233,6 +3330,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" @@ -3246,27 +3349,117 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3274,9 +3467,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -3286,18 +3479,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -3306,18 +3499,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -3327,18 +3520,18 @@ dependencies = [ [[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", @@ -3347,9 +3540,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -3358,9 +3551,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3369,11 +3562,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 3570c41..3ba11ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ sqlx = { version = "0.8.6", features = [ sqlx-postgres = { version = "0.8.6", optional = true } axum = { version = "0.8.4", optional = true } tokio = { version = "1.47.1", features = ["full"], optional = true } -percent-encoding = { version = "2.3.1", optional = true } +percent-encoding = { version = "2.3.2", optional = true } [features] bare = [] diff --git a/examples/web_server/src/main.rs b/examples/web_server/src/main.rs index 55d6349..1947963 100644 --- a/examples/web_server/src/main.rs +++ b/examples/web_server/src/main.rs @@ -107,13 +107,14 @@ //! use std::sync::Arc; //! //! #[tokio::main] -//! async fn main() { +//! async fn main() -> std::io::Result<()> { //! env_logger::init(); //! let auth_service = Arc::new(AuthService::default()); //! #[cfg(feature = "axum")] -//! start_server(auth_service).await; +//! start_server(auth_service, None).await?; //! #[cfg(not(feature = "web"))] //! println!("Please enable the 'web' feature to run the web server example."); +//! Ok(()) //! } //! ``` //! @@ -134,8 +135,9 @@ use std::sync::Arc; /// - If the `web` feature is enabled, the server is started and listens on `0.0.0.0:3000`. /// - If the `web` feature is not enabled, a message is printed to enable the feature. /// -/// # Panics -/// This function will panic if the Tokio runtime cannot be started. +/// # Returns +/// Returns [`std::io::Result<()>`]. Errors from starting the Tokio runtime or server are returned +/// to the caller rather than panicked. #[tokio::main] async fn main() -> std::io::Result<()> { // Initialize logging diff --git a/src/web_axum/handlers/token.rs b/src/web_axum/handlers/token.rs index 312641d..45fa49d 100644 --- a/src/web_axum/handlers/token.rs +++ b/src/web_axum/handlers/token.rs @@ -6,6 +6,7 @@ use axum::{ }; use crate::{ + AuthError, auth_service::AuthService, web_axum::{ models::{ @@ -41,10 +42,16 @@ pub(crate) async fn validate( subject: claims.get_subject().to_string(), expiration: claims.get_expiration(), })), - Err(_) => Ok(Json(ValidateTokenResponse { + Err( + AuthError::InvalidToken(_) | AuthError::TokenExpired | AuthError::TokenValidation(_), + ) => Ok(Json(ValidateTokenResponse { valid: false, subject: String::new(), expiration: 0, })), + Err(err) => Err(ApiError::internal(format!( + "token validation service error: {}", + err + ))), } }