allsource_core/
auth_api.rs

1use crate::auth::{Permission, Role, User};
2use crate::middleware::{Admin, Authenticated};
3use axum::{
4    extract::State,
5    http::StatusCode,
6    Json,
7};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11// AppState is defined in api_v1.rs and re-exported
12use crate::api_v1::AppState;
13
14// ============================================================================
15// Request/Response Types
16// ============================================================================
17
18#[derive(Debug, Deserialize)]
19pub struct RegisterRequest {
20    pub username: String,
21    pub email: String,
22    pub password: String,
23    pub role: Option<Role>,
24    pub tenant_id: Option<String>,
25}
26
27#[derive(Debug, Serialize)]
28pub struct RegisterResponse {
29    pub user_id: Uuid,
30    pub username: String,
31    pub email: String,
32    pub role: Role,
33    pub tenant_id: String,
34}
35
36#[derive(Debug, Deserialize)]
37pub struct LoginRequest {
38    pub username: String,
39    pub password: String,
40}
41
42#[derive(Debug, Serialize)]
43pub struct LoginResponse {
44    pub token: String,
45    pub user: UserInfo,
46}
47
48#[derive(Debug, Serialize)]
49pub struct UserInfo {
50    pub id: Uuid,
51    pub username: String,
52    pub email: String,
53    pub role: Role,
54    pub tenant_id: String,
55}
56
57impl From<User> for UserInfo {
58    fn from(user: User) -> Self {
59        Self {
60            id: user.id,
61            username: user.username,
62            email: user.email,
63            role: user.role,
64            tenant_id: user.tenant_id,
65        }
66    }
67}
68
69#[derive(Debug, Deserialize)]
70pub struct CreateApiKeyRequest {
71    pub name: String,
72    pub role: Option<Role>,
73    pub expires_in_days: Option<i64>,
74}
75
76#[derive(Debug, Serialize)]
77pub struct CreateApiKeyResponse {
78    pub id: Uuid,
79    pub name: String,
80    pub key: String,
81    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
82}
83
84#[derive(Debug, Serialize)]
85pub struct ApiKeyInfo {
86    pub id: Uuid,
87    pub name: String,
88    pub tenant_id: String,
89    pub role: Role,
90    pub created_at: chrono::DateTime<chrono::Utc>,
91    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
92    pub active: bool,
93    pub last_used: Option<chrono::DateTime<chrono::Utc>>,
94}
95
96// ============================================================================
97// Handlers
98// ============================================================================
99
100/// Register a new user
101/// POST /api/v1/auth/register
102pub async fn register_handler(
103    State(state): State<AppState>,
104    auth: Option<Authenticated>,
105    Json(req): Json<RegisterRequest>,
106) -> Result<(StatusCode, Json<RegisterResponse>), (StatusCode, String)> {
107    // Only admins can register other users (or allow self-registration in dev mode)
108    if let Some(auth_ctx) = auth {
109        auth_ctx
110            .0
111            .require_permission(Permission::Admin)
112            .map_err(|_| (StatusCode::FORBIDDEN, "Admin permission required".to_string()))?;
113    }
114
115    let role = req.role.unwrap_or(Role::Developer);
116    let tenant_id = req.tenant_id.unwrap_or_else(|| "default".to_string());
117
118    let user = state
119        .auth_manager
120        .register_user(req.username, req.email, &req.password, role.clone(), tenant_id.clone())
121        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
122
123    Ok((
124        StatusCode::CREATED,
125        Json(RegisterResponse {
126            user_id: user.id,
127            username: user.username,
128            email: user.email,
129            role,
130            tenant_id,
131        }),
132    ))
133}
134
135/// Login with username and password
136/// POST /api/v1/auth/login
137pub async fn login_handler(
138    State(state): State<AppState>,
139    Json(req): Json<LoginRequest>,
140) -> Result<Json<LoginResponse>, (StatusCode, String)> {
141    let token = state
142        .auth_manager
143        .authenticate(&req.username, &req.password)
144        .map_err(|e| (StatusCode::UNAUTHORIZED, e.to_string()))?;
145
146    // Get user info
147    let user_id = state
148        .auth_manager
149        .validate_token(&token)
150        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
151        .sub
152        .parse::<Uuid>()
153        .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid user ID".to_string()))?;
154
155    let user = state
156        .auth_manager
157        .get_user(&user_id)
158        .ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))?;
159
160    Ok(Json(LoginResponse {
161        token,
162        user: user.into(),
163    }))
164}
165
166/// Get current user info
167/// GET /api/v1/auth/me
168pub async fn me_handler(
169    State(state): State<AppState>,
170    Authenticated(auth_ctx): Authenticated,
171) -> Result<Json<UserInfo>, (StatusCode, String)> {
172    let user_id = auth_ctx
173        .user_id()
174        .parse::<Uuid>()
175        .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid user ID".to_string()))?;
176
177    let user = state
178        .auth_manager
179        .get_user(&user_id)
180        .ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))?;
181
182    Ok(Json(user.into()))
183}
184
185/// Create API key
186/// POST /api/v1/auth/api-keys
187pub async fn create_api_key_handler(
188    State(state): State<AppState>,
189    Authenticated(auth_ctx): Authenticated,
190    Json(req): Json<CreateApiKeyRequest>,
191) -> Result<(StatusCode, Json<CreateApiKeyResponse>), (StatusCode, String)> {
192    // Users can create API keys for themselves
193    // Or admins can create for any tenant
194    let role = req.role.unwrap_or(Role::ServiceAccount);
195
196    auth_ctx
197        .require_permission(Permission::Write)
198        .map_err(|_| (StatusCode::FORBIDDEN, "Write permission required".to_string()))?;
199
200    let expires_at = req.expires_in_days.map(|days| {
201        chrono::Utc::now() + chrono::Duration::days(days)
202    });
203
204    let (api_key, key) = state
205        .auth_manager
206        .create_api_key(req.name.clone(), auth_ctx.tenant_id().to_string(), role, expires_at);
207
208    Ok((
209        StatusCode::CREATED,
210        Json(CreateApiKeyResponse {
211            id: api_key.id,
212            name: req.name,
213            key,
214            expires_at,
215        }),
216    ))
217}
218
219/// List API keys
220/// GET /api/v1/auth/api-keys
221pub async fn list_api_keys_handler(
222    State(state): State<AppState>,
223    Authenticated(auth_ctx): Authenticated,
224) -> Result<Json<Vec<ApiKeyInfo>>, (StatusCode, String)> {
225    let keys = state.auth_manager.list_api_keys(auth_ctx.tenant_id());
226
227    let key_infos: Vec<ApiKeyInfo> = keys
228        .into_iter()
229        .map(|k| ApiKeyInfo {
230            id: k.id,
231            name: k.name,
232            tenant_id: k.tenant_id,
233            role: k.role,
234            created_at: k.created_at,
235            expires_at: k.expires_at,
236            active: k.active,
237            last_used: k.last_used,
238        })
239        .collect();
240
241    Ok(Json(key_infos))
242}
243
244/// Revoke API key
245/// DELETE /api/v1/auth/api-keys/:id
246pub async fn revoke_api_key_handler(
247    State(state): State<AppState>,
248    Authenticated(auth_ctx): Authenticated,
249    axum::extract::Path(key_id): axum::extract::Path<Uuid>,
250) -> Result<StatusCode, (StatusCode, String)> {
251    auth_ctx
252        .require_permission(Permission::Write)
253        .map_err(|_| (StatusCode::FORBIDDEN, "Write permission required".to_string()))?;
254
255    state
256        .auth_manager
257        .revoke_api_key(&key_id)
258        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
259
260    Ok(StatusCode::NO_CONTENT)
261}
262
263/// List all users (admin only)
264/// GET /api/v1/auth/users
265pub async fn list_users_handler(
266    State(state): State<AppState>,
267    Admin(_): Admin,
268) -> Json<Vec<UserInfo>> {
269    let users = state.auth_manager.list_users();
270    Json(users.into_iter().map(UserInfo::from).collect())
271}
272
273/// Delete user (admin only)
274/// DELETE /api/v1/auth/users/:id
275pub async fn delete_user_handler(
276    State(state): State<AppState>,
277    Admin(_): Admin,
278    axum::extract::Path(user_id): axum::extract::Path<Uuid>,
279) -> Result<StatusCode, (StatusCode, String)> {
280    state
281        .auth_manager
282        .delete_user(&user_id)
283        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
284
285    Ok(StatusCode::NO_CONTENT)
286}