wip frontend

This commit is contained in:
Hannaeko 2022-04-29 04:29:10 +02:00
parent 806d57379a
commit c04090adaf
21 changed files with 747 additions and 58 deletions

307
Cargo.lock generated
View file

@ -8,7 +8,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
@ -20,7 +20,7 @@ dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug",
"opaque-debug 0.3.0",
]
[[package]]
@ -37,6 +37,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -145,7 +154,19 @@ version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
dependencies = [
"digest",
"digest 0.10.3",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
@ -154,9 +175,33 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
]
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -201,7 +246,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
@ -270,13 +315,23 @@ dependencies = [
"libc",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"generic-array 0.14.5",
"typenum",
]
@ -374,13 +429,22 @@ dependencies = [
"migrations_macros",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer",
"block-buffer 0.10.2",
"crypto-common",
"subtle",
]
@ -418,6 +482,12 @@ dependencies = [
"syn",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fastrand"
version = "1.7.0"
@ -550,6 +620,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.5"
@ -577,7 +656,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug",
"opaque-debug 0.3.0",
"polyval",
]
@ -587,6 +666,30 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "globset"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags",
"ignore",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -623,7 +726,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
"digest 0.10.3",
]
[[package]]
@ -700,6 +803,24 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "ignore"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
dependencies = [
"crossbeam-utils",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "indexmap"
version = "1.8.1"
@ -794,6 +915,12 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matchers"
version = "0.1.0"
@ -913,6 +1040,7 @@ dependencies = [
"rocket_sync_db_pools",
"serde",
"serde_json",
"tera",
"time 0.3.9",
"tokio",
"trust-dns-client",
@ -973,6 +1101,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -1100,6 +1234,49 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@ -1126,7 +1303,7 @@ checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"opaque-debug 0.3.0",
"universal-hash",
]
@ -1277,6 +1454,8 @@ version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -1416,6 +1595,15 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.5"
@ -1468,6 +1656,18 @@ dependencies = [
"serde",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.10.2"
@ -1476,7 +1676,7 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"digest 0.10.3",
]
[[package]]
@ -1580,6 +1780,22 @@ dependencies = [
"winapi",
]
[[package]]
name = "tera"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d"
dependencies = [
"globwalk",
"lazy_static",
"pest",
"pest_derive",
"regex",
"serde",
"serde_json",
"unic-segment",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -1880,6 +2096,12 @@ dependencies = [
"serde",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uncased"
version = "0.9.6"
@ -1890,6 +2112,56 @@ dependencies = [
"version_check",
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
dependencies = [
"unic-ucd-segment",
]
[[package]]
name = "unic-ucd-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -1917,7 +2189,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array",
"generic-array 0.14.5",
"subtle",
]
@ -1961,6 +2233,17 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"

View file

@ -25,6 +25,7 @@ figment = { version = "0.10.6", features = ["toml", "env"] }
clap = {version = "3", features = ["derive", "cargo"]}
argon2 = {version = "0.4", default-features = false, features = ["alloc", "password-hash"] }
rand = "0.8"
tera = {version = "1", default-features = false}
# From trust-dns-client
futures-util = { version = "0.3.5", default-features = false, features = ["std"] }
# From rocket / cookie-rs

22
public/scripts/api.js Normal file
View file

@ -0,0 +1,22 @@
const baseUrl = '/api/v1';
function apiGet(url) {
return fetch(`${baseUrl}/${url}`)
.then(res => {
if (!res.ok) {
// do something here
throw new Error('Not ok');
}
return res.json();
})
}
function getRecords(zone) {
return apiGet(`zones/${zone}/records`)
}
export {
getRecords,
};

100
public/scripts/records.js Normal file
View file

@ -0,0 +1,100 @@
import { html, Component, render, createContext, useState, useEffect } from 'https://unpkg.com/htm/preact/standalone.mjs';
import { getRecords } from './api.js';
const rdataInputProperties = {
Address: {label: 'adresse', type: 'text'},
Serial: {label: 'serial', type: 'number'},
Minimum: {label: 'minimum', type: 'number'},
Retry: {label: 'nouvelle tentative', type: 'number'},
Refresh: {label: 'actualisation', type: 'number'},
MaintainerName: {label: 'contact', type: 'text'},
MasterServerName: {label: 'serveur primaire', type: 'text'},
Expire: {label: 'expiration', type: 'number'},
Target: {label: 'cible', type: 'text'},
}
const Editable = createContext(false);
function RDataInput({ name, value = '', index = 0 }) {
const {label, type} = rdataInputProperties[name] || {label: name, type: 'text'};
return html`
<${Editable.Consumer}>
${
(editable) => {
if (editable) {
return html`
<div>
<label for=record_${index}_${name}>${label}:</label>
<input id=record_${index}_${name} type=${type} value=${value} />
</div>
`;
} else {
return html`
<div>
<span class=label>${label}:</span>
<span class=value>${value}</span>
</div>
`
}
}
}
<//>
`;
}
function RData({ rdata, index }) {
const { Address: address } = rdata;
return Object.entries(rdata).map(([name, value]) => html`<${RDataInput} name=${name} value=${value} index=${index} />`);
}
function Record({name, ttl, type, rdata, index = 0}) {
return html`
<tr>
<td class=domain>${name}</div>
<td class=type>${type}</div>
<td class=ttl>${ttl}</div>
<td class=rdata><${RData} rdata=${rdata} index=${index}/></div>
</tr>
`;
}
function RecordList({ zone }) {
const [records, setRecords] = useState([]);
const [editable, setEditable] = useState(false);
const toggleEdit = () => setEditable(!editable);
useEffect(() => {
getRecords(zone)
.then((res) => setRecords(res));
}, [])
return html`
<${Editable.Provider} value=${editable}>
<button onclick=${toggleEdit}>${ editable ? 'Save' : 'Edit'}</button>
<table class=record-list>
<thead>
<tr>
<th>Nom</th>
<th>Type</th>
<th>TTL</th>
<th>Données</th>
</tr>
</thead>
<tbody>
${records.map(
({Name, Class, TTL, Type, ...rdata}, index) => {
return html`<${Record} name=${Name} ttl=${TTL} type=${Type} rdata=${rdata} index=${index}/>`
}
)}
</tbody>
</ul>
<//>
`;
}
export { RecordList };

41
public/styles/main.css Normal file
View file

@ -0,0 +1,41 @@
body {
color: #2e2033;
}
.record-list {
border-collapse: collapse;
}
.record-list .rdata {
display: flex;
flex-wrap: wrap;
}
.record-list th, .record-list td {
font-weight: normal;
text-align: left;
vertical-align: top;
padding: 0.25rem;
}
.record-list thead {
background: #ccb9ff;
color: #39004d;
}
.record-list tbody tr:nth-child(even) td {
background: #ccb9ff3d;
}
.record-list tbody tr .rdata span.label,
.record-list tbody tr .rdata label {
display: inline-block;
padding: 0.1em 0.5em;
background: #cecece;
font-size: 0.7rem;
border-radius: 0.5em;
margin-right: 0.1rem;
}
.record-list tbody tr .rdata > div {
margin: 0.1rem 0.5rem 0.1rem 0;
}

View file

@ -4,13 +4,15 @@ use std::process::exit;
use clap::{Parser, Subcommand};
use rocket::{Rocket, Build};
use rocket::fairing::AdHoc;
use rocket::fs::FileServer;
use figment::Figment;
use crate::config::Config;
use crate::routes::users::*;
use crate::routes::zones::*;
use crate::routes::api;
use crate::routes::ui;
use crate::{DbConn, get_db_conn};
use crate::cli::NomiloCommand;
use crate::template::TemplateState;
#[derive(Subcommand)]
pub enum ServerCommand {
@ -78,21 +80,31 @@ impl NomiloCommand for RunServerCommand {
if let Some(port) = self.port {
figment = figment.merge(("port", port));
}
let template_state = TemplateState::new(&app_config.web_app.templates_directory);
let static_files = FileServer::from(&app_config.web_app.static_files);
let _res = rocket::custom(figment)
.manage(app_config)
.manage(template_state)
.attach(DbConn::fairing())
.attach(AdHoc::on_ignite("Database migration", run_migrations_fairing))
.mount("/api/v1", routes![
get_zone_records,
create_zone_records,
update_zone_records,
delete_zone_records,
get_zones,
create_zone,
add_member_to_zone,
create_auth_token,
create_user,
api::get_zone_records,
api::create_zone_records,
api::update_zone_records,
api::delete_zone_records,
api::get_zones,
api::create_zone,
api::add_member_to_zone,
api::create_auth_token,
api::create_user,
])
.mount("/", routes![
ui::get_login_page,
ui::post_login_page,
ui::get_zone_records_page,
])
.mount("/", static_files)
.launch().await;
});
}

View file

@ -1,5 +1,6 @@
use std::net::SocketAddr;
use std::time::Duration;
use std::path::PathBuf;
use serde::{Deserialize, Deserializer};
@ -34,6 +35,8 @@ pub struct TsigConfig {
pub struct WebAppConfig {
#[serde(deserialize_with = "from_std_duration")]
pub token_duration: Duration,
pub templates_directory: PathBuf,
pub static_files: PathBuf,
}

42
src/controllers.rs Normal file
View file

@ -0,0 +1,42 @@
use time;
use rocket::http::{Cookie, SameSite, CookieJar};
use rocket::State;
use crate::config::Config;
use crate::DbConn;
use crate::models;
pub async fn do_login(
conn: DbConn,
config: &State<Config>,
auth_request: models::AuthTokenRequest,
cookies: &CookieJar<'_>
) -> Result<models::Session, models::UserError> {
let session_duration = config.web_app.token_duration;
let session = conn.run(move |c| {
let user_info = models::LocalUser::get_user_by_creds(
c,
&auth_request.username,
&auth_request.password
)?;
models::Session::new(c, &user_info, session_duration)
}).await?;
// Conversion between different date / time libraries, very cursed, I don't like that
// About unwrap: I guess too bad if session time is over year 9999 (current max time if time-rs)
let expires = time::OffsetDateTime::from_unix_timestamp(session.expires_at.timestamp()).unwrap();
let session_cookie = Cookie::build(models::session::COOKIE_NAME, session.session_id.clone())
.same_site(SameSite::Strict)
.secure(true)
.http_only(true)
.expires(expires)
.finish();
cookies.add(session_cookie);
Ok(session)
}

View file

@ -5,12 +5,14 @@
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations;
mod models;
mod config;
mod schema;
mod routes;
mod dns;
mod cli;
mod config;
mod dns;
mod models;
mod schema;
mod template;
mod controllers;
use std::process::exit;

View file

@ -20,7 +20,7 @@ const AUTH_HEADER: &str = "Authorization";
pub const COOKIE_NAME: &str = "session_id";
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, FromForm)]
pub struct AuthTokenRequest {
pub username: String,
pub password: String,

5
src/routes/api/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod users;
pub mod zones;
pub use users::*;
pub use zones::*;

View file

@ -1,4 +1,4 @@
use rocket::http::{Cookie, SameSite, CookieJar};
use rocket::http::CookieJar;
use rocket::serde::json::Json;
use rocket::State;
use rocket::http::Status;
@ -6,7 +6,8 @@ use rocket::http::Status;
use crate::config::Config;
use crate::DbConn;
use crate::models;
use time;
use crate::controllers::do_login;
#[post("/users/me/token", data = "<auth_request>")]
@ -17,32 +18,12 @@ pub async fn create_auth_token(
cookies: &CookieJar<'_>
) -> Result<Json<models::Session>, models::ErrorResponse> {
let session_duration = config.web_app.token_duration;
let session = conn.run(move |c| {
let user_info = models::LocalUser::get_user_by_creds(
c,
&auth_request.username,
&auth_request.password
)?;
models::Session::new(c, &user_info, session_duration)
}).await?;
// Conversion between different date / time libraries, very cursed, I don't like that
// About unwrap: I guess too bad if session time is over year 9999 (current max time if time-rs)
let expires = time::OffsetDateTime::from_unix_timestamp(session.expires_at.timestamp()).unwrap();
let session_cookie = Cookie::build(models::session::COOKIE_NAME, session.session_id.clone())
.same_site(SameSite::Strict)
.secure(true)
.http_only(true)
.expires(expires)
.finish();
cookies.add(session_cookie);
Ok(Json(session))
do_login(
conn,
config,
auth_request.into_inner(),
cookies
).await.map(|s| Json(s)).map_err(models::ErrorResponse::from)
}
#[post("/users", data = "<user_request>")]

View file

@ -1,2 +1,2 @@
pub mod users;
pub mod zones;
pub mod ui;
pub mod api;

48
src/routes/ui/auth.rs Normal file
View file

@ -0,0 +1,48 @@
use serde::Serialize;
use rocket::{State, Either};
use rocket::http::{Status, CookieJar};
use rocket::form::Form;
use rocket::response::Redirect;
use crate::template::Template;
use crate::config::Config;
use crate::models;
use crate::DbConn;
use crate::controllers::do_login;
#[derive(Serialize)]
pub struct LoginPage {
error: Option<String>
}
#[get("/login")]
pub async fn get_login_page() -> Template<'static, LoginPage> {
Template::new("login.html", LoginPage { error: None })
}
#[post("/login", data = "<auth_request>")]
pub async fn post_login_page(
conn: DbConn,
config: &State<Config>,
auth_request: Form<models::AuthTokenRequest>,
cookies: &CookieJar<'_>
) -> Result<Redirect, Either<Template<'static, LoginPage>, Status>> {
let res = do_login(
conn,
config,
auth_request.into_inner(),
cookies
).await;
match res {
Ok(_) => Ok(Redirect::to(uri!("/zones"))),
Err(models::UserError::BadCreds) => Err(Either::Left(Template::new(
"login.html",
LoginPage {
error: Some("Identifants incorrects".to_string())
}
))),
Err(e) => Err(Either::Right(models::ErrorResponse::from(e).status))
}
}

