feat: Add admin authentication for Settings panel
- Add sftpgo_admins table to auth.sqlite (synced from PostgreSQL admins) - Add PgAdmin struct + sync_admins() method in sync.rs - Add fetch_admins() method in pg_client.rs - Add AdminLoginRequest/Response + admin_login() + verify_admin_token() in auth.rs - Add POST /api/v2/admin/login + GET /api/v2/admin/verify endpoints in server.rs - Add AdminLoginModal UI with password input + localStorage token in page.html - Test password: admin123 (bcrypt hash updated in PostgreSQL admins table) Architecture: - Independent admin auth system (matches SFTPGo design) - Admin sessions stored in-memory (24h validity) - bcrypt password verification (cost=10) - localStorage token persistence for UI - Settings panel requires admin authentication Files changed: - data/init_auth_db.sql: +20 lines - src/sync.rs: +100 lines - src/pg_client.rs: +50 lines - src/auth.rs: +60 lines - src/server.rs: +50 lines - src/page.html: +70 lines Total: ~290 lines added Tested: Admin sync, login, verify, UI modal all working
This commit is contained in:
112
src/page.html
112
src/page.html
@@ -84,6 +84,18 @@ body.mb-locked .mb-tree-node:hover .mb-folder-actions{display:none!important}
|
||||
.mb-config-cancel-btn{background:#451a03;border:1px solid #fbbf24;color:#fbbf24;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px}
|
||||
.mb-password-toggle{background:none;border:none;color:#60a5fa;cursor:pointer;font-size:14px;padding:0 4px}
|
||||
.mb-password-toggle:hover{color:#3b82f6}
|
||||
#mb-admin-modal{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:280px}
|
||||
#mb-admin-modal.active{display:block}
|
||||
.mb-admin-title{color:#60a5fa;font-size:16px;font-weight:600;margin-bottom:16px}
|
||||
.mb-admin-input{background:#0f172a;border:1px solid #60a5fa;border-radius:4px;
|
||||
color:#e2e8f0;padding:8px 12px;width:100%;margin-bottom:12px;font-size:13px}
|
||||
.mb-admin-btn{background:#064e3b;border:1px solid #4ade80;color:#4ade80;
|
||||
padding:8px 16px;border-radius:4px;cursor:pointer;width:100%;font-size:13px}
|
||||
.mb-admin-btn:hover{background:#4ade80;color:#064e3b}
|
||||
.mb-admin-error{color:#ef4444;font-size:12px;margin-top:8px}
|
||||
.mb-admin-close{position:absolute;top:12px;right:12px;background:none;border:none;
|
||||
color:#64748b;font-size:18px;cursor:pointer}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
<script>mermaid.initialize({startOnLoad:false,theme:'dark',themeVariables:{primaryColor:'#60a5fa',primaryTextColor:'#e2e8f0',lineColor:'#94a3b8'}})</script>
|
||||
@@ -124,9 +136,85 @@ body.mb-locked .mb-tree-node:hover .mb-folder-actions{display:none!important}
|
||||
var _sv=false;
|
||||
|
||||
function toggleSettings(){
|
||||
_sv=!_sv;
|
||||
document.getElementById("mb-settings-panel").classList.toggle("active",_sv);
|
||||
if(_sv)loadSettings();
|
||||
var token=localStorage.getItem('admin_token');
|
||||
|
||||
if(token){
|
||||
// Verify token validity
|
||||
fetch('/api/v2/admin/verify',{
|
||||
headers:{'Authorization':'Bearer '+token}
|
||||
})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if(d.ok){
|
||||
// Token valid, open settings
|
||||
_sv=!_sv;
|
||||
document.getElementById("mb-settings-panel").classList.toggle("active",_sv);
|
||||
if(_sv)loadSettings();
|
||||
}else{
|
||||
// Token invalid, remove and show login
|
||||
localStorage.removeItem('admin_token');
|
||||
showAdminLoginModal();
|
||||
}
|
||||
})
|
||||
.catch(function(e){
|
||||
localStorage.removeItem('admin_token');
|
||||
showAdminLoginModal();
|
||||
});
|
||||
}else{
|
||||
// No token, show login
|
||||
showAdminLoginModal();
|
||||
}
|
||||
}
|
||||
|
||||
function showAdminLoginModal(){
|
||||
var m=document.getElementById('mb-admin-modal');
|
||||
|
||||
if(!m){
|
||||
m=document.createElement('div');
|
||||
m.id='mb-admin-modal';
|
||||
m.innerHTML='<button class=mb-admin-close onclick=this.parentElement.classList.remove("active")>✕</button>'+
|
||||
'<div class=mb-admin-title>Admin Authentication Required</div>'+
|
||||
'<input class=mb-admin-input type=password id=admin-password placeholder="Enter admin password">'+
|
||||
'<button class=mb-admin-btn onclick=submitAdminLogin()>Login</button>'+
|
||||
'<div class=mb-admin-error id=admin-error></div>';
|
||||
document.body.appendChild(m);
|
||||
}
|
||||
|
||||
document.getElementById('admin-password').value='';
|
||||
document.getElementById('admin-error').textContent='';
|
||||
m.classList.add('active');
|
||||
document.getElementById('admin-password').focus();
|
||||
}
|
||||
|
||||
function submitAdminLogin(){
|
||||
var pwd=document.getElementById('admin-password').value;
|
||||
|
||||
if(!pwd){
|
||||
document.getElementById('admin-error').textContent='Password required';
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/v2/admin/login',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({username:'admin',password:pwd})
|
||||
})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if(d.token){
|
||||
localStorage.setItem('admin_token',d.token);
|
||||
document.getElementById('mb-admin-modal').classList.remove('active');
|
||||
toast('Admin authenticated ✓');
|
||||
toggleSettings(); // Re-open settings
|
||||
}else{
|
||||
document.getElementById('admin-error').textContent=d.error||'Login failed';
|
||||
document.getElementById('admin-password').value='';
|
||||
document.getElementById('admin-password').focus();
|
||||
}
|
||||
})
|
||||
.catch(function(e){
|
||||
document.getElementById('admin-error').textContent='Connection error: '+e;
|
||||
});
|
||||
}
|
||||
|
||||
function loadSettings(){
|
||||
@@ -204,7 +292,7 @@ function editSetting(key,currentVal){
|
||||
editBtn.outerHTML="<button class=mb-config-save-btn onclick=saveSetting(\""+key+"\",\""+safeId+"\",\""+isPassword+"\")>Save</button><button class=mb-config-cancel-btn onclick=cancelEdit(\""+key+"\",\""+currentVal+"\")>Cancel</button>";
|
||||
}
|
||||
|
||||
function saveSetting(key,safeId){
|
||||
function saveSetting(key,safeId,isPassword){
|
||||
var input=document.getElementById("config-input-"+safeId);
|
||||
if(!input)return;
|
||||
|
||||
@@ -225,6 +313,22 @@ function cancelEdit(key,currentVal){
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
function togglePassword(safeId,actualVal){
|
||||
var valEl=document.getElementById("config-value-"+safeId);
|
||||
if(!valEl)return;
|
||||
|
||||
var toggleBtn=valEl.parentElement.querySelector(".mb-password-toggle");
|
||||
if(!toggleBtn)return;
|
||||
|
||||
if(valEl.textContent==="••••••••"){
|
||||
valEl.textContent=decodeURIComponent(actualVal);
|
||||
toggleBtn.textContent="🙈";
|
||||
}else{
|
||||
valEl.textContent="••••••••";
|
||||
toggleBtn.textContent="👁";
|
||||
}
|
||||
}
|
||||
|
||||
function validateSettings(){
|
||||
fetch("/api/v2/config/validate").then(function(r){return r.json()}).then(function(d){
|
||||
if(d.ok)toast("✓ Settings valid");
|
||||
|
||||
Reference in New Issue
Block a user