Skip to main content

litellm_rs/server/routes/ai/
context.rs

1//! Request context and authentication helpers
2
3use crate::core::models::ApiKey;
4use crate::core::models::user::types::User;
5use crate::core::types::context::RequestContext;
6use crate::utils::error::gateway_error::GatewayError;
7use actix_web::{HttpMessage, HttpRequest, HttpResponse, Result as ActixResult};
8use serde::Serialize;
9use std::future::Future;
10use tracing::{debug, error};
11
12/// Get request context from headers and middleware extensions
13pub fn get_request_context(req: &HttpRequest) -> ActixResult<RequestContext> {
14    if let Some(context) = req.extensions().get::<RequestContext>() {
15        return Ok(context.clone());
16    }
17
18    let mut context = RequestContext::new();
19
20    // Extract request ID
21    if let Some(request_id) = req.headers().get("x-request-id")
22        && let Ok(id) = request_id.to_str()
23    {
24        context.request_id = id.to_string();
25    }
26
27    // Extract user agent
28    if let Some(user_agent) = req.headers().get("user-agent")
29        && let Ok(agent) = user_agent.to_str()
30    {
31        context.user_agent = Some(agent.to_string());
32    }
33
34    context.client_ip = req.connection_info().peer_addr().map(|ip| ip.to_string());
35
36    Ok(context)
37}
38
39/// Extract user from request extensions
40pub fn get_authenticated_user(req: &HttpRequest) -> Option<User> {
41    req.extensions().get::<User>().cloned()
42}
43
44/// Extract API key from request extensions
45pub fn get_authenticated_api_key(req: &HttpRequest) -> Option<ApiKey> {
46    req.extensions().get::<ApiKey>().cloned()
47}
48
49/// Check if user has permission for the requested operation.
50///
51/// Permission logic (two-role model: admin vs user):
52/// - Unauthenticated requests are always denied.
53/// - Admin roles (`SuperAdmin`, `Admin`) have full access to every operation.
54/// - API-usage operations (`chat`, `completions`, `models`, `embeddings`, `images`,
55///   `audio`, `moderations`, `assistants`, `files`, `fine_tuning`) are allowed for
56///   any authenticated user/key.
57/// - Management operations (`keys.list_all`, `users.manage`, `config.manage`,
58///   `teams.manage`, `analytics.admin`) require an admin role.
59/// - API key `permissions` can grant admin-level access via `"*"` or `"system.admin"`,
60///   or grant a specific operation directly.
61pub fn check_permission(user: Option<&User>, api_key: Option<&ApiKey>, operation: &str) -> bool {
62    use crate::core::models::user::types::UserRole;
63
64    // Unauthenticated requests are always denied
65    if user.is_none() && api_key.is_none() {
66        return false;
67    }
68
69    // Check if the user has an admin role
70    let user_is_admin = user
71        .map(|u| matches!(u.role, UserRole::SuperAdmin | UserRole::Admin))
72        .unwrap_or(false);
73
74    // Check if the API key carries admin-level permissions
75    let key_is_admin = api_key
76        .map(|k| {
77            k.permissions
78                .iter()
79                .any(|p| p == "*" || p == "system.admin")
80        })
81        .unwrap_or(false);
82
83    if user_is_admin || key_is_admin {
84        return true;
85    }
86
87    // Check if the API key explicitly grants this operation
88    let key_has_operation = api_key
89        .map(|k| k.permissions.iter().any(|p| p == operation))
90        .unwrap_or(false);
91
92    if key_has_operation {
93        return true;
94    }
95
96    // Management operations require admin — deny for non-admin callers
97    let is_management_op = matches!(
98        operation,
99        "keys.list_all" | "users.manage" | "config.manage" | "teams.manage" | "analytics.admin"
100    );
101
102    if is_management_op {
103        return false;
104    }
105
106    // API-usage operations are allowed for any authenticated caller
107    true
108}
109
110/// Log API usage for billing and analytics
111pub async fn log_api_usage(context: &RequestContext, model: &str, tokens_used: u32, cost: f64) {
112    // In a real implementation, this would log usage to the database
113    debug!(
114        "API usage: user_id={:?}, model={}, tokens={}, cost={}",
115        context.user_id, model, tokens_used, cost
116    );
117}
118
119/// Common handler for JSON AI requests: extract context → call handler → json or error response.
120///
121/// Eliminates the repeated pattern of:
122/// ```ignore
123/// let context = get_request_context(&req)?;
124/// match handler(request.into_inner(), context).await {
125///     Ok(r) => Ok(HttpResponse::Ok().json(r)),
126///     Err(e) => { error!("..."); Ok(openai_errors::gateway_error_response(&e)) }
127/// }
128/// ```
129pub async fn handle_ai_request<Req, Resp, F, Fut>(
130    req: &HttpRequest,
131    request: Req,
132    error_label: &str,
133    handler: F,
134) -> ActixResult<HttpResponse>
135where
136    Resp: Serialize,
137    F: FnOnce(Req, RequestContext) -> Fut,
138    Fut: Future<Output = Result<Resp, GatewayError>>,
139{
140    let context = get_request_context(req)?;
141    match handler(request, context).await {
142        Ok(response) => Ok(HttpResponse::Ok().json(response)),
143        Err(e) => {
144            error!("{} error: {}", error_label, e);
145            Ok(super::openai_errors::gateway_error_response(&e))
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::core::models::user::types::{User, UserRole};
154    use crate::core::models::{Metadata, UsageStats};
155
156    fn create_test_user() -> User {
157        User::new(
158            "testuser".to_string(),
159            "test@example.com".to_string(),
160            "hash".to_string(),
161        )
162    }
163
164    fn create_admin_user() -> User {
165        let mut user = create_test_user();
166        user.role = UserRole::Admin;
167        user
168    }
169
170    fn create_super_admin_user() -> User {
171        let mut user = create_test_user();
172        user.role = UserRole::SuperAdmin;
173        user
174    }
175
176    fn create_test_api_key() -> ApiKey {
177        ApiKey {
178            metadata: Metadata::new(),
179            name: "test-key".to_string(),
180            key_hash: "hash".to_string(),
181            key_prefix: "sk-test".to_string(),
182            user_id: None,
183            team_id: None,
184            permissions: vec![],
185            rate_limits: None,
186            expires_at: None,
187            is_active: true,
188            last_used_at: None,
189            usage_stats: UsageStats::default(),
190        }
191    }
192
193    fn create_admin_api_key() -> ApiKey {
194        let mut key = create_test_api_key();
195        key.permissions = vec!["*".to_string()];
196        key
197    }
198
199    // ==================== check_permission Tests ====================
200
201    #[test]
202    fn test_check_permission_no_auth() {
203        assert!(!check_permission(None, None, "chat"));
204    }
205
206    #[test]
207    fn test_check_permission_no_auth_management() {
208        assert!(!check_permission(None, None, "keys.list_all"));
209    }
210
211    #[test]
212    fn test_check_permission_with_user() {
213        let user = create_test_user();
214        assert!(check_permission(Some(&user), None, "chat"));
215    }
216
217    #[test]
218    fn test_check_permission_with_api_key() {
219        let api_key = create_test_api_key();
220        assert!(check_permission(None, Some(&api_key), "chat"));
221    }
222
223    #[test]
224    fn test_check_permission_with_both() {
225        let user = create_test_user();
226        let api_key = create_test_api_key();
227        assert!(check_permission(Some(&user), Some(&api_key), "chat"));
228    }
229
230    #[test]
231    fn test_check_permission_various_operations() {
232        let user = create_test_user();
233        assert!(check_permission(Some(&user), None, "chat"));
234        assert!(check_permission(Some(&user), None, "completions"));
235        assert!(check_permission(Some(&user), None, "embeddings"));
236        assert!(check_permission(Some(&user), None, "images"));
237    }
238
239    // ==================== Admin role Tests ====================
240
241    #[test]
242    fn test_admin_user_can_access_management_ops() {
243        let admin = create_admin_user();
244        assert!(check_permission(Some(&admin), None, "keys.list_all"));
245        assert!(check_permission(Some(&admin), None, "users.manage"));
246        assert!(check_permission(Some(&admin), None, "config.manage"));
247        assert!(check_permission(Some(&admin), None, "teams.manage"));
248        assert!(check_permission(Some(&admin), None, "analytics.admin"));
249    }
250
251    #[test]
252    fn test_super_admin_can_access_management_ops() {
253        let sa = create_super_admin_user();
254        assert!(check_permission(Some(&sa), None, "keys.list_all"));
255        assert!(check_permission(Some(&sa), None, "users.manage"));
256        assert!(check_permission(Some(&sa), None, "config.manage"));
257    }
258
259    #[test]
260    fn test_admin_user_can_access_api_ops() {
261        let admin = create_admin_user();
262        assert!(check_permission(Some(&admin), None, "chat"));
263        assert!(check_permission(Some(&admin), None, "completions"));
264        assert!(check_permission(Some(&admin), None, "models"));
265    }
266
267    // ==================== Regular user denied management Tests ====================
268
269    #[test]
270    fn test_regular_user_denied_management_ops() {
271        let user = create_test_user();
272        assert!(!check_permission(Some(&user), None, "keys.list_all"));
273        assert!(!check_permission(Some(&user), None, "users.manage"));
274        assert!(!check_permission(Some(&user), None, "config.manage"));
275        assert!(!check_permission(Some(&user), None, "teams.manage"));
276        assert!(!check_permission(Some(&user), None, "analytics.admin"));
277    }
278
279    #[test]
280    fn test_viewer_denied_management_ops() {
281        let mut user = create_test_user();
282        user.role = UserRole::Viewer;
283        assert!(!check_permission(Some(&user), None, "keys.list_all"));
284        assert!(!check_permission(Some(&user), None, "users.manage"));
285    }
286
287    #[test]
288    fn test_api_user_denied_management_ops() {
289        let mut user = create_test_user();
290        user.role = UserRole::ApiUser;
291        assert!(!check_permission(Some(&user), None, "keys.list_all"));
292        assert!(!check_permission(Some(&user), None, "config.manage"));
293    }
294
295    #[test]
296    fn test_manager_denied_management_ops() {
297        let mut user = create_test_user();
298        user.role = UserRole::Manager;
299        assert!(!check_permission(Some(&user), None, "users.manage"));
300    }
301
302    // ==================== API key permission Tests ====================
303
304    #[test]
305    fn test_admin_api_key_can_access_management_ops() {
306        let key = create_admin_api_key();
307        assert!(check_permission(None, Some(&key), "keys.list_all"));
308        assert!(check_permission(None, Some(&key), "users.manage"));
309        assert!(check_permission(None, Some(&key), "config.manage"));
310    }
311
312    #[test]
313    fn test_system_admin_api_key_can_access_management_ops() {
314        let mut key = create_test_api_key();
315        key.permissions = vec!["system.admin".to_string()];
316        assert!(check_permission(None, Some(&key), "keys.list_all"));
317        assert!(check_permission(None, Some(&key), "users.manage"));
318    }
319
320    #[test]
321    fn test_regular_api_key_denied_management_ops() {
322        let key = create_test_api_key();
323        assert!(!check_permission(None, Some(&key), "keys.list_all"));
324        assert!(!check_permission(None, Some(&key), "users.manage"));
325    }
326
327    #[test]
328    fn test_api_key_with_specific_management_permission() {
329        let mut key = create_test_api_key();
330        key.permissions = vec!["keys.list_all".to_string()];
331        assert!(check_permission(None, Some(&key), "keys.list_all"));
332        assert!(!check_permission(None, Some(&key), "users.manage"));
333    }
334
335    // ==================== get_authenticated_user Tests ====================
336
337    #[test]
338    fn test_get_authenticated_user_returns_none() {
339        let req = actix_web::test::TestRequest::default().to_http_request();
340        assert!(get_authenticated_user(&req).is_none());
341    }
342
343    // ==================== get_authenticated_api_key Tests ====================
344
345    #[test]
346    fn test_get_authenticated_api_key_returns_none() {
347        let req = actix_web::test::TestRequest::default().to_http_request();
348        assert!(get_authenticated_api_key(&req).is_none());
349    }
350
351    // ==================== log_api_usage Tests ====================
352
353    #[tokio::test]
354    async fn test_log_api_usage() {
355        let context = RequestContext::new();
356        log_api_usage(&context, "gpt-4", 100, 0.002).await;
357        // Function should not panic
358    }
359
360    #[tokio::test]
361    async fn test_log_api_usage_various_models() {
362        let context = RequestContext::new();
363        log_api_usage(&context, "gpt-3.5-turbo", 50, 0.001).await;
364        log_api_usage(&context, "claude-3-opus", 200, 0.005).await;
365        log_api_usage(&context, "gemini-pro", 75, 0.0015).await;
366    }
367
368    #[tokio::test]
369    async fn test_log_api_usage_zero_tokens() {
370        let context = RequestContext::new();
371        log_api_usage(&context, "gpt-4", 0, 0.0).await;
372    }
373
374    #[tokio::test]
375    async fn test_log_api_usage_large_values() {
376        let context = RequestContext::new();
377        log_api_usage(&context, "gpt-4", 100000, 100.0).await;
378    }
379
380    #[tokio::test]
381    async fn test_log_api_usage_with_user_id() {
382        let mut context = RequestContext::new();
383        context.user_id = Some(uuid::Uuid::new_v4().to_string());
384        log_api_usage(&context, "gpt-4", 100, 0.002).await;
385    }
386
387    // ==================== RequestContext Tests ====================
388
389    #[test]
390    fn test_request_context_new() {
391        let context = RequestContext::new();
392        assert!(context.user_id.is_none());
393    }
394}