Added basic auth system
This commit is contained in:
parent
a503726b42
commit
5ca02189f4
144
Cargo.lock
generated
144
Cargo.lock
generated
@ -362,14 +362,14 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.7.5"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
"base64 0.21.7",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
@ -392,8 +392,8 @@ dependencies = [
|
|||||||
"sha1",
|
"sha1",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper 1.0.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite 0.21.0",
|
"tokio-tungstenite 0.24.0",
|
||||||
"tower",
|
"tower 0.5.1",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -401,9 +401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.4.3"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -414,7 +414,30 @@ dependencies = [
|
|||||||
"mime",
|
"mime",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"sync_wrapper 0.1.2",
|
"sync_wrapper 1.0.1",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tower 0.5.1",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -422,11 +445,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
|
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.77",
|
"syn 2.0.77",
|
||||||
@ -961,6 +983,17 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -1241,7 +1274,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -2435,7 +2468,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@ -2722,7 +2755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4468,7 +4501,7 @@ dependencies = [
|
|||||||
"serde_qs",
|
"serde_qs",
|
||||||
"server_fn_macro_default",
|
"server_fn_macro_default",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@ -5199,18 +5232,6 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-tungstenite"
|
|
||||||
version = "0.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"log",
|
|
||||||
"tokio",
|
|
||||||
"tungstenite 0.21.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-tungstenite"
|
name = "tokio-tungstenite"
|
||||||
version = "0.23.1"
|
version = "0.23.1"
|
||||||
@ -5227,6 +5248,18 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tungstenite 0.24.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.12"
|
version = "0.7.12"
|
||||||
@ -5304,6 +5337,22 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"tokio",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -5437,25 +5486,6 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tungstenite"
|
|
||||||
version = "0.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"bytes",
|
|
||||||
"data-encoding",
|
|
||||||
"http 1.1.0",
|
|
||||||
"httparse",
|
|
||||||
"log",
|
|
||||||
"rand",
|
|
||||||
"sha1",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
"utf-8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.23.0"
|
version = "0.23.0"
|
||||||
@ -5477,6 +5507,24 @@ dependencies = [
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http 1.1.0",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@ -6022,6 +6070,7 @@ name = "wrbapp"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
"csv",
|
"csv",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-logger",
|
"dioxus-logger",
|
||||||
@ -6029,8 +6078,11 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"surrealdb",
|
"surrealdb",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -10,11 +10,15 @@ edition = "2021"
|
|||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
||||||
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
||||||
|
web-sys = { version = "0.3.70", features = ["Window", "Location"] }
|
||||||
|
|
||||||
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"], optional = true }
|
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"], optional = true }
|
||||||
axum = { version = "0.7", optional = true }
|
axum = { version = "0.7", optional = true }
|
||||||
|
axum-extra = { version = "0.9", features = ["cookie"], optional = true }
|
||||||
|
time = { version = "0.3", optional = true }
|
||||||
once_cell = { version = "1.19", optional = true }
|
once_cell = { version = "1.19", optional = true }
|
||||||
surrealdb = { version = "2.0", features = ["kv-rocksdb"], optional = true }
|
surrealdb = { version = "2.0", features = ["kv-rocksdb"], optional = true }
|
||||||
|
thiserror = { version = "1.0" }
|
||||||
|
|
||||||
csv = { version = "1.3", optional = true }
|
csv = { version = "1.3", optional = true }
|
||||||
|
|
||||||
@ -25,5 +29,5 @@ manganis = "0.2"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
server = [ "dioxus/axum", "tokio", "axum", "once_cell", "surrealdb", "csv" ]
|
server = [ "dioxus/axum", "tokio", "axum", "axum-extra", "time", "once_cell", "surrealdb", "csv" ]
|
||||||
web = ["dioxus/web"]
|
web = ["dioxus/web"]
|
||||||
|
@ -2404,24 +2404,24 @@ input.tab:checked + .tab-content,
|
|||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-10 {
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-5 {
|
.mb-5 {
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-6 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-10 {
|
.mt-10 {
|
||||||
margin-top: 2.5rem;
|
margin-top: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-2 {
|
.mt-16 {
|
||||||
margin-top: 0.5rem;
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-20 {
|
.mt-3 {
|
||||||
margin-top: 5rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-5 {
|
.mt-5 {
|
||||||
@ -2601,8 +2601,8 @@ input.tab:checked + .tab-content,
|
|||||||
padding-bottom: 2.5rem;
|
padding-bottom: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pb-3 {
|
.pt-2 {
|
||||||
padding-bottom: 0.75rem;
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
|
@ -3,7 +3,10 @@ pub mod icons;
|
|||||||
pub mod navbar;
|
pub mod navbar;
|
||||||
pub mod topbar;
|
pub mod topbar;
|
||||||
|
|
||||||
use crate::Route;
|
use crate::{
|
||||||
|
util::model::{session::Session, user::User},
|
||||||
|
Route,
|
||||||
|
};
|
||||||
use auth::Auth;
|
use auth::Auth;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
@ -34,7 +37,9 @@ pub fn Global() -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn get_user_from_cookie() -> Result<(), ServerFnError> {
|
async fn get_user_from_cookie() -> Result<User, ServerFnError> {
|
||||||
Err(ServerFnError::new("Not authenticated"))
|
let token = Session::get_token_from_cookie().await?;
|
||||||
// Ok(())
|
let user = Session::fetch_user_from_token(token).await?;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
use crate::components::layout::icons;
|
use crate::components::layout::icons;
|
||||||
use crate::util::model::member::Member;
|
use crate::util::model::member::Member;
|
||||||
|
use crate::util::model::session::Session;
|
||||||
|
use crate::util::model::user::User;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn Auth() -> Element {
|
pub fn Auth() -> Element {
|
||||||
@ -10,14 +12,14 @@ pub fn Auth() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col items-center",
|
class: "flex flex-col items-center",
|
||||||
h1 { class: "font-bold text-primary text-center text-2xl mt-20", "Waddinxveense Reddingsbrigade" },
|
h1 { class: "font-bold text-primary text-center text-2xl mt-16", "Waddinxveense Reddingsbrigade" },
|
||||||
div {
|
div {
|
||||||
class: "card bg-base-200 mt-20 w-full max-w-lg shadow-xl",
|
class: "card bg-base-200 mt-8 w-full max-w-lg shadow-xl",
|
||||||
div {
|
div {
|
||||||
class: "card-body",
|
class: "card-body",
|
||||||
div {
|
div {
|
||||||
role: "tablist",
|
role: "tablist",
|
||||||
class: "tabs tabs-boxed bg-base-300 mx-auto w-full mb-10",
|
class: "tabs tabs-boxed bg-base-300 mx-auto w-full mb-6",
|
||||||
button {
|
button {
|
||||||
onclick: move |_| register.set(false),
|
onclick: move |_| register.set(false),
|
||||||
role: "tab",
|
role: "tab",
|
||||||
@ -45,8 +47,17 @@ pub fn Auth() -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn Login() -> Element {
|
fn Login() -> Element {
|
||||||
|
let mut input_email = use_signal(|| "".to_string());
|
||||||
|
let mut input_password = use_signal(|| "".to_string());
|
||||||
|
|
||||||
|
let submit = move |_| async move {
|
||||||
|
if let Ok(_) = login(input_email(), input_password()).await {
|
||||||
|
let window = web_sys::window().expect("Could not find window");
|
||||||
|
window.location().reload().expect("Could not reload window");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
form {
|
|
||||||
label {
|
label {
|
||||||
class: "form-control w-full",
|
class: "form-control w-full",
|
||||||
div {
|
div {
|
||||||
@ -56,25 +67,29 @@ fn Login() -> Element {
|
|||||||
input {
|
input {
|
||||||
r#type: "email",
|
r#type: "email",
|
||||||
class: "input input-bordered w-full",
|
class: "input input-bordered w-full",
|
||||||
|
value: "{input_email}",
|
||||||
|
oninput: move |event| input_email.set(event.value())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
class: "form-control w-full mt-5",
|
class: "form-control w-full mt-3",
|
||||||
div {
|
div {
|
||||||
class: "label",
|
class: "label",
|
||||||
span { class: "label-text", "Password" },
|
span { class: "label-text", "Wachtwoord" },
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
r#type: "password",
|
r#type: "password",
|
||||||
class: "input input-bordered w-full",
|
class: "input input-bordered w-full",
|
||||||
|
value: "{input_password}",
|
||||||
|
oninput: move |event| input_password.set(event.value())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "card-actions mt-8",
|
class: "card-actions mt-5",
|
||||||
button {
|
button {
|
||||||
class: "btn btn-primary",
|
class: "btn btn-primary",
|
||||||
"Inloggen"
|
onclick: submit,
|
||||||
}
|
"Inloggen",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +97,20 @@ fn Login() -> Element {
|
|||||||
|
|
||||||
fn Register() -> Element {
|
fn Register() -> Element {
|
||||||
let mut members: Signal<HashMap<String, Member>> = use_signal(|| HashMap::new());
|
let mut members: Signal<HashMap<String, Member>> = use_signal(|| HashMap::new());
|
||||||
|
|
||||||
let mut input_registration_token = use_signal(|| "".to_string());
|
let mut input_registration_token = use_signal(|| "".to_string());
|
||||||
|
let mut input_email = use_signal(|| "".to_string());
|
||||||
|
let mut input_password = use_signal(|| "".to_string());
|
||||||
|
let mut input_password_repeat = use_signal(|| "".to_string());
|
||||||
|
|
||||||
|
let submit = move |_| async move {
|
||||||
|
let members_set: Vec<String> = members().into_keys().collect();
|
||||||
|
|
||||||
|
if let Ok(_) = register(input_email(), input_password(), members_set).await {
|
||||||
|
let window = web_sys::window().expect("Could not find window");
|
||||||
|
window.location().reload().expect("Could not reload window");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
label {
|
label {
|
||||||
@ -112,7 +140,7 @@ fn Register() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "flex gap-1 mt-2 flex-wrap pb-3",
|
class: "flex gap-1 pt-2 flex-wrap",
|
||||||
for (id, member) in members() {
|
for (id, member) in members() {
|
||||||
div {
|
div {
|
||||||
class: "badge badge-md badge-neutral h-8 gap-1 hover:cursor-pointer hover:line-through whitespace-nowrap",
|
class: "badge badge-md badge-neutral h-8 gap-1 hover:cursor-pointer hover:line-through whitespace-nowrap",
|
||||||
@ -124,17 +152,85 @@ fn Register() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
label {
|
||||||
|
class: "form-control w-full mt-3",
|
||||||
|
div {
|
||||||
|
class: "label",
|
||||||
|
span { class: "label-text", "Email" },
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
r#type: "email",
|
||||||
|
class: "input input-bordered w-full",
|
||||||
|
value: "{input_email}",
|
||||||
|
oninput: move |event| input_email.set(event.value())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
class: "form-control w-full mt-3",
|
||||||
|
div {
|
||||||
|
class: "label",
|
||||||
|
span { class: "label-text", "Wachtwoord" },
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
r#type: "password",
|
||||||
|
class: "input input-bordered w-full",
|
||||||
|
value: "{input_password}",
|
||||||
|
oninput: move |event| input_password.set(event.value())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
class: "form-control w-full mt-3",
|
||||||
|
div {
|
||||||
|
class: "label",
|
||||||
|
span { class: "label-text", "Herhaal wachtwoord" },
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
r#type: "password",
|
||||||
|
class: "input input-bordered w-full",
|
||||||
|
value: "{input_password_repeat}",
|
||||||
|
oninput: move |event| input_password_repeat.set(event.value())
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "card-actions mt-8",
|
class: "card-actions mt-5",
|
||||||
button {
|
button {
|
||||||
class: "btn btn-primary",
|
class: "btn btn-primary",
|
||||||
"Volgende"
|
onclick: submit,
|
||||||
|
"Registreren"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn login(email: String, password: String) -> Result<(), ServerFnError> {
|
||||||
|
// Verify username and password
|
||||||
|
let user_id = User::verify_credentials(email, password).await?;
|
||||||
|
|
||||||
|
// Create a new session
|
||||||
|
let session = Session::new(user_id).await?;
|
||||||
|
session.set_cookie()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn register(
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
member_ids: Vec<String>,
|
||||||
|
) -> Result<(), ServerFnError> {
|
||||||
|
// Creat a new user
|
||||||
|
let user = User::new(email, password, member_ids).await?;
|
||||||
|
|
||||||
|
// Generate a session and set the cookie
|
||||||
|
let session = Session::new(user.id).await?;
|
||||||
|
session.set_cookie()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn fetch_member(registration_token: String) -> Result<Member, ServerFnError> {
|
async fn fetch_member(registration_token: String) -> Result<Member, ServerFnError> {
|
||||||
let member = Member::fetch_from_registration_token(registration_token).await?;
|
let member = Member::fetch_from_registration_token(registration_token).await?;
|
||||||
|
23
src/err.rs
Normal file
23
src/err.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
SurrealDb(#[from] surrealdb::Error),
|
||||||
|
|
||||||
|
#[error("No document returned")]
|
||||||
|
NoDocument,
|
||||||
|
|
||||||
|
#[error("Invalid header value")]
|
||||||
|
InvalidHeaderValue(#[from] axum::http::header::InvalidHeaderValue),
|
||||||
|
|
||||||
|
#[error("Could not create timestamp")]
|
||||||
|
ComponentRange(#[from] time::error::ComponentRange),
|
||||||
|
|
||||||
|
#[error("No session cookie set")]
|
||||||
|
NoSessionCookie,
|
||||||
|
|
||||||
|
#[error("Could not get cookie jar")]
|
||||||
|
CookieJar(String),
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
|
mod err;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub use err::Error;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod member;
|
pub mod member;
|
||||||
|
pub mod session;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
@ -292,12 +292,12 @@ mod tests {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let rows = match csv_to_rows(data) {
|
let rows = match MembersMigration::csv_to_rows(data) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(err) => return Err(err.to_string()),
|
Err(err) => return Err(err.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let members = rows_to_members(rows);
|
let members = MembersMigration::rows_to_members(rows);
|
||||||
|
|
||||||
assert_eq!(expected, members);
|
assert_eq!(expected, members);
|
||||||
|
|
||||||
|
84
src/util/model/session.rs
Normal file
84
src/util/model/session.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::util::surrealdb::DB;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::user::User;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Session {
|
||||||
|
id: String,
|
||||||
|
user_id: String,
|
||||||
|
expires: i64,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl Session {
|
||||||
|
pub async fn new(user_id: String) -> Result<Self, crate::Error> {
|
||||||
|
let mut res = DB
|
||||||
|
.query("CREATE ONLY session SET user = type::thing('user', $user_id) RETURN record::id(id) as id, record::id(user) as user_id, time::unix(expires) as expires, token;")
|
||||||
|
.bind(("user_id", user_id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let session: Option<Self> = res.take(0)?;
|
||||||
|
|
||||||
|
match session {
|
||||||
|
Some(s) => Ok(s),
|
||||||
|
None => Err(crate::Error::NoDocument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cookie(&self) -> Result<(), crate::Error> {
|
||||||
|
use axum::http::{header, HeaderValue};
|
||||||
|
use axum_extra::extract::cookie::{Cookie, Expiration, SameSite};
|
||||||
|
use dioxus::prelude::server_context;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
let timestamp = OffsetDateTime::from_unix_timestamp(self.expires)?;
|
||||||
|
let expiration = Expiration::from(timestamp);
|
||||||
|
|
||||||
|
let mut cookie = Cookie::build(("session_token", &self.token))
|
||||||
|
.expires(expiration)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
cookie.set_same_site(SameSite::Strict);
|
||||||
|
|
||||||
|
server_context()
|
||||||
|
.response_parts_mut()
|
||||||
|
.unwrap()
|
||||||
|
.headers
|
||||||
|
.insert(
|
||||||
|
header::SET_COOKIE,
|
||||||
|
HeaderValue::from_str(&cookie.to_string())?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_user_from_token(session_token: String) -> Result<User, crate::Error> {
|
||||||
|
let mut res = DB
|
||||||
|
.query("SELECT record::id(user) as id, user.email as email FROM session WHERE token = $session_token")
|
||||||
|
.bind(("session_token", session_token))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user: Option<User> = res.take(0)?;
|
||||||
|
|
||||||
|
match user {
|
||||||
|
Some(u) => Ok(u),
|
||||||
|
None => Err(crate::Error::NoDocument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_token_from_cookie() -> Result<String, crate::Error> {
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
|
||||||
|
let jar: CookieJar = extract().await.unwrap_or_default();
|
||||||
|
|
||||||
|
match jar.get("session_token") {
|
||||||
|
Some(s) => Ok(s.value().to_string()),
|
||||||
|
None => Err(crate::Error::NoSessionCookie),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,63 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::util::surrealdb::DB;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use surrealdb::sql::statements::{BeginStatement, CommitStatement};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: String,
|
||||||
|
email: String,
|
||||||
|
password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl User {
|
||||||
|
pub async fn new(
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
member_ids: Vec<String>,
|
||||||
|
) -> Result<Self, crate::Error> {
|
||||||
|
// Create user record
|
||||||
|
let mut transaction = DB.query(BeginStatement::default())
|
||||||
|
.query("let $user = CREATE ONLY user SET email = $email, password = crypto::argon2::generate($password) RETURN record::id(id) as id, email;")
|
||||||
|
.bind(("email", email))
|
||||||
|
.bind(("password", password));
|
||||||
|
|
||||||
|
// Link user with members
|
||||||
|
for member_id in member_ids {
|
||||||
|
transaction = transaction.query(format!("RELATE ONLY (type::thing('user', $user.id))->user_to_member->(type::thing('member', $member_id_{}));", member_id))
|
||||||
|
.bind((format!("member_id_{}", member_id), member_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = transaction
|
||||||
|
.query("RETURN $user")
|
||||||
|
.query(CommitStatement::default())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user: Option<Self> = res.take(0)?;
|
||||||
|
|
||||||
|
match user {
|
||||||
|
Some(u) => Ok(u),
|
||||||
|
None => Err(crate::Error::NoDocument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify_credentials(
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<String, crate::Error> {
|
||||||
|
let mut res = DB
|
||||||
|
.query("SELECT VALUE record::id(id) as id FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
|
||||||
|
.bind(("email", email)).bind(("password", password))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let id: Option<String> = res.take(0)?;
|
||||||
|
|
||||||
|
match id {
|
||||||
|
Some(i) => Ok(i),
|
||||||
|
None => Err(crate::Error::NoDocument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,5 +15,43 @@ pub async fn initialize() -> surrealdb::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_queries() -> surrealdb::Result<()> {
|
async fn apply_queries() -> surrealdb::Result<()> {
|
||||||
|
// Define user table
|
||||||
|
DB.query(
|
||||||
|
"
|
||||||
|
DEFINE TABLE OVERWRITE user SCHEMAFULL;
|
||||||
|
|
||||||
|
DEFINE FIELD email ON TABLE user TYPE string
|
||||||
|
VALUE string::lowercase($value)
|
||||||
|
ASSERT string::is::email($value);
|
||||||
|
DEFINE FIELD password ON TABLE user TYPE string;
|
||||||
|
|
||||||
|
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
DB.query(
|
||||||
|
"
|
||||||
|
DEFINE TABLE OVERWRITE user_to_member SCHEMAFULL TYPE RELATION FROM user TO member ENFORCED;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Define session table
|
||||||
|
DB.query(
|
||||||
|
"
|
||||||
|
DEFINE TABLE OVERWRITE session SCHEMAFULL;
|
||||||
|
|
||||||
|
DEFINE FIELD user ON TABLE session TYPE record;
|
||||||
|
DEFINE FIELD token ON TABLE session TYPE string
|
||||||
|
VALUE rand::string(32);
|
||||||
|
DEFINE FIELD expires ON TABLE session TYPE datetime
|
||||||
|
VALUE time::now() + 1w;
|
||||||
|
|
||||||
|
DEFINE INDEX sessionTokenIndex ON TABLE session COLUMNS token UNIQUE;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user