mabel/src/routes/auth.rs

101 lines
2.5 KiB
Rust

use axum::{
extract::State,
http::{header::SET_COOKIE, StatusCode},
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use chrono::Duration;
use serde::Deserialize;
use serde_json::json;
use std::sync::Arc;
use crate::{
auth::{hash::verify, session::Session, user::User},
http::{
error::ApiError,
json::JsonBody,
session::{RequireSession, RequireUser},
},
state::AppState,
};
#[derive(Deserialize)]
struct LoginRequest {
pub username: String,
pub password: String,
}
async fn login(
State(state): State<Arc<AppState>>,
JsonBody(payload): JsonBody<LoginRequest>,
) -> impl IntoResponse {
let user = User::find(&state.database, payload.username.as_str())
.await
.map_err(ApiError::from)?;
let invalid = || -> ApiError {
ApiError::Client {
status: StatusCode::UNAUTHORIZED,
code: "invalid-login",
message: "No matching user was found",
}
};
let user = user.ok_or_else(invalid)?;
let plaintext = user.password.ok_or_else(invalid)?;
if !verify(&payload.password, &plaintext).map_err(ApiError::from)? {
return Err(invalid());
}
let session = Session::create(
&state.database,
user.id,
Duration::seconds(state.config.session_duration),
)
.await
.map_err(ApiError::from)?;
let token = session.token();
let mut response: Response =
Json(json!({ "session_token": token, "expires_at": session.expires_at })).into_response();
response.headers_mut().insert(
SET_COOKIE,
session
.cookie(&state.config.domain(), state.config.secure())
.parse()
.map_err(anyhow::Error::from)?,
);
Ok(response)
}
async fn me(RequireUser(user): RequireUser) -> Result<String, ApiError<'static>> {
Ok(user.name)
}
async fn logout(
State(state): State<Arc<AppState>>,
RequireSession(session): RequireSession,
) -> Result<impl IntoResponse, ApiError<'static>> {
session.destroy(&state.database).await?;
let mut response: Response = Json(json!({ "ok": true })).into_response();
response.headers_mut().insert(
SET_COOKIE,
Session::cookie_for_delete(&state.config.domain(), state.config.secure())
.parse()
.map_err(anyhow::Error::from)?,
);
Ok(response)
}
pub fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/login", post(login))
.route("/logout", post(logout))
.route("/me", get(me))
}