diff --git a/data/auth.sqlite b/data/auth.sqlite index cae6ee9..0f6f826 100644 Binary files a/data/auth.sqlite and b/data/auth.sqlite differ diff --git a/src/page.html b/src/page.html index c613edb..617778c 100644 --- a/src/page.html +++ b/src/page.html @@ -423,19 +423,145 @@ d.forEach(function(x){var o=document.createElement("option");o.value=x.num;o.tex }) // ═══════════════ FILE TREE PANEL ═══════════════ -var _tv=false, _tm="tree", _td=null; +var _tv=false, _tm="tree", _td=null, _tree_user=null; function toggleTree(){ - _tv=!_tv; - document.getElementById("mb-tree-panel").classList.toggle("active",_tv); - if(_tv)loadTree(); + var token=localStorage.getItem('tree_token'); + var savedUser=localStorage.getItem('tree_user'); + + if(token && savedUser){ + // Verify token validity + fetch('/api/v2/auth/verify',{ + headers:{'Authorization':'Bearer '+token} + }) + .then(function(r){return r.json()}) + .then(function(d){ + if(d.ok && d.user_id===savedUser){ + // Token valid, open tree + _tree_user=savedUser; + _tv=!_tv; + document.getElementById("mb-tree-panel").classList.toggle("active",_tv); + if(_tv)loadTree(); + }else{ + // Token invalid or user mismatch, clear and show login + localStorage.removeItem('tree_token'); + localStorage.removeItem('tree_user'); + showTreeLoginModal(); + } + }) + .catch(function(e){ + localStorage.removeItem('tree_token'); + localStorage.removeItem('tree_user'); + showTreeLoginModal(); + }); + }else{ + // No token, show login + showTreeLoginModal(); + } +} + +function showTreeLoginModal(){ + var m=document.getElementById('mb-tree-login-modal'); + + if(!m){ + m=document.createElement('div'); + m.id='mb-tree-login-modal'; + m.style.cssText='display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1e293b;border:1px solid #334155;padding:24px;border-radius:8px;z-index:10000;min-width:320px'; + m.innerHTML=''+ + '
File Tree Authentication
'+ + '
'+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + ''+ + ''+ + '
'+ + ''+ + '
'; + document.body.appendChild(m); + } + + document.getElementById('tree-user').value=''; + document.getElementById('tree-password').value=''; + document.getElementById('tree-password').type='password'; + document.getElementById('tree-error').textContent=''; + m.classList.add('active'); + m.style.display='block'; + document.getElementById('tree-user').focus(); +} + +function handleTreeKeyPress(e){ + if(e.key==='Enter'||e.keyCode===13){ + submitTreeLogin(); + } +} + +function toggleTreePassword(){ + var pwdInput=document.getElementById('tree-password'); + var toggleBtn=pwdInput.parentElement.querySelector('button'); + + if(pwdInput.type==='password'){ + pwdInput.type='text'; + toggleBtn.textContent='🙈'; + }else{ + pwdInput.type='password'; + toggleBtn.textContent='👁'; + } +} + +function submitTreeLogin(){ + var user=document.getElementById('tree-user').value.trim(); + var pwd=document.getElementById('tree-password').value; + + if(!user){ + document.getElementById('tree-error').textContent='User ID required'; + return; + } + if(!pwd){ + document.getElementById('tree-error').textContent='Password required'; + return; + } + + fetch('/api/v2/auth/login',{ + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify({username:user,password:pwd}) + }) + .then(function(r){return r.json()}) + .then(function(d){ + if(d.token){ + localStorage.setItem('tree_token',d.token); + localStorage.setItem('tree_user',user); + _tree_user=user; + document.getElementById('mb-tree-login-modal').style.display='none'; + toast('Logged in as '+user+' ✓'); + _tv=true; + document.getElementById("mb-tree-panel").classList.add("active"); + loadTree(); + }else{ + document.getElementById('tree-error').textContent=d.error||'Login failed'; + document.getElementById('tree-password').value=''; + document.getElementById('tree-password').focus(); + } + }) + .catch(function(e){ + document.getElementById('tree-error').textContent='Connection error: '+e; + }); } function loadTree(){ var b=document.getElementById("mb-tree-body"); if(!b)return; b.innerHTML="
Loading...
"; - fetch("/api/v2/tree/demo?mode="+_tm).then(function(r){return r.json()}).then(function(d){ + + var token=localStorage.getItem('tree_token'); + var user=_tree_user||localStorage.getItem('tree_user')||'demo'; + + fetch("/api/v2/tree/"+user+"?mode="+_tm,{ + headers:{'Authorization':'Bearer '+token} + }).then(function(r){return r.json()}).then(function(d){ _td=d; var h=""; // Mode buttons @@ -749,7 +875,9 @@ function selIcon(idx,ico){ } function applyIcon(nid,ico){ - fetch("/api/v2/tree/demo/node/"+nid,{method:"PUT",headers:{"Content-Type":"application/json"}, + var token=localStorage.getItem('tree_token'); + var user=_tree_user||localStorage.getItem('tree_user')||'demo'; + fetch("/api/v2/tree/"+user+"/node/"+nid,{method:"PUT",headers:{"Content-Type":"application/json","Authorization":"Bearer "+token}, body:JSON.stringify({icon:ico})}) .then(function(r){return r.json()}).then(function(){ loadTree();toast(ico?"Icon → "+ico:"Icon reset to default"); diff --git a/src/server.rs b/src/server.rs index 7c4566f..893c019 100644 --- a/src/server.rs +++ b/src/server.rs @@ -450,15 +450,14 @@ async fn get_tree( Path(user_id): Path, Query(query): Query, ) -> impl IntoResponse { - // Tree API is public for demo user (no authentication required) - // Commented out authentication check to allow public access - // if let Err(status) = verify_auth(&state, &headers) { - // return ( - // status, - // Json(serde_json::json!({"error": "Unauthorized"})), - // ) - // .into_response(); - // } + // Verify authentication for tree access + if let Err(status) = verify_auth(&state, &headers) { + return ( + status, + Json(serde_json::json!({"error": "Unauthorized", "message": "Please login to access file tree"})), + ) + .into_response(); + } let _ = &state.db_dir; let mode = query["mode"].as_str().unwrap_or("tree").to_string();