Files
k-notes/notes-api/src/routes/auth.rs
2025-12-23 02:15:25 +01:00

110 lines
2.8 KiB
Rust

//! Authentication routes
use axum::{Json, extract::State, http::StatusCode};
use axum_login::AuthSession;
use validator::Validate;
use notes_domain::User;
use password_auth::generate_hash;
use crate::auth::{AuthBackend, AuthUser, Credentials};
use crate::dto::{LoginRequest, RegisterRequest};
use crate::error::{ApiError, ApiResult};
use crate::state::AppState;
/// Register a new user
pub async fn register(
State(state): State<AppState>,
mut auth_session: AuthSession<AuthBackend>,
Json(payload): Json<RegisterRequest>,
) -> ApiResult<StatusCode> {
payload
.validate()
.map_err(|e| ApiError::validation(e.to_string()))?;
// Check if user exists
if state
.user_repo
.find_by_email(&payload.email)
.await
.map_err(ApiError::from)?
.is_some()
{
return Err(ApiError::Domain(
notes_domain::DomainError::UserAlreadyExists(payload.email.clone()),
));
}
// Hash password
let password_hash = generate_hash(&payload.password);
// Create use
// For local registration, we use email as subject
let user = User::new_local(&payload.email, &password_hash);
state.user_repo.save(&user).await.map_err(ApiError::from)?;
// Auto login after registration
let user = AuthUser(user);
auth_session
.login(&user)
.await
.map_err(|e| ApiError::internal(e.to_string()))?;
Ok(StatusCode::CREATED)
}
/// Login user
pub async fn login(
mut auth_session: AuthSession<AuthBackend>,
Json(payload): Json<LoginRequest>,
) -> ApiResult<StatusCode> {
payload
.validate()
.map_err(|e| ApiError::validation(e.to_string()))?;
let user = auth_session
.authenticate(Credentials {
email: payload.email,
password: payload.password,
})
.await
.map_err(|e| ApiError::internal(e.to_string()))?
.ok_or_else(|| ApiError::validation("Invalid email or password"))?; // Generic error for security
auth_session
.login(&user)
.await
.map_err(|e| ApiError::internal(e.to_string()))?;
Ok(StatusCode::OK)
}
/// Logout user
pub async fn logout(mut auth_session: AuthSession<AuthBackend>) -> ApiResult<StatusCode> {
auth_session
.logout()
.await
.map_err(|e| ApiError::internal(e.to_string()))?;
Ok(StatusCode::OK)
}
/// Get current user
pub async fn me(
auth_session: AuthSession<AuthBackend>,
) -> ApiResult<Json<crate::dto::UserResponse>> {
let user =
auth_session
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Not logged in".to_string(),
)))?;
Ok(Json(crate::dto::UserResponse {
id: user.0.id,
email: user.0.email.clone(),
created_at: user.0.created_at,
}))
}