Login Implementation

This commit is contained in:
2025-05-17 16:55:51 -04:00
parent bb740cbefc
commit d40c379216
15 changed files with 201 additions and 25 deletions

2
AobaClient/Cargo.lock generated
View File

@@ -37,12 +37,14 @@ name = "aoba-client"
version = "0.1.0"
dependencies = [
"dioxus",
"js-sys",
"prost",
"serde",
"serde_repr",
"tonic",
"tonic-build",
"tonic-web-wasm-client",
"web-sys",
]
[[package]]

View File

@@ -16,6 +16,8 @@ tonic = { version = "*", default-features = false, features = [
] }
prost = "0.13"
tonic-web-wasm-client = "0.7"
js-sys = { version = "0.3.77" }
web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
[build-dependencies]
tonic-build = { version = "*", default-features = false, features = ["prost"] }

View File

@@ -2,3 +2,8 @@
display: grid;
width: 100%;
}
label {
display: flex;
flex-direction: column;
}

View File

@@ -18,7 +18,7 @@ body {
margin: 0;
}
#main {
#main:has(#content) {
display: grid;
grid-template-columns: $navBarSize 1fr;
grid-template-areas: "Nav Content";
@@ -65,3 +65,40 @@ body {
}
}
}
#main:has(#centralModal) {
display: grid;
place-items: center;
height: 100dvh;
width: 100dvw;
}
#centralModal {
display: flex;
flex-direction: column;
}
form {
display: flex;
flex-direction: column;
gap: 5px;
}
.notif {
background-color: red;
display: grid;
grid-template-columns: 50px 1fr;
height: 50px;
border-radius: 20px;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
.icon {
padding: 10px;
}
.message {
padding: 10px;
align-self: center;
}
}

View File

