cognee_http_server/auth/
extractor.rs1use axum::{extract::FromRequestParts, http::request::Parts};
17use uuid::Uuid;
18
19use crate::error::ApiError;
20use crate::state::AppState;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AuthMethod {
26 ApiKey,
27 BearerJwt,
28 CookieJwt,
29 DefaultUser,
30}
31
32#[derive(Debug, Clone)]
35pub struct AuthenticatedUser {
36 pub id: Uuid,
37 pub email: String,
38 pub is_superuser: bool,
39 pub is_verified: bool,
40 pub is_active: bool,
41 pub tenant_id: Option<Uuid>,
42 pub auth_method: AuthMethod,
43}
44
45impl FromRequestParts<AppState> for AuthenticatedUser {
46 type Rejection = ApiError;
47
48 async fn from_request_parts(
49 parts: &mut Parts,
50 state: &AppState,
51 ) -> Result<Self, Self::Rejection> {
52 if let Some(resolver) = state.auth_resolver.as_ref()
53 && let Some(user) = resolver.resolve(parts).await
54 {
55 if !user.is_active {
56 return Err(ApiError::LoginBadCredentials);
57 }
58 return Ok(user);
59 }
60 if state.config.require_authentication {
61 Err(ApiError::Unauthorized)
62 } else {
63 Ok(default_user_from_state(state))
64 }
65 }
66}
67
68pub fn default_user_from_state(state: &AppState) -> AuthenticatedUser {
82 let email = state.config.default_user_email.clone();
85 let id = Uuid::new_v5(&Uuid::NAMESPACE_OID, email.as_bytes());
86 AuthenticatedUser {
87 id,
88 email,
89 is_superuser: true,
90 is_verified: true,
91 is_active: true,
92 tenant_id: None,
93 auth_method: AuthMethod::DefaultUser,
94 }
95}
96
97#[derive(Debug, Clone)]
102pub struct OptionalAuthenticatedUser(pub Option<AuthenticatedUser>);
103
104impl FromRequestParts<AppState> for OptionalAuthenticatedUser {
105 type Rejection = std::convert::Infallible;
106
107 async fn from_request_parts(
108 parts: &mut Parts,
109 state: &AppState,
110 ) -> Result<Self, Self::Rejection> {
111 if let Some(resolver) = state.auth_resolver.as_ref()
112 && let Some(user) = resolver.resolve(parts).await
113 {
114 if user.is_active {
115 return Ok(Self(Some(user)));
116 }
117 return Ok(Self(None));
118 }
119 if state.config.require_authentication {
120 Ok(Self(None))
121 } else {
122 Ok(Self(Some(default_user_from_state(state))))
123 }
124 }
125}
126
127#[cfg(test)]
130#[allow(
131 clippy::unwrap_used,
132 clippy::expect_used,
133 reason = "test code — panics are acceptable failures"
134)]
135mod tests {
136 use super::*;
137 use crate::config::HttpServerConfig;
138
139 #[tokio::test]
145 async fn default_user_id_matches_uuid5_of_configured_email() {
146 let cfg = HttpServerConfig {
147 default_user_email: "alice@example.com".to_string(),
148 ..HttpServerConfig::default()
149 };
150 let state = AppState::build(cfg)
151 .await
152 .expect("AppState::build with default config must succeed");
153
154 let user = default_user_from_state(&state);
155
156 let expected_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, "alice@example.com".as_bytes());
157 assert_eq!(
158 user.id, expected_id,
159 "owner id must be uuid5(NAMESPACE_OID, email)"
160 );
161 assert_eq!(user.email, "alice@example.com");
162 assert!(user.is_active);
163 assert!(user.is_superuser);
164 assert_eq!(user.auth_method, AuthMethod::DefaultUser);
165 assert!(user.tenant_id.is_none());
166 }
167
168 #[tokio::test]
172 async fn default_user_id_for_default_email_is_stable() {
173 let state = AppState::build(HttpServerConfig::default())
174 .await
175 .expect("AppState::build with default config must succeed");
176
177 let user = default_user_from_state(&state);
178
179 let expected_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, "default_user@example.com".as_bytes());
180 assert_eq!(user.id, expected_id);
181 assert_ne!(user.id, Uuid::nil(), "must not regress to the old nil-UUID");
182 }
183}