vultur_sso_api_client/
lib.rs

1//! # VULTUR SSO API Client
2//!
3//! Rust library for integrating with VULTUR SSO in API backends.
4//! Provides JWT validation, authentication middleware, and .well-known endpoint serving.
5//!
6//! ## Features
7//!
8//! - JWT token validation against vultur-ident-api
9//! - Authentication middleware for bouncing unauthenticated requests
10//! - .well-known endpoint serving for permission configurations
11//! - Permission checking utilities
12//! - Axum integration
13//!
14//! ## Basic Example (`Arc<VulturSSOClient>` state)
15//!
16//! ```rust,no_run
17//! use vultur_sso_api_client::{VulturSSOClient, Config, auth_middleware};
18//! use axum::{Router, routing::get, middleware};
19//! use std::sync::Arc;
20//!
21//! #[tokio::main]
22//! async fn main() {
23//!     let config = Config::builder()
24//!         .ident_api_url("https://ident.vultur.com")
25//!         .application_name("my-app")
26//!         .build()
27//!         .unwrap();
28//!     
29//!     let client = Arc::new(VulturSSOClient::new(config));
30//!     
31//!     let app: Router<Arc<VulturSSOClient>> = Router::new()
32//!         .route("/protected", get(protected_handler))
33//!         .layer(middleware::from_fn_with_state(
34//!             client.clone(),
35//!             auth_middleware
36//!         ))
37//!         .with_state(client);
38//! }
39//!
40//! async fn protected_handler() -> &'static str {
41//!     "This is protected!"
42//! }
43//! ```
44//!
45//! ## Generic Example (Composite AppState)
46//!
47//! For applications that need composite state (recommended for new projects):
48//!
49//! ```rust,no_run
50//! use vultur_sso_api_client::{VulturSSOClient, Config, auth_middleware_generic};
51//! use axum::{Router, routing::get, middleware, extract::FromRef};
52//! use std::sync::Arc;
53//!
54//! #[derive(Clone)]
55//! struct AppState {
56//!     sso_client: Arc<VulturSSOClient>,
57//!     database_url: String,
58//! }
59//!
60//! impl FromRef<AppState> for Arc<VulturSSOClient> {
61//!     fn from_ref(app_state: &AppState) -> Arc<VulturSSOClient> {
62//!         app_state.sso_client.clone()
63//!     }
64//! }
65//!
66//! #[tokio::main]
67//! async fn main() {
68//!     let config = Config::builder()
69//!         .ident_api_url("https://ident.vultur.com")
70//!         .application_name("my-app")
71//!         .build()
72//!         .unwrap();
73//!     
74//!     let app_state = AppState {
75//!         sso_client: Arc::new(VulturSSOClient::new(config)),
76//!         database_url: "sqlite://app.db".to_string(),
77//!     };
78//!     
79//!     let app: Router<AppState> = Router::new()
80//!         .route("/protected", get(protected_handler))
81//!         .layer(middleware::from_fn_with_state(
82//!             app_state.clone(),
83//!             auth_middleware_generic::<AppState>
84//!         ))
85//!         .with_state(app_state);
86//! }
87//!
88//! async fn protected_handler() -> &'static str {
89//!     "This is protected!"
90//! }
91//! ```
92
93pub mod client;
94pub mod config;
95pub mod error;
96pub mod middleware;
97pub mod permissions;
98pub mod types;
99pub mod well_known;
100
101// Re-export main types and functions
102pub use client::VulturSSOClient;
103pub use config::{Config, ConfigBuilder};
104pub use error::{Result, VulturSSOError};
105pub use middleware::{
106    auth_middleware, auth_middleware_generic, AuthenticatedUser, JwtToken, RequestExt, RequireAuth,
107    RequirePermission,
108};
109pub use permissions::{
110    PermissionChecker, PermissionConfigBuilder, PermissionPatterns, PermissionScopeBuilder,
111};
112pub use types::PermissionScope;
113pub use types::{Permission, PermissionConfig, UserInfo, UserRole};
114pub use well_known::{serve_well_known, WellKnownHandler};
115
116/// Macro to create a permission extractor for a specific permission scope
117///
118/// # Example
119///
120/// ```rust,no_run
121/// use vultur_sso_api_client::{require_permission, VulturSSOClient, Config};
122/// use axum::{extract::State, routing::get, Router};
123/// use std::sync::Arc;
124///
125/// require_permission!(CanManageUsers, "users.admin");
126///
127/// async fn admin_handler(
128///     user: CanManageUsers,
129///     State(client): State<Arc<VulturSSOClient>>,
130/// ) -> String {
131///     format!("Admin access granted to {}", user.user().character_name)
132/// }
133/// ```
134#[macro_export]
135macro_rules! require_permission {
136    ($extractor_name:ident, $permission:expr) => {
137        #[derive(Debug, Clone)]
138        pub struct $extractor_name {
139            user: $crate::UserInfo,
140        }
141
142        impl $extractor_name {
143            pub fn user(&self) -> &$crate::UserInfo {
144                &self.user
145            }
146        }
147
148        impl $crate::middleware::AuthenticatedUser for $extractor_name {
149            fn user(&self) -> &$crate::UserInfo {
150                &self.user
151            }
152        }
153
154        #[axum::async_trait]
155        impl<S> axum::extract::FromRequestParts<S> for $extractor_name
156        where
157            S: Send + Sync,
158            std::sync::Arc<$crate::VulturSSOClient>: axum::extract::FromRef<S>,
159        {
160            type Rejection = $crate::VulturSSOError;
161
162            async fn from_request_parts(
163                parts: &mut axum::http::request::Parts,
164                state: &S,
165            ) -> Result<Self, Self::Rejection> {
166                use $crate::middleware::RequestExt;
167
168                // Get user info from extensions
169                let user = parts
170                    .extensions
171                    .get::<$crate::UserInfo>()
172                    .ok_or_else(|| $crate::VulturSSOError::unauthorized("User not authenticated"))?
173                    .clone();
174
175                // Admin users have all permissions
176                if user.is_admin {
177                    return Ok($extractor_name { user });
178                }
179
180                // Get JWT token from extensions
181                let token = parts
182                    .extensions
183                    .get::<$crate::JwtToken>()
184                    .ok_or_else(|| $crate::VulturSSOError::unauthorized("JWT token not found"))?;
185
186                // Extract the SSO client from state using FromRef
187                let client: std::sync::Arc<$crate::VulturSSOClient> =
188                    axum::extract::FromRef::from_ref(state);
189
190                // Check permission
191                let has_permission = client
192                    .check_user_permission(&user.eth_address, $permission, &token.0)
193                    .await?;
194
195                if !has_permission {
196                    return Err($crate::VulturSSOError::forbidden(format!(
197                        "Permission '{}' required",
198                        $permission
199                    )));
200                }
201
202                Ok($extractor_name { user })
203            }
204        }
205    };
206}