diff options
Diffstat (limited to 'src/login/routes.rs')
| -rw-r--r-- | src/login/routes.rs | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/src/login/routes.rs b/src/login/routes.rs new file mode 100644 index 0000000..c9def2a --- /dev/null +++ b/src/login/routes.rs @@ -0,0 +1,78 @@ +use axum::{ + extract::{Form, State}, + http::StatusCode, + response::IntoResponse, + routing::post, + Router, +}; +use axum_extra::extract::cookie::{Cookie, CookieJar}; +use chrono::Utc; +use sqlx::sqlite::SqlitePool; + +use crate::error::InternalError; + +use super::repo::{logins::Provider as _, tokens::Provider as _}; + +pub fn router() -> Router<SqlitePool> { + Router::new().route("/login", post(on_login)) +} + +#[derive(serde::Deserialize)] +struct Login { + name: String, + password: String, +} + +async fn on_login( + State(db): State<SqlitePool>, + cookies: CookieJar, + Form(form): Form<Login>, +) -> Result<impl IntoResponse, InternalError> { + let now = Utc::now(); + let mut tx = db.begin().await?; + + // Spelling the following in the more conventional form, + // if let Some(…) = create().await? {} + // else if let Some(…) = validate().await? {} + // else {} + // pushes the specifics of whether the returned error types are Send or not + // (they aren't) into the type of this function's generated Futures, which + // in turn makes this function unusable as an Axum handler. + let login = tx.logins().create(&form.name, &form.password).await?; + let login = if login.is_some() { + login + } else { + tx.logins().authenticate(&form.name, &form.password).await? + }; + + // If `login` is Some, then we have an identity and can issue an identity + // token. If `login` is None, then neither creating a new login nor authenticating + // an existing one succeeded, and we must reject the attempt. + // + // These properties will be transferred to `token`, as well. + let token = if let Some(login) = login { + Some(tx.tokens().issue(&login.id, now).await?) + } else { + None + }; + + tx.commit().await?; + + let resp = if let Some(token) = token { + let cookie = Cookie::build(("identity", token)) + .http_only(true) + .permanent() + .build(); + let cookies = cookies.add(cookie); + + (StatusCode::OK, cookies, "logged in") + } else { + ( + StatusCode::UNAUTHORIZED, + cookies, + "invalid name or password", + ) + }; + + Ok(resp) +} |
