From 00eb79c271e228d83466b08fbf22ebc95b7632ac Mon Sep 17 00:00:00 2001
From: Pierre Jarriges <pierre.jarriges@tutanota.com>
Date: Tue, 4 Oct 2022 12:53:57 +0200
Subject: [PATCH] admin static views

---
 admin_app/auth/index.html        | 15 ++++++++++++
 admin_app/login/index.html       | 26 ++++++++++++++++++++
 default_static/admin.js          |  2 +-
 default_views/401.html           | 18 ++++++++++++++
 default_views/404.html           | 15 ++++++++++++
 src/main.rs                      | 16 +++++++++---
 src/middleware/authentication.rs | 14 +++++++++--
 src/service/admin.rs             | 41 +------------------------------
 src/service/default.rs           | 15 ++++++++++++
 src/service/files.rs             |  1 +
 src/service/mod.rs               |  2 ++
 src/static_files/static_files.rs | 42 +++++++++++++++++++++++++++++++-
 12 files changed, 160 insertions(+), 47 deletions(-)
 create mode 100644 admin_app/auth/index.html
 create mode 100644 admin_app/login/index.html
 create mode 100644 default_views/401.html
 create mode 100644 default_views/404.html
 create mode 100644 src/service/default.rs

diff --git a/admin_app/auth/index.html b/admin_app/auth/index.html
new file mode 100644
index 0000000..b40b6ea
--- /dev/null
+++ b/admin_app/auth/index.html
@@ -0,0 +1,15 @@
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Krustacea | Admin dashboard</title>
+</head>
+
+<body>
+    ADMIN Dahsboard
+</body>
+<script src='/assets/default/admin.js'></script>
+
+</html>
\ No newline at end of file
diff --git a/admin_app/login/index.html b/admin_app/login/index.html
new file mode 100644
index 0000000..8ebf404
--- /dev/null
+++ b/admin_app/login/index.html
@@ -0,0 +1,26 @@
+<html lang='en' prefix='og: https://ogp.me/ns#'>
+
+<head>
+    <meta charset='UTF-8'>
+    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
+    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
+    <title>Krustacea - Admin Login</title>
+    <link rel='stylesheet' href='/assets/default/admin.css'>
+</head>
+
+<body>
+    <form id='admin-login-form'>
+        <div>
+            <label for='username'>Admin Id</label>
+            <input type='text' name='username' />
+        </div>
+        <div>
+            <label for='password'>Password</label>
+            <input type='password' name='password' />
+        </div>
+        <input type='submit' />
+    </form>
+</body>
+<script src='/assets/default/admin.js'></script>
+
+</html>
\ No newline at end of file
diff --git a/default_static/admin.js b/default_static/admin.js
index 065f3cd..0b48213 100644
--- a/default_static/admin.js
+++ b/default_static/admin.js
@@ -3,7 +3,7 @@ document.getElementById('admin-login-form').onsubmit = function (e) {
     fetch('/admin/login', { method: 'POST', body: new URLSearchParams(new FormData(e.target)) }).then(res => {
         if (res.status >= 200 && res.status < 400) {
             console.log(res)
-            window.location = '/admin/auth/workspace';
+            window.location = '/admin_app/auth/';
         }
 
     }).catch(err => console.log(err))
diff --git a/default_views/401.html b/default_views/401.html
new file mode 100644
index 0000000..529b461
--- /dev/null
+++ b/default_views/401.html
@@ -0,0 +1,18 @@
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Krustacea | Unauthorized</title>
+</head>
+
+<body>
+    <h1>Error 401 - Unauthorized</h1>
+    <p>
+        Please <a href='/admin_app/login/'>login</a> before accessing this page.
+    </p>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/default_views/404.html b/default_views/404.html
new file mode 100644
index 0000000..25aad06
--- /dev/null
+++ b/default_views/404.html
@@ -0,0 +1,15 @@
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Krustacea | Not Found</title>
+</head>
+
+<body>
+    <h1>Page Not Found</h1>
+    <a href="/">Back to home</a>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 66b5818..57fe537 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,6 +43,7 @@ async fn main() -> std::io::Result<()> {
 
     let srv_conf = tls_config(&app_state.config);
     let dir = website.static_files_manager.dir.clone();
+    let auth_service = std::sync::Arc::new(AuthService {});
 
     HttpServer::new(move || {
         App::new()
@@ -51,14 +52,22 @@ async fn main() -> std::io::Result<()> {
             .wrap(RedirectHttps::default().to_port(port_tls))
             .app_data(web::Data::new(RwLock::new(app_state.clone())))
             .app_data(web::Data::new(RwLock::new(website.clone())))
+            .service(
+                web::scope("/admin_app")
+                    .service(
+                        web::scope("/auth").wrap(auth_service.clone()).service(
+                            Files::new("/", &dir.join("admin_app").join("auth"))
+                                .index_file("index.html"),
+                        ),
+                    )
+                    .service(Files::new("/", &dir.join("admin_app")).index_file("index.html")),
+            )
             .service(
                 web::scope("/admin")
-                    .service(service::admin_login)
                     .service(service::admin_authenticate)
                     .service(
                         web::scope("/auth")
-                            .wrap(AuthService {})
-                            .service(service::admin_workspace)
+                            .wrap(auth_service.clone())
                             .service(service::add_page)
                             .service(service::get_page_data)
                             .service(service::update_page)
@@ -72,6 +81,7 @@ async fn main() -> std::io::Result<()> {
             )
             .service(service::files::favicon)
             .service(Files::new("/", &dir).index_file("index.html"))
+            .default_service(web::to(service::not_found))
     })
     .bind(format!("{}:{}", host, port))?
     .bind_rustls(format!("{}:{}", host, port_tls), srv_conf)?
diff --git a/src/middleware/authentication.rs b/src/middleware/authentication.rs
index faf9588..66473fd 100644
--- a/src/middleware/authentication.rs
+++ b/src/middleware/authentication.rs
@@ -1,4 +1,4 @@
-use crate::{app::AdminAuthToken, AppState};
+use crate::{app::AdminAuthToken, website::WebSite, AppState};
 use actix_web::{
     body::{EitherBody, MessageBody},
     dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
@@ -66,9 +66,19 @@ where
         Box::pin(async move {
             let mut req = req;
             if let false = authenticate(&mut req, &token).await {
+                let f401 = req
+                    .app_data::<web::Data<RwLock<WebSite>>>()
+                    .expect("Failed to extract WebSite from ServiceRequest")
+                    .read()
+                    .expect("Failed to read WebSite")
+                    .static_files_manager
+                    .dir
+                    .join("default_views")
+                    .join("401.html");
+
                 return Ok(req.into_response(
                     HttpResponse::Unauthorized()
-                        .body("<html><body>Error 401 - Unauthorized - Please go to <a href='/admin/login'>login page</a>.</body></html>") // TODO a proper 401 view ?
+                        .body(std::fs::read_to_string(f401)?)
                         .map_into_right_body(),
                 ));
             }
diff --git a/src/service/admin.rs b/src/service/admin.rs
index 76fe7a2..3252782 100644
--- a/src/service/admin.rs
+++ b/src/service/admin.rs
@@ -1,17 +1,11 @@
 use crate::AppState;
 use actix_web::{
-    get, post,
+    post,
     web::{Data, Form},
     HttpRequest, HttpResponse,
 };
 use std::sync::RwLock;
 
-#[get("/workspace")]
-async fn admin_workspace() -> HttpResponse {
-    // TODO return admin static web view with js application
-    actix_web::HttpResponse::Ok().body("Welcome Admin")
-}
-
 #[derive(serde::Deserialize)]
 struct Credentials {
     username: String,
@@ -55,36 +49,3 @@ async fn admin_authenticate(
         }
     }
 }
-
-#[get("/login")]
-pub async fn admin_login() -> HttpResponse {
-    // TODO create a module with built-in admin static views
-    HttpResponse::Ok().body(
-        "
-<html lang='en' prefix='og: https://ogp.me/ns#'>
-<head>
-    <meta charset='UTF-8'>
-    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
-    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
-    <title>Krutacea - Admin Login</title>
-    <link rel='stylesheet' href='/assets/default/admin.css'>
-</head>
-
-<body>
-    <form id='admin-login-form'>
-    <div>
-        <label for='username'>Admin Id</label>
-        <input type='text' name='username'/>
-    </div>
-    <div>
-        <label for='password'>Password</label>
-        <input type='password' name='password' /> 
-    </div>
-        <input type='submit' />
-    </form>
-</body>
-<script src='/assets/default/admin.js'></script>
-</html>
-",
-    )
-}
diff --git a/src/service/default.rs b/src/service/default.rs
new file mode 100644
index 0000000..ccab01c
--- /dev/null
+++ b/src/service/default.rs
@@ -0,0 +1,15 @@
+use crate::website::WebSite;
+use actix_web::{web::Data, HttpResponse, Responder};
+use std::sync::RwLock;
+
+pub async fn not_found(website: Data<RwLock<WebSite>>) -> impl Responder {
+    let f404 = website
+        .read()
+        .expect("Failed to read WebSite")
+        .static_files_manager
+        .dir
+        .join("default_views")
+        .join("404.html");
+
+    HttpResponse::NotFound().body(std::fs::read_to_string(f404).unwrap())
+}
diff --git a/src/service/files.rs b/src/service/files.rs
index e55c419..a46ed06 100644
--- a/src/service/files.rs
+++ b/src/service/files.rs
@@ -22,6 +22,7 @@ pub async fn favicon(website: web::Data<RwLock<WebSite>>) -> impl Responder {
             .unwrap()
             .static_files_manager
             .dir
+            .join("assets")
             .join("default")
             .join("favicon.ico"),
     )
diff --git a/src/service/mod.rs b/src/service/mod.rs
index 18a7bf1..57f77d2 100644
--- a/src/service/mod.rs
+++ b/src/service/mod.rs
@@ -1,5 +1,7 @@
 mod admin;
+mod default;
 pub mod files;
 mod website;
 pub use admin::*;
+pub use default::*;
 pub use website::*;
diff --git a/src/static_files/static_files.rs b/src/static_files/static_files.rs
index ae8c6e6..6f3d3df 100644
--- a/src/static_files/static_files.rs
+++ b/src/static_files/static_files.rs
@@ -59,7 +59,21 @@ impl StaticFilesManager {
 
         if let Err(err) = Self::copy_default_files(&dir) {
             return Err(format!(
-                "Error copying StaticFilesManager default assets directory- {}",
+                "Error copying StaticFilesManager default assets directory - {}",
+                err
+            ));
+        }
+
+        if let Err(err) = Self::copy_admin_web_app(&dir) {
+            return Err(format!(
+                "Error copying StaticFilesManager admin_web_app directory - {}",
+                err
+            ));
+        }
+
+        if let Err(err) = Self::copy_default_views(&dir) {
+            return Err(format!(
+                "Error copying StaticFilesManager defult_views directory - {}",
                 err
             ));
         }
@@ -97,6 +111,32 @@ impl StaticFilesManager {
         }
     }
 
+    fn copy_admin_web_app(static_dir: &PathBuf) -> Result<(), String> {
+        let local_admin_app_dir = std::env::current_dir().unwrap().join("admin_app");
+        let dest = static_dir.join("admin_app");
+        let mut cpy_options = fs_extra::dir::CopyOptions::new();
+        cpy_options.content_only = true;
+        cpy_options.overwrite = true;
+
+        match fs_extra::dir::copy(local_admin_app_dir, dest, &cpy_options) {
+            Err(err) => Err(format!("{}", err)),
+            Ok(_) => Ok(()),
+        }
+    }
+
+    fn copy_default_views(static_dir: &PathBuf) -> Result<(), String> {
+        let local_admin_app_dir = std::env::current_dir().unwrap().join("default_views");
+        let dest = static_dir.join("default_views");
+        let mut cpy_options = fs_extra::dir::CopyOptions::new();
+        cpy_options.content_only = true;
+        cpy_options.overwrite = true;
+
+        match fs_extra::dir::copy(local_admin_app_dir, dest, &cpy_options) {
+            Err(err) => Err(format!("{}", err)),
+            Ok(_) => Ok(()),
+        }
+    }
+
     fn rec_read_dir(&mut self, root: &Path, strip_from: &Path) {
         for entry in root.read_dir().unwrap() {
             if let Ok(entry) = entry {
-- 
GitLab