allsource_core/infrastructure/web/
auth_api.rs1use crate::infrastructure::security::{
2 auth::{Permission, Role, User},
3 middleware::{Admin, Authenticated, OptionalAuth},
4};
5use axum::{Json, extract::State, http::StatusCode};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::infrastructure::web::api_v1::AppState;
11
12#[derive(Debug, Deserialize)]
17pub struct RegisterRequest {
18 pub username: String,
19 pub email: String,
20 pub password: String,
21 pub role: Option<Role>,
22 pub tenant_id: Option<String>,
23}
24
25#[derive(Debug, Serialize)]
26pub struct RegisterResponse {
27 pub user_id: Uuid,
28 pub username: String,
29 pub email: String,
30 pub role: Role,
31 pub tenant_id: String,
32}
33
34#[derive(Debug, Deserialize)]
35pub struct LoginRequest {
36 pub username: String,
37 pub password: String,
38}
39
40#[derive(Debug, Serialize)]
41pub struct LoginResponse {
42 pub token: String,
43 pub user: UserInfo,
44}
45
46#[derive(Debug, Serialize)]
47pub struct UserInfo {
48 pub id: Uuid,
49 pub username: String,
50 pub email: String,
51 pub role: Role,
52 pub tenant_id: String,
53}
54
55impl From<User> for UserInfo {
56 fn from(user: User) -> Self {
57 Self {
58 id: user.id,
59 username: user.username,
60 email: user.email,
61 role: user.role,
62 tenant_id: user.tenant_id,
63 }
64 }
65}
66
67#[derive(Debug, Deserialize)]
68pub struct CreateApiKeyRequest {
69 pub name: String,
70 pub role: Option<Role>,
71 pub expires_in_days: Option<i64>,
72 pub tenant_id: Option<String>,
75}
76
77#[derive(Debug, Serialize)]
78pub struct CreateApiKeyResponse {
79 pub id: Uuid,
80 pub name: String,
81 pub key: String,
82 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
83}
84
85#[derive(Debug, Serialize)]
86pub struct ApiKeyInfo {
87 pub id: Uuid,
88 pub name: String,
89 pub tenant_id: String,
90 pub role: Role,
91 pub created_at: chrono::DateTime<chrono::Utc>,
92 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
93 pub active: bool,
94 pub last_used: Option<chrono::DateTime<chrono::Utc>>,
95}
96
97pub async fn register_handler(
104 State(state): State<AppState>,
105 OptionalAuth(auth): OptionalAuth,
106 Json(req): Json<RegisterRequest>,
107) -> Result<(StatusCode, Json<RegisterResponse>), (StatusCode, String)> {
108 let is_admin_request = auth.is_some();
110 if let Some(auth_ctx) = auth {
111 auth_ctx
112 .require_permission(Permission::Admin)
113 .map_err(|_| {
114 (
115 StatusCode::FORBIDDEN,
116 "Admin permission required".to_string(),
117 )
118 })?;
119 }
120
121 let role = req.role.unwrap_or(Role::Developer);
122
123 let tenant_id = if let Some(tid) = req.tenant_id {
127 tid
128 } else if is_admin_request {
129 "default".to_string()
131 } else {
132 let tid = format!("tenant-{}", req.username.to_lowercase().replace(' ', "-"));
134 tracing::info!(
135 "Auto-creating isolated tenant '{tid}' for new user '{}'",
136 req.username
137 );
138 tid
139 };
140
141 let user = state
142 .auth_manager
143 .register_user(
144 req.username,
145 req.email,
146 &req.password,
147 role.clone(),
148 tenant_id.clone(),
149 )
150 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
151
152 Ok((
153 StatusCode::CREATED,
154 Json(RegisterResponse {
155 user_id: user.id,
156 username: user.username,
157 email: user.email,
158 role,
159 tenant_id,
160 }),
161 ))
162}
163
164pub async fn login_handler(
167 State(state): State<AppState>,
168 Json(req): Json<LoginRequest>,
169) -> Result<Json<LoginResponse>, (StatusCode, String)> {
170 let token = state
171 .auth_manager
172 .authenticate(&req.username, &req.password)
173 .map_err(|e| (StatusCode::UNAUTHORIZED, e.to_string()))?;
174
175 let user_id = state
177 .auth_manager
178 .validate_token(&token)
179 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
180 .sub
181 .parse::<Uuid>()
182 .map_err(|_| {
183 (
184 StatusCode::INTERNAL_SERVER_ERROR,
185 "Invalid user ID".to_string(),
186 )
187 })?;
188
189 let user = state
190 .auth_manager
191 .get_user(&user_id)
192 .ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))?;
193
194 Ok(Json(LoginResponse {
195 token,
196 user: user.into(),
197 }))
198}
199
200pub async fn me_handler(
208 State(state): State<AppState>,
209 Authenticated(auth_ctx): Authenticated,
210) -> Result<Json<UserInfo>, (StatusCode, String)> {
211 if let Ok(user_id) = auth_ctx.user_id().parse::<Uuid>()
213 && let Some(user) = state.auth_manager.get_user(&user_id)
214 {
215 return Ok(Json(user.into()));
216 }
217
218 Ok(Json(UserInfo {
222 id: auth_ctx.user_id().parse::<Uuid>().unwrap_or_default(),
223 username: auth_ctx.user_id().to_string(),
224 email: String::new(),
225 role: auth_ctx.claims.role.clone(),
226 tenant_id: auth_ctx.tenant_id().to_string(),
227 }))
228}
229
230pub async fn create_api_key_handler(
237 State(state): State<AppState>,
238 Authenticated(auth_ctx): Authenticated,
239 Json(req): Json<CreateApiKeyRequest>,
240) -> Result<(StatusCode, Json<CreateApiKeyResponse>), (StatusCode, String)> {
241 let role = req.role.unwrap_or(Role::ServiceAccount);
242
243 auth_ctx
247 .require_permission(Permission::Admin)
248 .map_err(|_| {
249 (
250 StatusCode::FORBIDDEN,
251 "Admin permission required to create API keys".to_string(),
252 )
253 })?;
254
255 let expires_at = req
256 .expires_in_days
257 .map(|days| chrono::Utc::now() + chrono::Duration::days(days));
258
259 let effective_tenant_id = if let Some(ref tid) = req.tenant_id {
261 if auth_ctx.require_permission(Permission::Admin).is_ok() {
262 tid.clone()
263 } else {
264 auth_ctx.tenant_id().to_string()
265 }
266 } else {
267 auth_ctx.tenant_id().to_string()
268 };
269
270 let (api_key, key) =
271 state
272 .auth_manager
273 .create_api_key(req.name.clone(), effective_tenant_id, role, expires_at);
274
275 Ok((
276 StatusCode::CREATED,
277 Json(CreateApiKeyResponse {
278 id: api_key.id,
279 name: req.name,
280 key,
281 expires_at,
282 }),
283 ))
284}
285
286pub async fn list_api_keys_handler(
289 State(state): State<AppState>,
290 Authenticated(auth_ctx): Authenticated,
291) -> Result<Json<Vec<ApiKeyInfo>>, (StatusCode, String)> {
292 let keys = state.auth_manager.list_api_keys(auth_ctx.tenant_id());
293
294 let key_infos: Vec<ApiKeyInfo> = keys
295 .into_iter()
296 .map(|k| ApiKeyInfo {
297 id: k.id,
298 name: k.name,
299 tenant_id: k.tenant_id,
300 role: k.role,
301 created_at: k.created_at,
302 expires_at: k.expires_at,
303 active: k.active,
304 last_used: k.last_used,
305 })
306 .collect();
307
308 Ok(Json(key_infos))
309}
310
311pub async fn revoke_api_key_handler(
314 State(state): State<AppState>,
315 Authenticated(auth_ctx): Authenticated,
316 axum::extract::Path(key_id): axum::extract::Path<Uuid>,
317) -> Result<StatusCode, (StatusCode, String)> {
318 auth_ctx
319 .require_permission(Permission::Write)
320 .map_err(|_| {
321 (
322 StatusCode::FORBIDDEN,
323 "Write permission required".to_string(),
324 )
325 })?;
326
327 state
328 .auth_manager
329 .revoke_api_key(&key_id)
330 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
331
332 Ok(StatusCode::NO_CONTENT)
333}
334
335pub async fn list_users_handler(
338 State(state): State<AppState>,
339 Admin(_): Admin,
340) -> Json<Vec<UserInfo>> {
341 let users = state.auth_manager.list_users();
342 Json(users.into_iter().map(UserInfo::from).collect())
343}
344
345pub async fn delete_user_handler(
348 State(state): State<AppState>,
349 Admin(_): Admin,
350 axum::extract::Path(user_id): axum::extract::Path<Uuid>,
351) -> Result<StatusCode, (StatusCode, String)> {
352 state
353 .auth_manager
354 .delete_user(&user_id)
355 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
356
357 Ok(StatusCode::NO_CONTENT)
358}