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
181pub async fn me_handler(
182    State(state): State<AppState>,
183    Authenticated(auth_ctx): Authenticated,
184) -> Result<Json<UserInfo>, (StatusCode, String)> {
185    let user_id = auth_ctx.user_id().parse::<Uuid>().map_err(|_| {
186        (
187            StatusCode::INTERNAL_SERVER_ERROR,
188            "Invalid user ID".to_string(),
189        )
190    })?;
191
192    let user = state
193        .auth_manager
194        .get_user(&user_id)
195        .ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))?;
196
197    Ok(Json(user.into()))
198}
199
200/// Create API key
201/// POST /api/v1/auth/api-keys
202pub async fn create_api_key_handler(
203    State(state): State<AppState>,
204    Authenticated(auth_ctx): Authenticated,
205    Json(req): Json<CreateApiKeyRequest>,
206) -> Result<(StatusCode, Json<CreateApiKeyResponse>), (StatusCode, String)> {
207    // Users can create API keys for themselves
208    // Or admins can create for any tenant
209    let role = req.role.unwrap_or(Role::ServiceAccount);
210
211    auth_ctx
212        .require_permission(Permission::Write)
213        .map_err(|_| {
214            (
215                StatusCode::FORBIDDEN,
216                "Write permission required".to_string(),
217            )
218        })?;
219
220    let expires_at = req
221        .expires_in_days
222        .map(|days| chrono::Utc::now() + chrono::Duration::days(days));
223
224    let (api_key, key) = state.auth_manager.create_api_key(
225        req.name.clone(),
226        auth_ctx.tenant_id().to_string(),
227        role,
228        expires_at,
229    );
230
231    Ok((
232        StatusCode::CREATED,
233        Json(CreateApiKeyResponse {
234            id: api_key.id,
235            name: req.name,
236            key,
237            expires_at,
238        }),
239    ))
240}
241
242/// List API keys
243/// GET /api/v1/auth/api-keys
244pub async fn list_api_keys_handler(
245    State(state): State<AppState>,
246    Authenticated(auth_ctx): Authenticated,
247) -> Result<Json<Vec<ApiKeyInfo>>, (StatusCode, String)> {
248    let keys = state.auth_manager.list_api_keys(auth_ctx.tenant_id());
249
250    let key_infos: Vec<ApiKeyInfo> = keys
251        .into_iter()
252        .map(|k| ApiKeyInfo {
253            id: k.id,
254            name: k.name,
255            tenant_id: k.tenant_id,
256            role: k.role,
257            created_at: k.created_at,
258            expires_at: k.expires_at,
259            active: k.active,
260            last_used: k.last_used,
261        })
262        .collect();
263
264    Ok(Json(key_infos))
265}
266
267/// Revoke API key
268/// DELETE /api/v1/auth/api-keys/:id
269pub async fn revoke_api_key_handler(
270    State(state): State<AppState>,
271    Authenticated(auth_ctx): Authenticated,
272    axum::extract::Path(key_id): axum::extract::Path<Uuid>,
273) -> Result<StatusCode, (StatusCode, String)> {
274    auth_ctx
275        .require_permission(Permission::Write)
276        .map_err(|_| {
277            (
278                StatusCode::FORBIDDEN,
279                "Write permission required".to_string(),
280            )
281        })?;
282
283    state
284        .auth_manager
285        .revoke_api_key(&key_id)
286        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
287
288    Ok(StatusCode::NO_CONTENT)
289}
290
291/// List all users (admin only)
292/// GET /api/v1/auth/users
293pub async fn list_users_handler(
294    State(state): State<AppState>,
295    Admin(_): Admin,
296) -> Json<Vec<UserInfo>> {
297    let users = state.auth_manager.list_users();
298    Json(users.into_iter().map(UserInfo::from).collect())
299}
300
301/// Delete user (admin only)
302/// DELETE /api/v1/auth/users/:id
303pub async fn delete_user_handler(
304    State(state): State<AppState>,
305    Admin(_): Admin,
306    axum::extract::Path(user_id): axum::extract::Path<Uuid>,
307) -> Result<StatusCode, (StatusCode, String)> {
308    state
309        .auth_manager
310        .delete_user(&user_id)
311        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
312
313    Ok(StatusCode::NO_CONTENT)
314}