Add user management system with JWT authentication

- Database schema: users, permissions, client_access tables
- Auth: JWT tokens with Argon2 password hashing
- API: login, user CRUD, permission management
- Dashboard: login required, admin Users tab
- Auto-creates initial admin user on first run
This commit is contained in:
2025-12-29 20:57:30 -07:00
parent 743b73dfe7
commit 3fc4e1f96a
13 changed files with 2354 additions and 70 deletions

View File

@@ -348,6 +348,7 @@
<button class="tab" data-tab="access">Access</button>
<button class="tab" data-tab="build">Build</button>
<button class="tab" data-tab="settings">Settings</button>
<button class="tab admin-only" data-tab="users" style="display: none;">Users</button>
</nav>
<main class="content">
@@ -510,6 +511,21 @@
</p>
</div>
</div>
<!-- Users Tab (Admin Only) -->
<div class="tab-panel" id="users-panel">
<div class="card">
<div class="card-header">
<div>
<h2 class="card-title">User Management</h2>
<p class="card-description">Manage user accounts and permissions</p>
</div>
</div>
<p style="color: hsl(var(--muted-foreground));">
<a href="/users" style="color: hsl(var(--primary));">Open User Management</a>
</p>
</div>
</div>
</main>
<!-- Chat Modal -->
@@ -546,19 +562,59 @@
});
// Check auth
const token = localStorage.getItem("token");
const user = JSON.parse(localStorage.getItem("user") || "null");
const token = localStorage.getItem("guruconnect_token");
const user = JSON.parse(localStorage.getItem("guruconnect_user") || "null");
if (!token) {
document.getElementById("userInfo").textContent = "Demo Mode";
} else if (user) {
document.getElementById("userInfo").textContent = user.email || user.name || "Technician";
// Verify authentication
async function checkAuth() {
if (!token) {
window.location.href = "/login";
return;
}
try {
const response = await fetch("/api/auth/me", {
headers: { "Authorization": `Bearer ${token}` }
});
if (!response.ok) {
localStorage.removeItem("guruconnect_token");
localStorage.removeItem("guruconnect_user");
window.location.href = "/login";
return;
}
const userData = await response.json();
// Update user display
document.getElementById("userInfo").textContent = userData.username + " (" + userData.role + ")";
// Show admin-only elements
if (userData.role === "admin") {
document.querySelectorAll(".admin-only").forEach(el => {
el.style.display = "";
});
}
} catch (err) {
console.error("Auth check failed:", err);
// Don't redirect on network error, just show what we have
if (user) {
document.getElementById("userInfo").textContent = user.username || "User";
if (user.role === "admin") {
document.querySelectorAll(".admin-only").forEach(el => {
el.style.display = "";
});
}
}
}
}
checkAuth();
// Logout
document.getElementById("logoutBtn").addEventListener("click", () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("guruconnect_token");
localStorage.removeItem("guruconnect_user");
window.location.href = "/login";
});