Added basic auth system

This commit is contained in:
xeovalyte 2024-09-30 17:01:13 +02:00
parent a503726b42
commit 5ca02189f4
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:kSQDrQDmKzljJzfGYcd3m9RqHi4h8rSwkZ3sQ9kBURo
12 changed files with 466 additions and 97 deletions

144
Cargo.lock generated
View File

@ -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]]

View File

@ -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"]

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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,36 +47,49 @@ 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 {
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",
},
}
div { div {
class: "card-actions mt-8", class: "label",
button { span { class: "label-text", "Email" },
class: "btn btn-primary", }
"Inloggen" 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 { 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
View 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),
}

View File

@ -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;

View File

@ -1,2 +1,3 @@
pub mod member; pub mod member;
pub mod session;
pub mod user; pub mod user;

View File

@ -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
View 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),
}
}
}

View File

@ -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),
}
}
}

View File

@ -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(())
} }