@@ -21,6 +21,7 @@ pub fn Input(props: InputProps) -> Element {
input {
type : props.r#type.unwrap_or("text".into()),
value: props.value,
oninput: move |e| if let Some(mut s) = props.value { s.set(e.value()); },
name: props.name,
placeholder:ph,
required: props.required

View File

@@ -0,0 +1,52 @@
use dioxus::prelude::*;
#[component]
pub fn Info() -> Element {
rsx! {
svg {
class: "size-6",
fill: "currentColor",
view_box: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg",
path {
clip_rule: "evenodd",
d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 0 1 .67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 1 1-.671-1.34l.041-.022ZM12 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
fill_rule: "evenodd",
}
}
}
}
#[component]
pub fn Warn() -> Element {
rsx! {
svg {
class: "size-6",
fill: "currentColor",
view_box: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg",
path {
clip_rule: "evenodd",
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
fill_rule: "evenodd",
}
}
}
}
#[component]
pub fn Error() -> Element {
rsx! {
svg {
class: "size-6",
fill: "currentColor",
view_box: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg",
path {
clip_rule: "evenodd",
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
fill_rule: "evenodd",
}
}
}
}

View File

@@ -2,8 +2,11 @@ pub mod basic;
mod media_grid;
mod media_item;
mod navbar;
mod notif;
mod search;
pub use media_grid::*;
pub use media_item::*;
pub use navbar::*;
pub use notif::*;
pub use search::*;
mod icons;

View File

@@ -0,0 +1,44 @@
use dioxus::{html::u::icon, prelude::*};
use crate::components::icons;
#[derive(PartialEq, Clone, Props)]
pub struct NotifProps {
r#type: Option<NotifType>,
message: String,
}
#[derive(PartialEq, Clone)]
pub enum NotifType {
Notice,
Error,
Warning,
}
#[component]
pub fn Notif(props: NotifProps) -> Element {
let t = props.r#type.unwrap_or(NotifType::Notice);
let type_class = match t {
NotifType::Notice => "notice",
NotifType::Error => "error",
NotifType::Warning => "warning",
};
let m = props.message;
rsx! {
div{
class: "notif {type_class}",
div{
class: "icon",
match t {
NotifType::Notice => icons::Error(),
NotifType::Error => icons::Error(),
NotifType::Warning => icons::Warn(),
}
}
div{
class: "message",
"{m}"
}
}
}
}

View File

@@ -1,6 +1,35 @@
use dioxus::signals::Signal;
use dioxus::signals::{Signal, Writable};
use web_sys::window;
#[derive(Clone, Copy, Default)]
pub struct AuthContext {
pub jwt: Signal<Option<String>>,
}
impl AuthContext {
pub fn set_token(&mut self, token: String) {
self.jwt.set(Some(token.clone()));
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
_ = local_storage.set_item("token", token.as_str());
}
pub fn new() -> Self {
println!("new");
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
match local_storage.get_item("token") {
Ok(value) => {
if let Some(jwt) = value {
println!("jwt");
return AuthContext {
jwt: Signal::new(Some(jwt)),
};
}
return AuthContext::default();
}
Err(_) => {
println!("err");
AuthContext::default()
}
}
}
}

View File

@@ -1,10 +0,0 @@
use dioxus::prelude::*;
use crate::route::Route;
#[component]
pub fn BasicLayout() -> Element {
rsx! {
Outlet::<Route> {}
}
}

View File

@@ -1,14 +1,14 @@
use dioxus::prelude::*;
use crate::{components::Navbar, contexts::AuthContext, layouts::BasicLayout, views::Login, Route};
use crate::{components::Navbar, contexts::AuthContext, views::Login, Route};
#[component]
pub fn MainLayout() -> Element {
let auth_context = use_context::<AuthContext>();
// if auth_context.jwt.cloned().is_none() {
// return rsx! { Login { } };
// }
if auth_context.jwt.cloned().is_none() {
return rsx! { Login { } };
}
return rsx! {
Navbar {}

View File

@@ -1,4 +1,2 @@
mod basic_layout;
mod main_layout;
pub use basic_layout::*;
pub use main_layout::*;

View File

@@ -24,7 +24,7 @@ fn main() {
#[component]
fn App() -> Element {
let _auth_state = use_context_provider(|| AuthContext::default());
let _auth_state = use_context_provider(|| AuthContext::new());
rsx! {
document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }

View File

@@ -2,7 +2,7 @@ use dioxus::prelude::*;
use tonic::IntoRequest;
use crate::{
components::basic::{Button, Input},
components::{basic::Input, Notif, NotifType},
contexts::AuthContext,
rpc::{aoba::Credentials, get_auth_rpc_client},
};
@@ -11,9 +11,16 @@ use crate::{
pub fn Login() -> Element {
let username = use_signal(|| "".to_string());
let password = use_signal(|| "".to_string());
let mut error: Signal<Option<String>> = use_signal(|| None);
let mut auth_context = use_context::<AuthContext>();
let onclick = move |_| {
let login = move |e: Event<MouseData>| {
e.prevent_default();
if username.cloned().is_empty() || password.cloned().is_empty() {
error.set(Some("Username and Password are required".into()));
return;
}
spawn(async move {
let mut auth = get_auth_rpc_client();
let result = auth
@@ -31,8 +38,9 @@ pub fn Login() -> Element {
crate::rpc::aoba::login_response::Result::Jwt(jwt) => {
auth_context.jwt.set(Some(jwt.token));
}
crate::rpc::aoba::login_response::Result::Error(_login_error) => {
crate::rpc::aoba::login_response::Result::Error(login_error) => {
auth_context.jwt.set(None);
error.set(Some(login_error.message));
}
};
}
@@ -46,12 +54,15 @@ pub fn Login() -> Element {
rsx! {
div{
id: "centralModal",
if let Some(err) = error.cloned() {
Notif{ type: NotifType::Error, message: err}
}
form{
Input { type : "text", name: "username", label: "Username", value: username, required: true },
Input { type : "password", name: "password", label: "Password", value: password, required: true },
Button {
text: "Login!",
onclick: onclick
button {
onclick: login,
"Login!",
}
}
}

View File

@@ -25,6 +25,8 @@ public class AccountsService(IMongoDatabase db)
public async Task<User?> VerifyLoginAsync(string username, string password, CancellationToken cancellationToken = default)
{
var user = await _users.Find(u => u.Username == username).FirstOrDefaultAsync(cancellationToken);
if(user == null)
return null;
if(user.IsArgon && Argon2.Verify(user.PasswordHash, password))
return user;