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
+            ],
+        )
 }