Mercurial > lbo > hg > analyrics
changeset 3:ca6c273aeb4f
Implement templating
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 09 Jul 2022 09:32:13 -0700 |
parents | 93ea889e6009 |
children | 7e94d639963c |
files | Cargo.toml Rocket.toml assets/index.html.hbs assets/login.html.hbs index.html login.html src/main.rs |
diffstat | 7 files changed, 110 insertions(+), 58 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.toml Sat Jul 09 08:44:23 2022 -0700 +++ b/Cargo.toml Sat Jul 09 09:32:13 2022 -0700 @@ -6,9 +6,11 @@ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.58" env_logger = "0.9.0" log = "0.4.17" rocket = { version = "0.5.0-rc.2", features = ["secrets"] } rocket_db_pools = { version = "0.1.0-rc.2", features = ["sqlx_sqlite"] } +rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] } sha256 = "1.0.3" tokio = { version = "1.19.2", features = ["fs"] }
--- a/Rocket.toml Sat Jul 09 08:44:23 2022 -0700 +++ b/Rocket.toml Sat Jul 09 09:32:13 2022 -0700 @@ -4,3 +4,4 @@ [default] port = 8000 secret_key = "edeaaff3f1774ad2888673770c6d64097e391bc362d7d6fb34982ddf0efd18cb" +template_dir = "assets"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/assets/index.html.hbs Sat Jul 09 09:32:13 2022 -0700 @@ -0,0 +1,18 @@ +<html> + <head> + <title>Analyrics Login</title> + </head> + <body> + Welcome to anaLyrics. + +{{#if flash}}<div class="flash">{{flash}}</div>{{/if}} +{{#if loggedin}} + <div>Logged in as {{username}}.</div> + <form action="/logout" method="POST"> + <input type="submit" value="Log out" /> + </form> +{{else}} + <a href="/login">Log in</a> +{{/if}} + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/assets/login.html.hbs Sat Jul 09 09:32:13 2022 -0700 @@ -0,0 +1,16 @@ +<html> + <head> + <title>Analyrics Login</title> + </head> + <body> +{{#if flash}}<div class="flash">{{flash}}</div>{{/if}} + + <form action="/login" method="POST"> + <label>User name: </label><input type="text" name="username" /> + <label>Password: </label><input type="text" name="password" /> + <input type="submit" value="Log in" /> + </form> + + + </body> +</html>
--- a/index.html Sat Jul 09 08:44:23 2022 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -<html> - <head> - <title>Analyrics Login</title> - </head> - <body> - Welcome to anaLyrics. - - <a href="/login">Log in</a> - - <form action="/logout" method="POST"> - <input type="submit" value="Log out" /> - </form> - </body> -</html>
--- a/login.html Sat Jul 09 08:44:23 2022 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -<html> - <head> - <title>Analyrics Login</title> - </head> - <body> - - <form action="/login" method="POST"> - <label>User name: </label><input type="text" name="username" /> - <label>Password: </label><input type="text" name="password" /> - <input type="submit" value="Log in" /> - </form> - - - </body> -</html>
--- a/src/main.rs Sat Jul 09 08:44:23 2022 -0700 +++ b/src/main.rs Sat Jul 09 09:32:13 2022 -0700 @@ -1,3 +1,4 @@ +use anyhow::{self, Error}; use log::{debug, error, info, Level}; #[macro_use] @@ -5,12 +6,14 @@ use rocket::form::Form; use rocket::http::{Cookie, CookieJar, Header, Status}; -use rocket::request::{self, FlashMessage}; +use rocket::request::{self, FlashMessage, FromRequest, Outcome, Request}; use rocket::response::{self, Flash, Redirect, Responder, Response}; use rocket_db_pools::sqlx::{self, pool::PoolConnection, Executor, Sqlite, SqlitePool}; use rocket_db_pools::{Connection, Database, Pool}; +use rocket_dyn_templates::{context, Template}; + use std::io; use std::path::Path; @@ -25,7 +28,7 @@ mut conn: PoolConnection<Sqlite>, user: S, password: S, -) -> Result<bool, sqlx::Error> { +) -> Result<bool, Error> { // TODO: salt passwords. let pwdhash = sha256::digest(password.as_ref()); let q = sqlx::query("SELECT username FROM users WHERE username = ? AND password_hash = ?") @@ -37,6 +40,22 @@ const USER_ID_COOKIE_KEY: &str = "user_id"; +struct LoggedInGuard(String); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for LoggedInGuard { + type Error = Error; + + async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> { + let cookies = req.cookies(); + if let Some(uc) = cookies.get_private(USER_ID_COOKIE_KEY) { + Outcome::Success(LoggedInGuard(uc.value().to_string())) + } else { + Outcome::Forward(()) + } + } +} + async fn asset<P: AsRef<Path>>(p: P) -> io::Result<File> { fs::OpenOptions::new().read(true).open(p).await } @@ -45,7 +64,7 @@ enum LoginResponse { // use templates later. #[response(status = 200, content_type = "html")] - Ok { body: File }, + Ok { body: Template }, #[response(status = 302, content_type = "html")] LoggedInAlready { redirect: Redirect }, } @@ -61,17 +80,19 @@ flash: Option<FlashMessage<'_>>, cookies: &CookieJar<'_>, ) -> Result<LoginResponse, InternalServerError> { - if let Some(flash) = flash { - info!("Flash message: {}: {}", flash.kind(), flash.message()); + let f; + if let Some(ref flash) = flash { + f = Some(format!("{}: {}", flash.kind(), flash.message())); + } else { + f = None; } if let Some(cookie) = cookies.get_private(USER_ID_COOKIE_KEY) { - info!("Logged in as {}", cookie.value()); Ok(LoginResponse::LoggedInAlready { redirect: Redirect::to(rocket::uri!("/")), }) } else { Ok(LoginResponse::Ok { - body: asset("login.html").await.unwrap(), + body: Template::render("login", context![flash: f]), }) } } @@ -98,24 +119,43 @@ ) -> Flash<Redirect> { // TO DO: look up user in database. let db = db.into_inner(); - if let Ok(true) = check_user_password(db, &login.username, &login.password).await { - let c = Cookie::new(USER_ID_COOKIE_KEY, login.username.clone()); - cookies.add_private(c); - Flash::success(Redirect::to(rocket::uri!("/")), "Successfully logged in.") - } else { - Flash::error( + match check_user_password(db, &login.username, &login.password).await { + Ok(true) => { + let c = Cookie::new(USER_ID_COOKIE_KEY, login.username.clone()); + cookies.add_private(c); + Flash::success(Redirect::to(rocket::uri!("/")), "Successfully logged in.") + } + Ok(false) => Flash::error( Redirect::to(rocket::uri!("/login")), - "User/password not found.", - ) + "User/password not found", + ), + Err(e) => Flash::error( + Redirect::to(rocket::uri!("/login")), + format!("User/password lookup failed: {}", e), + ), } } -#[rocket::get("/")] -async fn route_index(flash: Option<FlashMessage<'_>>) -> response::content::RawHtml<File> { - if let Some(flash) = flash { - info!("Flash message: {}: {}", flash.kind(), flash.message()); +#[rocket::get("/", rank = 1)] +async fn route_index_loggedin(lig: LoggedInGuard, flash: Option<FlashMessage<'_>>) -> Template { + let f; + if let Some(ref flash) = flash { + f = Some(format!("{}: {}", flash.kind(), flash.message())); + } else { + f = None; } - response::content::RawHtml(asset("index.html").await.unwrap()) + Template::render("index", context![loggedin: true, username: lig.0, flash: f]) +} + +#[rocket::get("/", rank = 2)] +async fn route_index_loggedout(flash: Option<FlashMessage<'_>>) -> Template { + let f; + if let Some(ref flash) = flash { + f = Some(format!("{}: {}", flash.kind(), flash.message())); + } else { + f = None; + } + Template::render("index", context![loggedin: false, flash: f]) } #[rocket::launch] @@ -123,13 +163,17 @@ env_logger::init(); info!("{:?}", rocket::Config::figment()); - rocket::build().attach(ConfigDB::init()).mount( - "/", - rocket::routes![ - route_index, - route_logout, - route_login_form, - route_login_post - ], - ) + rocket::build() + .attach(ConfigDB::init()) + .attach(Template::fairing()) + .mount( + "/", + rocket::routes![ + route_index_loggedin, + route_index_loggedout, + route_logout, + route_login_form, + route_login_post + ], + ) }