Skip to main content

allsource_core/infrastructure/web/
auth_api.rs

1use 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
9// AppState is defined in api_v1.rs and re-exported
10use crate::infrastructure::web::api_v1::AppState;
11
12// ============================================================================
13// Request/Response Types
14// ============================================================================
15
16#[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}
73
74#[derive(Debug, Serialize)]
75pub struct CreateApiKeyResponse {
76    pub id: Uuid,
77    pub name: String,
78    pub key: String,
79    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
80}
81
82#[derive(Debug, Serialize)]
83pub struct ApiKeyInfo {
84    pub id: Uuid,
85    pub name: String,
86    pub tenant_id: String,
87    pub role: Role,
88    pub created_at: chrono::DateTime<chrono::Utc>,
89    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
90    pub active: bool,
91    pub last_used: Option<chrono::DateTime<chrono::Utc>>,
92}
93
94// ============================================================================
95// Handlers
96// ============================================================================
97
98/// Register a new user
99/// POST /api/v1/auth/register
100pub async fn register_handler(
101    State(state): State<AppState>,
102    OptionalAuth(auth): OptionalAuth,
103    Json(req): Json<RegisterRequest>,
104) -> Result<(StatusCode, Json<RegisterResponse>), (StatusCode, String)> {
105    // Only admins can register other users (or allow self-registration in dev mode)
106    if let Some(auth_ctx) = auth {
107        auth_ctx
108            .require_permission(Permission::Admin)
109            .map_err(|_| {
110                (
111                    StatusCode::FORBIDDEN,
112                    "Admin permission required".to_string(),
113                )
114            })?;
115    }
116
117    let role = req.role.unwrap_or(Role::Developer);
118    let tenant_id = req.tenant_id.unwrap_or_else(|| "default".to_string());
119
120    let user = state
121        .auth_manager
122        .register_user(
123            req.username,
124            req.email,
125            &req.password,
126            role.clone(),
127            tenant_id.clone(),
128        )
129        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
130
131    Ok((
132        StatusCode::CREATED,
133        Json(RegisterResponse {
134            user_id: user.id,
135            username: user.username,
136            email: user.email,
137            role,
138            tenant_id,
139        }),
140    ))
141}
142
143/// Login with username and password
144/// POST /api/v1/auth/login
145pub async fn login_handler(
146    State(state): State<AppState>,
147    Json(req): Json<LoginRequest>,
148) -> Result<Json<LoginResponse>, (StatusCode, String)> {
149    let token = state
150        .auth_manager
151        .authenticate(&req.username, &req.password)
152        .map_err(|e| (StatusCode::UNAUTHORIZED, e.to_string()))?;
153
154    // Get user info
155    let user_id = state
156        .auth_manager
157        .validate_token(&token)
158        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
159        .sub
160        .parse::<Uuid>()
161        .map_err(|_| {
162            (
163                StatusCode::INTERNAL_SERVER_ERROR,
164                "Invalid user ID".to_string(),
165            )
166        })?;
167
168    let user = state
169        .auth_manager
170        .get_user(&user_id)
171        .ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))?;
172
173    Ok(Json(LoginResponse {
174        token,
175        user: user.into(),
176    }))
177}
178
179/// Get current user info
180/// GET /api/v1/auth/me
181///
182/// Returns identity info for the authenticated caller. Works for:
183/// - JWT-authenticated users (looks up full user record)
184/// - API key authentication (returns identity from claims)
185/// - Dev mode (returns synthetic dev identity from claims)
186pub async fn me_handler(
187    State(state): State<AppState>,
188    Authenticated(auth_ctx): Authenticated,
189) -> Result<Json<UserInfo>, (StatusCode, String)> {
190    // Try to look up a full user record if the subject is a valid UUID
191    if let Ok(user_id) = auth_ctx.user_id().parse::<Uuid>()
192        && let Some(user) = state.auth_manager.get_user(&user_id)
193    {
194        return Ok(Json(user.into()));
195    }
196
197    // For API key auth and dev mode, synthesize identity from claims.
198    // This is the path Query Service uses to validate API keys — it only
199    // needs tenant_id and role, not a full user record.
200    Ok(Json(UserInfo {
201        id: auth_ctx.user_id().parse::<Uuid>().unwrap_or_default(),
202        username: auth_ctx.user_id().to_string(),
203        email: String::new(),
204        role: auth_ctx.claims.role.clone(),
205        tenant_id: auth_ctx.tenant_id().to_string(),
206    }))
207}
208
209/// Create API key
210/// POST /api/v1/auth/api-keys
211pub async fn create_api_key_handler(
212    State(state): State<AppState>,
213    Authenticated(auth_ctx): Authenticated,
214    Json(req): Json<CreateApiKeyRequest>,
215) -> Result<(StatusCode, Json<CreateApiKeyResponse>), (StatusCode, String)> {
216    // Users can create API keys for themselves
217    // Or admins can create for any tenant
218    let role = req.role.unwrap_or(Role::ServiceAccount);
219
220    auth_ctx
221        .require_permission(Permission::Write)
222        .map_err(|_| {
223            (
224                StatusCode::FORBIDDEN,
225                "Write permission required".to_string(),
226            )
227        })?;
228
229    let expires_at = req
230        .expires_in_days
231        .map(|days| chrono::Utc::now() + chrono::Duration::days(days));
232
233    let (api_key, key) = state.auth_manager.create_api_key(
234        req.name.clone(),
235        auth_ctx.tenant_id().to_string(),
236        role,
237        expires_at,
238    );
239
240    Ok((
241        StatusCode::CREATED,
242        Json(CreateApiKeyResponse {
243            id: api_key.id,
244            name: req.name,
245            key,
246            expires_at,
247        }),
248    ))
249}
250
251/// List API keys
252/// GET /api/v1/auth/api-keys
253pub async fn list_api_keys_handler(
254    State(state): State<AppState>,
255    Authenticated(auth_ctx): Authenticated,
256) -> Result<Json<Vec<ApiKeyInfo>>, (StatusCode, String)> {
257    let keys = state.auth_manager.list_api_keys(auth_ctx.tenant_id());
258
259    let key_infos: Vec<ApiKeyInfo> = keys
260        .into_iter()
261        .map(|k| ApiKeyInfo {
262            id: k.id,
263            name: k.name,
264            tenant_id: k.tenant_id,
265            role: k.role,
266            created_at: k.created_at,
267            expires_at: k.expires_at,
268            active: k.active,
269            last_used: k.last_used,
270        })
271        .collect();
272
273    Ok(Json(key_infos))
274}
275
276/// Revoke API key
277/// DELETE /api/v1/auth/api-keys/:id
278pub async fn revoke_api_key_handler(
279    State(state): State<AppState>,
280    Authenticated(auth_ctx): Authenticated,
281    axum::extract::Path(key_id): axum::extract::Path<Uuid>,
282) -> Result<StatusCode, (StatusCode, String)> {
283    auth_ctx
284        .require_permission(Permission::Write)
285        .map_err(|_| {
286            (
287                StatusCode::FORBIDDEN,
288                "Write permission required".to_string(),
289            )
290        })?;
291
292    state
293        .auth_manager
294        .revoke_api_key(&key_id)
295        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
296
297    Ok(StatusCode::NO_CONTENT)
298}
299
300/// List all users (admin only)
301/// GET /api/v1/auth/users
302pub async fn list_users_handler(
303    State(state): State<AppState>,
304    Admin(_): Admin,
305) -> Json<Vec<UserInfo>> {
306    let users = state.auth_manager.list_users();
307    Json(users.into_iter().map(UserInfo::from).collect())
308}
309
310/// Delete user (admin only)
311/// DELETE /api/v1/auth/users/:id
312pub async fn delete_user_handler(
313    State(state): State<AppState>,
314    Admin(_): Admin,
315    axum::extract::Path(user_id): axum::extract::Path<Uuid>,
316) -> Result<StatusCode, (StatusCode, String)> {
317    state
318        .auth_manager
319        .delete_user(&user_id)
320        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
321
322    Ok(StatusCode::NO_CONTENT)
323}