1use super::types::{AuthMethod, AuthResult, AuthzResult};
4use crate::auth::jwt::types::{TeamScopeMarker, TokenType, VerifiedActiveTeam};
5use crate::config::models::auth::AuthConfig;
6use crate::core::models::user::types::{User, UserRole};
7use crate::core::teams::TeamRepository;
8use crate::core::types::context::RequestContext;
9use crate::storage::StorageLayer;
10use crate::storage::database::SeaOrmTeamRepository;
11use crate::utils::error::gateway_error::Result;
12use std::sync::Arc;
13use tracing::{debug, info};
14use uuid::Uuid;
15
16pub(super) struct TeamValidationSeal(());
17
18#[derive(Clone)]
20pub struct AuthSystem {
21 pub(super) config: Arc<AuthConfig>,
23 pub(super) storage: Arc<StorageLayer>,
25 pub(super) jwt: Arc<crate::auth::jwt::types::JwtHandler>,
27 pub(super) api_key: Arc<crate::auth::api_key::creation::ApiKeyHandler>,
29 pub(super) rbac: Arc<crate::auth::rbac::RbacSystem>,
31}
32
33impl AuthSystem {
34 pub async fn new(config: &AuthConfig, storage: Arc<StorageLayer>) -> Result<Self> {
36 info!("Initializing authentication system");
37
38 let config = Arc::new(config.clone());
39
40 let jwt = Arc::new(crate::auth::jwt::types::JwtHandler::new(&config).await?);
42
43 let hmac_secret = config.api_key_hmac_secret.clone();
45 let api_key = Arc::new(
46 crate::auth::api_key::creation::ApiKeyHandler::new(storage.clone(), hmac_secret)
47 .await?,
48 );
49
50 let rbac = Arc::new(crate::auth::rbac::RbacSystem::new(&config.rbac).await?);
52
53 info!("Authentication system initialized successfully");
54
55 Ok(Self {
56 config,
57 storage,
58 jwt,
59 api_key,
60 rbac,
61 })
62 }
63
64 pub async fn authenticate(
66 &self,
67 auth_method: AuthMethod,
68 context: RequestContext,
69 ) -> Result<AuthResult> {
70 debug!("Authenticating request: {:?}", auth_method);
71
72 match auth_method {
73 AuthMethod::Jwt(token) => self.authenticate_jwt(&token, context).await,
74 AuthMethod::ApiKey(key) => self.authenticate_api_key(&key, context).await,
75 AuthMethod::Session(session_id) => {
76 self.authenticate_session(&session_id, context).await
77 }
78 AuthMethod::None => Ok(AuthResult {
79 success: false,
80 user: None,
81 api_key: None,
82 session: None,
83 error: Some("No authentication provided".to_string()),
84 context,
85 }),
86 }
87 }
88
89 async fn authenticate_jwt(
91 &self,
92 token: &str,
93 mut context: RequestContext,
94 ) -> Result<AuthResult> {
95 match self.jwt.verify_access_token_with_provenance(token).await {
96 Ok(access_claims) => {
97 let claims = access_claims.public;
98 if !matches!(claims.token_type, TokenType::Access) {
100 return Ok(AuthResult {
101 success: false,
102 user: None,
103 api_key: None,
104 session: None,
105 error: Some("Invalid token type: expected access token".to_string()),
106 context,
107 });
108 }
109
110 if let Some(user) = self.storage.db().find_user_by_id(claims.sub).await? {
113 if user.is_active() {
114 context.user_id = Some(user.id().to_string());
115 match access_claims.team_scope_version {
116 TeamScopeMarker::Absent => context.clear_team_id(),
117 TeamScopeMarker::Present(1) => {
118 if let Some(team_id) = claims.team_id {
119 match self.validate_active_team(user.id(), team_id).await? {
120 Some(verified) => {
121 context.set_team_id(verified.team_id());
122 }
123 None => {
124 return Ok(AuthResult {
125 success: false,
126 user: None,
127 api_key: None,
128 session: None,
129 error: Some(
130 "Selected team is not active".to_string(),
131 ),
132 context,
133 });
134 }
135 }
136 } else {
137 context.clear_team_id();
138 }
139 }
140 TeamScopeMarker::Present(_) => {
141 return Ok(AuthResult {
142 success: false,
143 user: None,
144 api_key: None,
145 session: None,
146 error: Some(
147 "Unsupported access-token team scope version".to_string(),
148 ),
149 context,
150 });
151 }
152 }
153
154 Ok(AuthResult {
155 success: true,
156 user: Some(user),
157 api_key: None,
158 session: None,
159 error: None,
160 context,
161 })
162 } else {
163 Ok(AuthResult {
164 success: false,
165 user: None,
166 api_key: None,
167 session: None,
168 error: Some("User account is not active".to_string()),
169 context,
170 })
171 }
172 } else {
173 Ok(AuthResult {
174 success: false,
175 user: None,
176 api_key: None,
177 session: None,
178 error: Some("User not found".to_string()),
179 context,
180 })
181 }
182 }
183 Err(e) => Ok(AuthResult {
184 success: false,
185 user: None,
186 api_key: None,
187 session: None,
188 error: Some(format!("Invalid JWT token: {}", e)),
189 context,
190 }),
191 }
192 }
193
194 async fn authenticate_api_key(
196 &self,
197 key: &str,
198 mut context: RequestContext,
199 ) -> Result<AuthResult> {
200 match self.api_key.verify_key(key).await {
201 Ok(Some((api_key, user))) => {
202 context.set_api_key_id(api_key.metadata.id);
203 if let Some(budget_id) = api_key_budget_id(&api_key) {
204 context.set_api_key_budget_id(budget_id);
205 } else {
206 context.clear_api_key_budget_id();
207 }
208 context.user_id = api_key.user_id.map(|id| id.to_string());
209 if let Some(team_id) = api_key.team_id {
210 context.set_team_id(team_id);
211 } else {
212 context.clear_team_id();
213 }
214
215 Ok(AuthResult {
216 success: true,
217 user,
218 api_key: Some(api_key),
219 session: None,
220 error: None,
221 context,
222 })
223 }
224 Ok(None) => Ok(AuthResult {
225 success: false,
226 user: None,
227 api_key: None,
228 session: None,
229 error: Some("Invalid API key".to_string()),
230 context,
231 }),
232 Err(error) => Err(error),
233 }
234 }
235
236 async fn authenticate_session(
238 &self,
239 _session_id: &str,
240 context: RequestContext,
241 ) -> Result<AuthResult> {
242 Ok(AuthResult {
244 success: false,
245 user: None,
246 api_key: None,
247 session: None,
248 error: Some("Session authentication is not yet implemented".to_string()),
249 context,
250 })
251 }
252
253 pub async fn authorize(&self, user: &User, permissions: &[String]) -> Result<AuthzResult> {
255 debug!(
256 "Authorizing user {} for permissions: {:?}",
257 user.username, permissions
258 );
259
260 let user_permissions = self.rbac.get_user_permissions(user).await?;
261 let allowed = self.rbac.check_permissions(&user_permissions, permissions);
262
263 Ok(AuthzResult {
264 allowed,
265 required_permissions: permissions.to_vec(),
266 user_permissions,
267 reason: if !allowed {
268 Some("Insufficient permissions".to_string())
269 } else {
270 None
271 },
272 })
273 }
274
275 pub(super) async fn get_user_permissions(&self, user: &User) -> Result<Vec<String>> {
277 let permissions = match user.role {
278 UserRole::Admin => vec![
279 "read:all".to_string(),
280 "write:all".to_string(),
281 "delete:all".to_string(),
282 "manage:users".to_string(),
283 "manage:api_keys".to_string(),
284 "manage:teams".to_string(),
285 ],
286 UserRole::SuperAdmin => vec![
287 "read:all".to_string(),
288 "write:all".to_string(),
289 "delete:all".to_string(),
290 "manage:users".to_string(),
291 "manage:api_keys".to_string(),
292 "manage:teams".to_string(),
293 "manage:system".to_string(),
294 ],
295 UserRole::Manager => vec![
296 "read:team".to_string(),
297 "write:team".to_string(),
298 "manage:team_users".to_string(),
299 "manage:team_api_keys".to_string(),
300 ],
301 UserRole::User => vec![
302 "read:own".to_string(),
303 "write:own".to_string(),
304 "use:api".to_string(),
305 ],
306 UserRole::ApiUser => vec!["use:api".to_string()],
307 UserRole::Viewer => vec!["read:own".to_string()],
308 };
309 Ok(permissions)
310 }
311
312 pub(crate) async fn validate_active_team(
318 &self,
319 user_id: Uuid,
320 team_id: Uuid,
321 ) -> Result<Option<VerifiedActiveTeam>> {
322 let repository = SeaOrmTeamRepository::new(self.storage.database.clone());
323 let Some(team) = repository.get(team_id).await? else {
324 return Ok(None);
325 };
326 if team.id() != team_id || !team.is_active() {
327 return Ok(None);
328 }
329
330 let Some(member) = repository.get_member(team_id, user_id).await? else {
331 return Ok(None);
332 };
333 if member.team_id != team_id || member.user_id != user_id || !member.is_active() {
334 return Ok(None);
335 }
336
337 Ok(Some(VerifiedActiveTeam::new(
338 user_id,
339 team_id,
340 TeamValidationSeal(()),
341 )))
342 }
343
344 pub fn config(&self) -> &AuthConfig {
346 &self.config
347 }
348
349 pub fn jwt(&self) -> &crate::auth::jwt::types::JwtHandler {
351 &self.jwt
352 }
353
354 pub fn api_key(&self) -> &crate::auth::api_key::creation::ApiKeyHandler {
356 &self.api_key
357 }
358
359 pub fn rbac(&self) -> &crate::auth::rbac::RbacSystem {
361 &self.rbac
362 }
363}
364
365pub(crate) fn api_key_budget_id(api_key: &crate::core::models::ApiKey) -> Option<Uuid> {
366 api_key
367 .metadata
368 .extra
369 .get("__core_keys")
370 .and_then(|value| value.get("budget_id"))
371 .and_then(|value| value.as_str())
372 .and_then(|value| Uuid::parse_str(value).ok())
373}