Added basic authentication backend and frontend
This commit is contained in:
parent
38109d8108
commit
fdf640ddab
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,3 +7,6 @@
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
database
|
||||
node_modules
|
||||
|
2944
Cargo.lock
generated
2944
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -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"]
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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" },
|
||||
|
38
src/main.rs
38
src/main.rs
@ -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
4
src/server.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod auth;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub mod surrealdb;
|
58
src/server/auth.rs
Normal file
58
src/server/auth.rs
Normal 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
30
src/server/surrealdb.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user