5
src/routes/ui/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod auth;
pub mod zones;
pub use auth::*;
pub use zones::*;

30
src/routes/ui/zones.rs Normal file
View file

@ -0,0 +1,30 @@
use serde::Serialize;
use rocket::http::{Status};
use crate::template::Template;
use crate::models;
use crate::DbConn;
#[derive(Serialize)]
pub struct RecordsPage {
zone: String
}
#[get("/zone/<zone>/records")]
pub async fn get_zone_records_page(user_info: models::UserInfo, zone: models::AbsoluteName, conn: DbConn) -> Result<Template<'static, RecordsPage>, Status> {
let zone_name = zone.to_utf8();
conn.run(move |c| {
if user_info.is_admin() {
models::Zone::get_by_name(c, &zone_name)
} else {
user_info.get_zone(c, &zone_name)
}
}).await.map_err(|e| models::ErrorResponse::from(e).status)?;
Ok(Template::new("zone/records.html", RecordsPage {
zone: zone.to_utf8(),
}))
}

65
src/template.rs Normal file
View file

@ -0,0 +1,65 @@
use std::path::Path;
use std::process::exit;
use serde::Serialize;
use rocket::request::Request;
use rocket::response::{self, Responder};
use rocket::http::{Status, ContentType};
use tera::{Tera, Context};
pub struct TemplateState {
tera: Tera,
}
impl TemplateState {
pub fn new(template_directory: &Path) -> Self {
let template_glob = template_directory.join("**").join("*");
match Tera::new(template_glob.to_str().expect("valid glob path string")) {
Ok(tera) => TemplateState { tera },
Err(e) => {
println!("Loading templates failed: {}", e);
exit(1)
}
}
}
}
pub struct Template<'t, S: Serialize> {
pub name: &'t str,
pub context: S,
}
impl<'r, S: Serialize> Template<'r, S> {
pub fn new(name: &'r str, context: S) -> Self {
Template {
name,
context
}
}
fn render(self, tera: &Tera) -> Result<(ContentType, String), Status> {
let context = Context::from_serialize(self.context).map_err(|e| {
error!("Failed to serialize context: {}", e);
Status::InternalServerError
})?;
let content = tera.render(self.name, &context).map_err(|e| {
error!("Failed to render template `{}`: {}", self.name, e);
Status::InternalServerError
})?;
Ok((ContentType::HTML, content))
}
}
impl<'r, 't, S: Serialize> Responder<'r, 'static> for Template<'t, S> {
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
let template_state = request.rocket().state::<TemplateState>().ok_or_else(|| {
Status::InternalServerError
})?;
self.render(&template_state.tera).respond_to(request)
}
}

13
templates/base.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock title %}Nomilo</title>
<link rel="stylesheet" type="text/css" href="/styles/main.css">
</head>
<body>
{% block content %}{% endblock content %}
{% block scripts %}{% endblock scripts %}
</body>
</html>

18
templates/login.html Normal file
View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Login ⋅ {% endblock title %}
{% block content %}
<main>
{% if error %}
<p>
{{ error }}
</p>
{% endif %}
<form method="POST" action="/login">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="Se connecter">
</form>
</main>
{% endblock content %}

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}{{ zone }} ⋅ Records ⋅ {% endblock title %}
{% block content %}
<main></main>
{% endblock content %}
{% block scripts %}
<script type="module">
const zoneName = "{{ zone }}";
import { RecordList } from '/scripts/records.js';
import { html, render } from 'https://unpkg.com/htm/preact/standalone.mjs';
render(html`<${RecordList} zone=${zoneName} />`, document.querySelector('main'));
</script>
{% endblock scripts %}