summaryrefslogtreecommitdiff
path: root/src/routes/auth.rs
diff options
context:
space:
mode:
authorDaniel Hader <[email protected]>2026-05-20 22:04:16 -0500
committerDaniel Hader <[email protected]>2026-05-20 22:04:16 -0500
commit92436c8bb9eafcc56219e784f8b374edfb1907a3 (patch)
tree45c9ebc50220414c20700e653cb17dd0c21ec00f /src/routes/auth.rs
parent74bc939843ae5c35fbd367c1ef0144b6074cfefe (diff)
basic login route with JWT
Diffstat (limited to 'src/routes/auth.rs')
-rw-r--r--src/routes/auth.rs66
1 files changed, 66 insertions, 0 deletions
diff --git a/src/routes/auth.rs b/src/routes/auth.rs
new file mode 100644
index 0000000..0b17ef4
--- /dev/null
+++ b/src/routes/auth.rs
@@ -0,0 +1,66 @@
+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<AppState>,
+ Json(request): Json<LoginRequest>
+) -> Result<impl IntoResponse, RouteError> {
+
+ 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(|_| RouteError::Internal("failed to encode jwt".into()))?;
+
+ Ok(Json(LoginResponse { token }))
+}
+
+