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]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
@ -392,8 +392,8 @@ dependencies = [
|
||||
"sha1",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.21.0",
|
||||
"tower",
|
||||
"tokio-tungstenite 0.24.0",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@ -401,9 +401,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@ -414,7 +414,30 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"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-service",
|
||||
"tracing",
|
||||
@ -422,11 +445,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
|
||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
@ -961,6 +983,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@ -1241,7 +1274,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tracing",
|
||||
@ -2435,7 +2468,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
@ -2722,7 +2755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4468,7 +4501,7 @@ dependencies = [
|
||||
"serde_qs",
|
||||
"server_fn_macro_default",
|
||||
"thiserror",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-layer",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@ -5199,18 +5232,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.23.1"
|
||||
@ -5227,6 +5248,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.12"
|
||||
@ -5304,6 +5337,22 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
@ -5437,25 +5486,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "tungstenite"
|
||||
version = "0.23.0"
|
||||
@ -5477,6 +5507,24 @@ dependencies = [
|
||||
"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]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@ -6022,6 +6070,7 @@ name = "wrbapp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"csv",
|
||||
"dioxus",
|
||||
"dioxus-logger",
|
||||
@ -6029,8 +6078,11 @@ dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
"surrealdb",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -10,11 +10,15 @@ edition = "2021"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
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 }
|
||||
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 }
|
||||
surrealdb = { version = "2.0", features = ["kv-rocksdb"], optional = true }
|
||||
thiserror = { version = "1.0" }
|
||||
|
||||
csv = { version = "1.3", optional = true }
|
||||
|
||||
@ -25,5 +29,5 @@ manganis = "0.2"
|
||||
|
||||
[features]
|
||||
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"]
|
||||
|
@ -2404,24 +2404,24 @@ input.tab:checked + .tab-content,
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
.mt-16 {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 5rem;
|
||||
.mt-3 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
@ -2601,8 +2601,8 @@ input.tab:checked + .tab-content,
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.pb-3 {
|
||||
padding-bottom: 0.75rem;
|
||||
.pt-2 {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
|
@ -3,7 +3,10 @@ pub mod icons;
|
||||
pub mod navbar;
|
||||
pub mod topbar;
|
||||
|
||||
use crate::Route;
|
||||
use crate::{
|
||||
util::model::{session::Session, user::User},
|
||||
Route,
|
||||
};
|
||||
use auth::Auth;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
@ -34,7 +37,9 @@ pub fn Global() -> Element {
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn get_user_from_cookie() -> Result<(), ServerFnError> {
|
||||
Err(ServerFnError::new("Not authenticated"))
|
||||
// Ok(())
|
||||
async fn get_user_from_cookie() -> Result<User, ServerFnError> {
|
||||
let token = Session::get_token_from_cookie().await?;
|
||||
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::util::model::member::Member;
|
||||
use crate::util::model::session::Session;
|
||||
use crate::util::model::user::User;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn Auth() -> Element {
|
||||
@ -10,14 +12,14 @@ pub fn Auth() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
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 {
|
||||
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 {
|
||||
class: "card-body",
|
||||
div {
|
||||
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 {
|
||||
onclick: move |_| register.set(false),
|
||||
role: "tab",
|
||||
@ -45,36 +47,49 @@ pub fn Auth() -> 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! {
|
||||
form {
|
||||
label {
|
||||
class: "form-control w-full",
|
||||
div {
|
||||
class: "label",
|
||||
span { class: "label-text", "Email" },
|
||||
}
|
||||
input {
|
||||
r#type: "email",
|
||||
class: "input input-bordered w-full",
|
||||
},
|
||||
}
|
||||
label {
|
||||
class: "form-control w-full mt-5",
|
||||
div {
|
||||
class: "label",
|
||||
span { class: "label-text", "Password" },
|
||||
}
|
||||
input {
|
||||
r#type: "password",
|
||||
class: "input input-bordered w-full",
|
||||
},
|
||||
}
|
||||
label {
|
||||
class: "form-control w-full",
|
||||
div {
|
||||
class: "card-actions mt-8",
|
||||
button {
|
||||
class: "btn btn-primary",
|
||||
"Inloggen"
|
||||
}
|
||||
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())
|
||||
},
|
||||
}
|
||||
div {
|
||||
class: "card-actions mt-5",
|
||||
button {
|
||||
class: "btn btn-primary",
|
||||
onclick: submit,
|
||||
"Inloggen",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,7 +97,20 @@ fn Login() -> Element {
|
||||
|
||||
fn Register() -> Element {
|
||||
let mut members: Signal<HashMap<String, Member>> = use_signal(|| HashMap::new());
|
||||
|
||||
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! {
|
||||
label {
|
||||
@ -112,7 +140,7 @@ fn Register() -> Element {
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "flex gap-1 mt-2 flex-wrap pb-3",
|
||||
class: "flex gap-1 pt-2 flex-wrap",
|
||||
for (id, member) in members() {
|
||||
div {
|
||||
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 {
|
||||
class: "card-actions mt-8",
|
||||
class: "card-actions mt-5",
|
||||
button {
|
||||
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]
|
||||
async fn fetch_member(registration_token: String) -> Result<Member, ServerFnError> {
|
||||
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)]
|
||||
|
||||
mod components;
|
||||
mod err;
|
||||
mod util;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub use err::Error;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use tracing::Level;
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod member;
|
||||
pub mod session;
|
||||
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,
|
||||
Err(err) => return Err(err.to_string()),
|
||||
};
|
||||
|
||||
let members = rows_to_members(rows);
|
||||
let members = MembersMigration::rows_to_members(rows);
|
||||
|
||||
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<()> {
|
||||
// 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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user