Skip to main content

webapp/
app.rs

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    // Verify the token is valid before attempting deletion
142    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}