allsource_core/
auth_api.rs

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