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();