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:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GuruConnect - Technician Login</title>
|
||||
<title>GuruConnect - Login</title>
|
||||
<style>
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
label { font-size: 14px; font-weight: 500; color: hsl(var(--foreground)); }
|
||||
|
||||
input[type="email"], input[type="password"] {
|
||||
input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
@@ -119,40 +119,24 @@
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.loading .spinner { display: inline-block; }
|
||||
|
||||
.demo-hint {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: hsla(var(--primary), 0.1);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-hint a {
|
||||
color: hsl(var(--primary));
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<h1>GuruConnect</h1>
|
||||
<p>Technician Login</p>
|
||||
<p>Sign in to your account</p>
|
||||
</div>
|
||||
|
||||
<form class="login-form" id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" placeholder="you@company.com" required>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" placeholder="Enter your username" autocomplete="username" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" placeholder="Enter your password" required>
|
||||
<input type="password" id="password" placeholder="Enter your password" autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="errorMessage"></div>
|
||||
@@ -163,10 +147,6 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="demo-hint">
|
||||
<p>Auth not yet configured. <a href="/dashboard">Skip to Dashboard</a></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><a href="/">Back to Support Portal</a></p>
|
||||
</div>
|
||||
@@ -177,10 +157,29 @@
|
||||
const loginBtn = document.getElementById("loginBtn");
|
||||
const errorMessage = document.getElementById("errorMessage");
|
||||
|
||||
// Check if already logged in
|
||||
const token = localStorage.getItem("guruconnect_token");
|
||||
if (token) {
|
||||
// Verify token is still valid
|
||||
fetch('/api/auth/me', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
window.location.href = '/dashboard';
|
||||
} else {
|
||||
localStorage.removeItem('guruconnect_token');
|
||||
localStorage.removeItem('guruconnect_user');
|
||||
}
|
||||
}).catch(() => {
|
||||
localStorage.removeItem('guruconnect_token');
|
||||
localStorage.removeItem('guruconnect_user');
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById("email").value;
|
||||
const username = document.getElementById("username").value;
|
||||
const password = document.getElementById("password").value;
|
||||
|
||||
setLoading(true);
|
||||
@@ -190,7 +189,7 @@
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password })
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
@@ -201,12 +200,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem("token", data.token);
|
||||
localStorage.setItem("user", JSON.stringify(data.user));
|
||||
// Store token and user info
|
||||
localStorage.setItem("guruconnect_token", data.token);
|
||||
localStorage.setItem("guruconnect_user", JSON.stringify(data.user));
|
||||
window.location.href = "/dashboard";
|
||||
|
||||
} catch (err) {
|
||||
showError("Auth not configured yet. Use the demo link below.");
|
||||
showError("Connection error. Please try again.");
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
@@ -222,9 +222,8 @@
|
||||
loginBtn.querySelector(".btn-text").textContent = loading ? "Signing in..." : "Sign In";
|
||||
}
|
||||
|
||||
if (localStorage.getItem("token")) {
|
||||
window.location.href = "/dashboard";
|
||||
}
|
||||
// Focus username field
|
||||
document.getElementById("username").focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user