1use leptos::prelude::*;
2use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
3use leptos_router::{
4 StaticSegment,
5 components::{Route, Router, Routes},
6};
7
8use crate::pages::{content::ContentPage, login::LoginPage};
9
10pub fn shell(options: LeptosOptions) -> impl IntoView {
11 view! {
12 <!DOCTYPE html>
13 <html lang="en">
14 <head>
15 <meta charset="utf-8"/>
16 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <AutoReload options=options.clone()/>
18 <HydrationScripts options/>
19 <MetaTags/>
20 </head>
21 <body>
22 <App/>
23 </body>
24 </html>
25 }
26}
27
28#[component]
29pub fn App() -> impl IntoView {
30 provide_meta_context();
31
32 view! {
33 <Stylesheet id="leptos" href="/pkg/webapp.css"/>
34 <Title text="WebApp.rs"/>
35 <Router>
36 <main>
37 <Routes fallback=|| "Page not found.".into_view()>
38 <Route path=StaticSegment("") view=LoginPage/>
39 <Route path=StaticSegment("content") view=ContentPage/>
40 </Routes>
41 </main>
42 </Router>
43 }
44}
45
46#[server]
47pub async fn register(username: String, password: String) -> Result<(), ServerFnError> {
48 use crate::{auth, database};
49
50 if username.is_empty() || password.is_empty() {
51 return Err(ServerFnError::new("Username and password are required"));
52 }
53
54 if username.len() > 64 || password.len() > 128 {
55 return Err(ServerFnError::new("Input too long"));
56 }
57
58 if database::user_exists(&username)
59 .await
60 .map_err(|e| ServerFnError::new(e.to_string()))?
61 {
62 return Err(ServerFnError::new("User already exists"));
63 }
64
65 let hash = auth::hash_password(&password).map_err(ServerFnError::new)?;
66 database::create_user(&username, &hash)
67 .await
68 .map_err(|e| ServerFnError::new(e.to_string()))?;
69
70 Ok(())
71}
72
73#[server]
74pub async fn login(username: String, password: String) -> Result<String, ServerFnError> {
75 use crate::{auth, database};
76
77 if username.is_empty() || password.is_empty() {
78 return Err(ServerFnError::new("Invalid credentials"));
79 }
80
81 let hash = database::get_password_hash(&username)
82 .await
83 .map_err(|e| ServerFnError::new(e.to_string()))?
84 .ok_or_else(|| ServerFnError::new("Invalid credentials"))?;
85
86 if !auth::verify_password(&password, &hash).map_err(ServerFnError::new)? {
87 return Err(ServerFnError::new("Invalid credentials"));
88 }
89
90 let token = auth::create_token(&username).map_err(|e| ServerFnError::new(e.to_string()))?;
91 let expires_at = auth::token_expiry();
92 database::create_session(&token, &username, expires_at)
93 .await
94 .map_err(|e| ServerFnError::new(e.to_string()))?;
95
96 Ok(token)
97}
98
99#[server]
100pub async fn renew_session(token: String) -> Result<String, ServerFnError> {
101 use crate::{auth, database};
102
103 let username = auth::verify_token(&token).map_err(|e| ServerFnError::new(e.to_string()))?;
104
105 if !database::session_exists(&token)
106 .await
107 .map_err(|e| ServerFnError::new(e.to_string()))?
108 {
109 return Err(ServerFnError::new("Session not found"));
110 }
111
112 let new_token = auth::create_token(&username).map_err(|e| ServerFnError::new(e.to_string()))?;
113 let expires_at = auth::token_expiry();
114 database::update_session(&token, &new_token, expires_at)
115 .await
116 .map_err(|e| ServerFnError::new(e.to_string()))?;
117
118 Ok(new_token)
119}
120
121#[server]
122pub async fn whoami(token: String) -> Result<String, ServerFnError> {
123 use crate::{auth, database};
124
125 let username = auth::verify_token(&token).map_err(|e| ServerFnError::new(e.to_string()))?;
126
127 if !database::session_exists(&token)
128 .await
129 .map_err(|e| ServerFnError::new(e.to_string()))?
130 {
131 return Err(ServerFnError::new("Session not found"));
132 }
133
134 Ok(username)
135}
136
137#[server]
138pub async fn logout(token: String) -> Result<(), ServerFnError> {
139 use crate::{auth, database};
140
141 auth::verify_token(&token).map_err(|e| ServerFnError::new(e.to_string()))?;
143
144 if !database::delete_session(&token)
145 .await
146 .map_err(|e| ServerFnError::new(e.to_string()))?
147 {
148 return Err(ServerFnError::new("Session not found"));
149 }
150
151 Ok(())
152}