diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-29 01:03:24 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-29 01:03:24 +0200 |
| commit | 74d2ada067bd2fd9436710ab7ac260e0eddf1314 (patch) | |
| tree | 1f791bb895a611d8e729a735ba098a647808a157 /src/auth.rs | |
| parent | create example config on startup (diff) | |
| download | minecraft-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.rs | 27 |
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 | ( |
