summaryrefslogtreecommitdiffstats
path: root/src/auth.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-29 01:03:24 +0200
committersalmonik <salmonik@tuta.io>2026-07-29 01:03:24 +0200
commit74d2ada067bd2fd9436710ab7ac260e0eddf1314 (patch)
tree1f791bb895a611d8e729a735ba098a647808a157 /src/auth.rs
parentcreate example config on startup (diff)
downloadminecraft-ui-74d2ada067bd2fd9436710ab7ac260e0eddf1314.tar.gz
minecraft-ui-74d2ada067bd2fd9436710ab7ac260e0eddf1314.zip
Add per-instance mod management with client-side detection
- New mod validation engine (src/mods.rs) with constant-pool class-file scanning: detects server-side content (block, tileentity, command, worldgen) vs client-only API (gui, renderer, forge/client) - 7 client-only mods correctly detected (AppleSkin, CraftingTweaks, Durability101, EnchantmentDescriptions, InventoryHUD, MouseTweaks, TrashSlot); 51 server-capable; 1 unknown - Routes + templating for mod upload (jar/zip), listing, delete, rescan - Multipart upload with 500MB limit (bypasses axum's default 2MB) - Per-instance mod summary card on edit page - Whitelist toggle checkbox on edit page (persisted, drives ENABLE_WHITELIST) - Dynamic Java image tag (java8/java17/java21) based on MC version - Class scan is informational-only; rejection based on metadata side=CLIENT - has_modpack flag cleanup on mod delete - Remove hx-boost from <body> (was corrupting multipart uploads)
Diffstat (limited to 'src/auth.rs')
-rw-r--r--src/auth.rs27
1 files changed, 16 insertions, 11 deletions
diff --git a/src/auth.rs b/src/auth.rs
index 8fa62ec..3c89e37 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -97,15 +97,17 @@ pub async fn require_auth(
97 mut request: Request, 97 mut request: Request,
98 next: Next, 98 next: Next,
99) -> Result<Response, Response> { 99) -> Result<Response, Response> {
100 let cookies = request 100 let raw_cookies = request
101 .headers() 101 .headers()
102 .get(axum::http::header::COOKIE) 102 .get(axum::http::header::COOKIE)
103 .and_then(|v| v.to_str().ok()) 103 .and_then(|v| v.to_str().ok())
104 .unwrap_or(""); 104 .unwrap_or("[no cookie header]")
105 .to_string();
105 106
106 let session_id = cookies.split(';').map(|s| s.trim()).find_map(|cookie| { 107 let session_id = raw_cookies.split(';').map(|s| s.trim()).find_map(|cookie| {
107 if let Some((name, value)) = cookie.split_once('=') { 108 if let Some((name, value)) = cookie.split_once('=') {
108 if name.trim() == SESSION_COOKIE { 109 let n = name.trim();
110 if n == SESSION_COOKIE {
109 Some(value.trim().to_string()) 111 Some(value.trim().to_string())
110 } else { 112 } else {
111 None 113 None
@@ -137,13 +139,18 @@ pub async fn require_auth(
137 .ok(); 139 .ok();
138 140
139 if let Some(account) = account { 141 if let Some(account) = account {
142 tracing::debug!("Auth OK for {}", account.username);
140 request.extensions_mut().insert(account); 143 request.extensions_mut().insert(account);
141 Ok(next.run(request).await) 144 Ok(next.run(request).await)
142 } else { 145 } else {
146 tracing::warn!("Session {id} not found in DB (raw cookies: {raw_cookies})");
143 Err(Redirect::to("/login").into_response()) 147 Err(Redirect::to("/login").into_response())
144 } 148 }
145 } 149 }
146 None => Err(Redirect::to("/login").into_response()), 150 None => {
151 tracing::warn!("No session cookie found (raw cookies: {raw_cookies})");
152 Err(Redirect::to("/login").into_response())
153 }
147 } 154 }
148} 155}
149 156
@@ -170,21 +177,19 @@ pub async fn logout(
170 State(state): State<AppState>, 177 State(state): State<AppState>,
171 headers: axum::http::HeaderMap, 178 headers: axum::http::HeaderMap,
172) -> impl IntoResponse { 179) -> impl IntoResponse {
173 if let Some(cookie_header) = headers.get(axum::http::header::COOKIE) { 180 if let Some(cookie_header) = headers.get(axum::http::header::COOKIE)
174 if let Ok(cookie_str) = cookie_header.to_str() { 181 && let Ok(cookie_str) = cookie_header.to_str() {
175 for part in cookie_str.split(';') { 182 for part in cookie_str.split(';') {
176 let part = part.trim(); 183 let part = part.trim();
177 if let Some((name, value)) = part.split_once('=') { 184 if let Some((name, value)) = part.split_once('=')
178 if name.trim() == SESSION_COOKIE { 185 && name.trim() == SESSION_COOKIE {
179 let _ = state 186 let _ = state
180 .db 187 .db
181 .conn() 188 .conn()
182 .execute("DELETE FROM sessions WHERE id = ?1", params![value.trim()]); 189 .execute("DELETE FROM sessions WHERE id = ?1", params![value.trim()]);
183 } 190 }
184 }
185 } 191 }
186 } 192 }
187 }
188 193
189 let clear_cookie = format!("{SESSION_COOKIE}=; Path=/; HttpOnly; Max-Age=0"); 194 let clear_cookie = format!("{SESSION_COOKIE}=; Path=/; HttpOnly; Max-Age=0");
190 ( 195 (