use std::time::{SystemTime, UNIX_EPOCH}; use axum::extract::{Json, State}; use axum::response::IntoResponse; use jsonwebtoken::{encode, Header}; use serde::{Deserialize, Serialize}; use crate::AppState; use crate::auth::{check_password}; use super::errors::RouteError; #[derive(Deserialize)] pub struct LoginRequest { email: String, password: String, } #[derive(Serialize)] struct LoginResponse { token: String, } #[derive(Serialize)] struct JWTClaims { sub: String, exp: usize, iat: usize, is_admin: bool, } pub async fn login( State(state): State, Json(request): Json ) -> Result { let user = match state.database.fetch_user_by_email(&request.email) { Err(_) => return Err(RouteError::Internal("database action failed".into())), Ok(None) => return Err(RouteError::UnregisteredEmail(request.email)), Ok(Some(user)) => user }; match check_password(&request.password, user.password_hash()) { Err(_) => return Err(RouteError::Internal("failed to check password".into())), Ok(false) => return Err(RouteError::AuthorizationFailure()), Ok(true) => {}, } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| RouteError::Internal("failed to access system clock".into()))? .as_secs() as usize; let claims = JWTClaims { sub: user.email().to_owned(), iat: now, exp: now + 60 * 60 * 24, is_admin: false }; let token= encode(&Header::default(), &claims, &state.api_key) .map_err(|e| RouteError::Internal(format!("failed to encode jwt: {e}")))?; Ok(Json(LoginResponse { token })) }