Compare commits
5 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f009bc18b | ||
|
|
643b83ac21 | ||
|
|
90b65240b2 | ||
|
|
69243a5590 | ||
|
|
10262142fc |
@@ -6,6 +6,8 @@ use axum::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
|
||||
use time::Duration;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct SetupRequest {
|
||||
@@ -41,7 +43,7 @@ pub async fn get_setup_status_handler(State(state): State<AppState>) -> impl Int
|
||||
path = "/api/setup",
|
||||
request_body = SetupRequest,
|
||||
responses(
|
||||
(status = 200, description = "Setup completed"),
|
||||
(status = 200, description = "Setup completed and logged in"),
|
||||
(status = 400, description = "Invalid request"),
|
||||
(status = 403, description = "Setup already completed"),
|
||||
(status = 500, description = "Internal server error")
|
||||
@@ -49,6 +51,7 @@ pub async fn get_setup_status_handler(State(state): State<AppState>) -> impl Int
|
||||
)]
|
||||
pub async fn setup_handler(
|
||||
State(state): State<AppState>,
|
||||
jar: CookieJar,
|
||||
Json(payload): Json<SetupRequest>,
|
||||
) -> impl IntoResponse {
|
||||
// 1. Check if setup is already completed (i.e., users exist)
|
||||
@@ -68,7 +71,6 @@ pub async fn setup_handler(
|
||||
|
||||
// 3. Create User
|
||||
// Lower cost for faster login on low-power devices (MIPS routers etc.)
|
||||
// Default is usually 12, which takes ~3s on slow CPUs. 6 should be much faster.
|
||||
let password_hash = match bcrypt::hash(&payload.password, 6) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
@@ -82,5 +84,42 @@ pub async fn setup_handler(
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create user").into_response();
|
||||
}
|
||||
|
||||
(StatusCode::OK, "Setup completed successfully").into_response()
|
||||
// 4. Auto-Login (Create Session)
|
||||
// Get the created user's ID
|
||||
let user = match state.db.get_user_by_username(&payload.username).await {
|
||||
Ok(Some(u)) => u,
|
||||
Ok(None) => return (StatusCode::INTERNAL_SERVER_ERROR, "User created but not found").into_response(),
|
||||
Err(e) => {
|
||||
tracing::error!("DB error fetching new user: {}", e);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response();
|
||||
}
|
||||
};
|
||||
let (user_id, _) = user;
|
||||
|
||||
// Create session token
|
||||
let token: String = (0..32).map(|_| {
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
rand::thread_rng().sample(Alphanumeric) as char
|
||||
}).collect();
|
||||
|
||||
// Default expiration: 1 day (since it's not "remember me")
|
||||
let expires_in = 60 * 60 * 24;
|
||||
let expires_at = time::OffsetDateTime::now_utc().unix_timestamp() + expires_in;
|
||||
|
||||
if let Err(e) = state.db.create_session(user_id, &token, expires_at).await {
|
||||
tracing::error!("Failed to create session for new user: {}", e);
|
||||
// Even if session fails, setup is technically complete, but login failed.
|
||||
// We return OK but user will have to login manually.
|
||||
return (StatusCode::OK, "Setup completed, please login").into_response();
|
||||
}
|
||||
|
||||
let mut cookie = Cookie::build(("auth_token", token))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Lax)
|
||||
.build();
|
||||
|
||||
cookie.set_max_age(Duration::seconds(expires_in));
|
||||
|
||||
(StatusCode::OK, jar.add(cookie), "Setup completed and logged in").into_response()
|
||||
}
|
||||
|
||||
@@ -56,31 +56,38 @@ pub fn App() -> impl IntoView {
|
||||
// 2. Check Auth Status
|
||||
let auth_res = gloo_net::http::Request::get("/api/auth/check").send().await;
|
||||
|
||||
match auth_res {
|
||||
Ok(resp) => {
|
||||
if resp.status() == 200 {
|
||||
logging::log!("Authenticated!");
|
||||
match auth_res {
|
||||
Ok(resp) => {
|
||||
if resp.status() == 200 {
|
||||
logging::log!("Authenticated!");
|
||||
|
||||
// Parse user info
|
||||
if let Ok(user_info) = resp.json::<UserResponse>().await {
|
||||
if let Some(store) = use_context::<crate::store::TorrentStore>() {
|
||||
store.user.set(Some(user_info.username));
|
||||
// Parse user info
|
||||
if let Ok(user_info) = resp.json::<UserResponse>().await {
|
||||
if let Some(store) = use_context::<crate::store::TorrentStore>() {
|
||||
store.user.set(Some(user_info.username));
|
||||
}
|
||||
}
|
||||
|
||||
set_is_authenticated.set(true);
|
||||
|
||||
// If user is already authenticated but on login/setup page, redirect to home
|
||||
let pathname = window().location().pathname().unwrap_or_default();
|
||||
if pathname == "/login" || pathname == "/setup" {
|
||||
logging::log!("Already authenticated, redirecting to home");
|
||||
let navigate = use_navigate();
|
||||
navigate("/", Default::default());
|
||||
}
|
||||
} else {
|
||||
logging::log!("Not authenticated, redirecting to /login");
|
||||
let navigate = use_navigate();
|
||||
let pathname = window().location().pathname().unwrap_or_default();
|
||||
if pathname != "/login" && pathname != "/setup" {
|
||||
navigate("/login", Default::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => logging::error!("Network error checking auth status: {}", e),
|
||||
}
|
||||
|
||||
set_is_authenticated.set(true);
|
||||
} else {
|
||||
logging::log!("Not authenticated, redirecting to /login");
|
||||
let navigate = use_navigate();
|
||||
let pathname = window().location().pathname().unwrap_or_default();
|
||||
if pathname != "/login" && pathname != "/setup" {
|
||||
navigate("/login", Default::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => logging::error!("Network error checking auth status: {}", e),
|
||||
}
|
||||
|
||||
set_is_loading.set(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -49,8 +48,9 @@ pub fn Setup() -> impl IntoView {
|
||||
match client.send().await {
|
||||
Ok(resp) => {
|
||||
if resp.ok() {
|
||||
// Redirect to login after setup (full reload to be safe)
|
||||
let _ = window().location().set_href("/login");
|
||||
// Redirect to home after setup (auto-login handled by backend)
|
||||
// Full reload to ensure auth state is refreshed
|
||||
let _ = window().location().set_href("/");
|
||||
} else {
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
set_error.set(Some(format!("Hata: {}", text)));
|
||||
|
||||
Reference in New Issue
Block a user