Added basic authentication backend and frontend

This commit is contained in:
xeovalyte 2024-06-04 19:55:49 +02:00
parent 38109d8108
commit fdf640ddab
No known key found for this signature in database
10 changed files with 2987 additions and 170 deletions

3
.gitignore vendored
View File

@ -7,3 +7,6 @@
# These are backup files generated by rustfmt
**/*.rs.bk
database
node_modules

2944
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,13 @@ edition = "2021"
[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
dioxus = { version = "0.5", features = ["fullstack", "router"] }
dioxus = { git = "https://github.com/DioxusLabs/dioxus.git", branch = "v0.5", features = ["fullstack", "router"] }
surrealdb = { version = "1.5.1", features = [ "kv-speedb" ], optional = true }
once_cell = "1.19.0"
tokio = { version = "1.38.0", features = [ "macros", "rt-multi-thread" ], optional = true }
argon2 = { version = "0.5.3", optional = true }
axum = { version = "0.7.5", optional = true }
# Debug
tracing = "0.1.40"
@ -17,5 +23,5 @@ dioxus-logger = "0.5.0"
[features]
default = []
server = ["dioxus/axum"]
server = ["dioxus/axum", "axum", "surrealdb", "tokio", "argon2" ]
web = ["dioxus/web"]

View File

@ -1871,39 +1871,27 @@ input.tab:checked + .tab-content,
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.static {
position: static;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.mt-6 {
margin-top: 1.5rem;
}
.mt-20 {
margin-top: 5rem;
}
.min-h-screen {
min-height: 100vh;
}
.w-96 {
width: 24rem;
.mt-6 {
margin-top: 1.5rem;
}
.w-full {
width: 100%;
}
.max-w-md {
max-width: 28rem;
}
.max-w-xl {
max-width: 36rem;
}
.max-w-2xl {
max-width: 42rem;
}
@ -1920,24 +1908,14 @@ input.tab:checked + .tab-content,
flex: none;
}
.bg-base-200 {
--tw-bg-opacity: 1;
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
}
.bg-base-100 {
--tw-bg-opacity: 1;
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
}
.bg-base-300 {
.bg-base-200 {
--tw-bg-opacity: 1;
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
}
.py-6 {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
}
.px-1 {
@ -1945,8 +1923,9 @@ input.tab:checked + .tab-content,
padding-right: 0.25rem;
}
.pt-20 {
padding-top: 5rem;
.py-6 {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.pt-40 {

View File

@ -1,3 +1,4 @@
use crate::server::auth;
use dioxus::prelude::*;
#[component]
@ -27,9 +28,15 @@ pub fn Auth() -> Element {
#[component]
fn Signin() -> Element {
let mut email = use_signal(|| "".to_string());
let mut password = use_signal(|| "".to_string());
rsx! {
form {
class: "card-body",
prevent_default: "oninput",
onsubmit: move |_| async move { if let Ok(_user_id) = auth::signin(email(), password()).await {
} },
div {
class: "form-control",
label {
@ -41,6 +48,8 @@ fn Signin() -> Element {
placeholder: "email",
class: "input input-bordered",
required: true,
value: "{email}",
oninput: move |event| email.set(event.value())
},
},
div {
@ -54,6 +63,8 @@ fn Signin() -> Element {
placeholder: "password",
class: "input input-bordered",
required: true,
value: "{password}",
oninput: move |event| password.set(event.value())
},
label {
class: "label",
@ -73,9 +84,17 @@ fn Signin() -> Element {
#[component]
fn Register() -> Element {
let mut email = use_signal(|| "".to_string());
let mut password = use_signal(|| "".to_string());
let mut confirm_password = use_signal(|| "".to_string());
rsx! {
form {
class: "card-body",
prevent_default: "oninput",
onsubmit: move |_| async move { if let Ok(user_id) = auth::register(email(), password()).await {
println!("Registered new user: {user_id}");
} },
div {
class: "form-control",
label {
@ -87,6 +106,8 @@ fn Register() -> Element {
placeholder: "email",
class: "input input-bordered",
required: true,
value: "{email}",
oninput: move |event| email.set(event.value())
},
},
div {
@ -100,6 +121,8 @@ fn Register() -> Element {
placeholder: "password",
class: "input input-bordered",
required: true,
value: "{password}",
oninput: move |event| password.set(event.value())
},
},
div {
@ -113,6 +136,8 @@ fn Register() -> Element {
placeholder: "password",
class: "input input-bordered",
required: true,
value: "{confirm_password}",
oninput: move |event| confirm_password.set(event.value())
},
},
div {

View File

@ -4,7 +4,7 @@ use dioxus::prelude::*;
pub fn Navbar() -> Element {
rsx! {
div {
class: "navbar bg-base-100",
class: "navbar bg-base-200",
div {
class: "flex-1",
a { class: "btn btn-ghost text-xl", href: "/", "XVMCMM" },

View File

@ -1,6 +1,8 @@
#![allow(non_snake_case)]
mod components;
mod server;
use dioxus::prelude::*;
use tracing::{info, Level};
@ -19,10 +21,42 @@ enum Route {
fn main() {
// Init logger
dioxus_logger::init(Level::INFO).expect("failed to init logger");
launch(App);
#[cfg(feature = "server")]
{
use axum::routing::*;
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
// Connect to databse
server::surrealdb::connect()
.await
.expect("Database connection failed");
tracing::info!("Database connection succesfull");
// Create axum app
let app = Router::new()
.serve_dioxus_application(ServeConfig::builder().build(), || {
VirtualDom::new(app)
})
.await;
// Run axum app
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
})
}
launch(app);
}
fn App() -> Element {
fn app() -> Element {
rsx! {
Navbar {},
Router::<Route> {},

4
src/server.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod auth;
#[cfg(feature = "server")]
pub mod surrealdb;

58
src/server/auth.rs Normal file
View File

@ -0,0 +1,58 @@
#[cfg(feature = "server")]
use super::surrealdb::DB;
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use surrealdb::sql::Thing;
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
email: String,
password: String,
}
#[cfg(feature = "server")]
#[derive(Debug, Deserialize)]
struct Record {
id: Thing,
}
#[server(Register)]
pub async fn register(email: String, password: String) -> Result<String, ServerFnError> {
tracing::info!("Creating new user");
let mut res = DB
.query("CREATE user SET email = $email, password = crypto::argon2::generate($password)")
.bind(("email", email))
.bind(("password", password))
.await?;
let user: Option<Record> = res.take(0)?;
match user {
Some(Record { id }) => {
tracing::info!("Created new user ({id})");
Ok(id.to_string())
}
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
}
}
#[server(Signin)]
pub async fn signin(email: String, password: String) -> Result<User, ServerFnError> {
let mut res = DB
.query("SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
.bind(("email", email))
.bind(("password", password))
.await?;
let user: Option<User> = res.take(0)?;
match user {
Some(u) => {
tracing::info!("User ({}) has signed in", u.email);
Ok(u)
}
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
}
}

30
src/server/surrealdb.rs Normal file
View File

@ -0,0 +1,30 @@
use once_cell::sync::Lazy;
use surrealdb::engine::local::{Db, SpeeDb};
use surrealdb::Surreal;
pub static DB: Lazy<Surreal<Db>> = Lazy::new(|| Surreal::init());
pub async fn connect() -> surrealdb::Result<()> {
DB.connect::<SpeeDb>("./database").await?;
DB.use_ns("xvmcmm").use_db("xvmcmm").await?;
define_schema().await?;
Ok(())
}
pub async fn define_schema() -> surrealdb::Result<()> {
let sql = "
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON TABLE user TYPE string
ASSERT string::is::email($value);
DEFINE FIELD password ON TABLE user TYPE string;
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
";
DB.query(sql).await?;
Ok(())
}