added authentication logic and password change enforcement
This commit is contained in:
parent
9128fce5c2
commit
23b38483b8
|
|
@ -19,6 +19,11 @@ class AuthManager {
|
|||
$_SESSION['last_activity'] = time();
|
||||
$_SESSION['login_time'] = time();
|
||||
|
||||
// Check if this is first login with default credentials
|
||||
if (USER_FIRST_LOGIN) {
|
||||
$_SESSION['requires_setup'] = true;
|
||||
}
|
||||
|
||||
// Generate new CSRF token
|
||||
generateCSRFToken();
|
||||
|
||||
|
|
@ -79,6 +84,75 @@ class AuthManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user credentials (username and/or password)
|
||||
*/
|
||||
public static function changeCredentials($currentPassword, $newUsername, $newPassword) {
|
||||
// Verify current password
|
||||
if (!password_verify($currentPassword, ADMIN_PASSWORD_HASH)) {
|
||||
return ['success' => false, 'error' => 'Current password is incorrect'];
|
||||
}
|
||||
|
||||
// Validate new username
|
||||
$newUsername = sanitizeInput($newUsername);
|
||||
if (strlen($newUsername) < 3 || strlen($newUsername) > 20) {
|
||||
return ['success' => false, 'error' => 'Username must be between 3 and 20 characters'];
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $newUsername)) {
|
||||
return ['success' => false, 'error' => 'Username can only contain letters, numbers, and underscores'];
|
||||
}
|
||||
|
||||
// Validate new password
|
||||
if (strlen($newPassword) < 6) {
|
||||
return ['success' => false, 'error' => 'Password must be at least 6 characters long'];
|
||||
}
|
||||
|
||||
try {
|
||||
// Load current user config
|
||||
$userConfig = loadUserConfig();
|
||||
|
||||
// Update credentials
|
||||
$userConfig['username'] = $newUsername;
|
||||
$userConfig['password_hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$userConfig['first_login'] = false;
|
||||
$userConfig['last_updated'] = time();
|
||||
|
||||
// Save updated config
|
||||
saveUserConfig($userConfig);
|
||||
|
||||
logMessage('INFO', "User credentials changed successfully. New username: '$newUsername'");
|
||||
|
||||
return ['success' => true, 'message' => 'Credentials updated successfully'];
|
||||
|
||||
} catch (Exception $e) {
|
||||
logMessage('ERROR', "Failed to update user credentials: " . $e->getMessage());
|
||||
return ['success' => false, 'error' => 'Failed to save new credentials'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user requires setup (first login)
|
||||
*/
|
||||
public static function requiresSetup() {
|
||||
return isset($_SESSION['requires_setup']) && $_SESSION['requires_setup'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete initial setup
|
||||
*/
|
||||
public static function completeSetup($currentPassword, $newUsername, $newPassword) {
|
||||
$result = self::changeCredentials($currentPassword, $newUsername, $newPassword);
|
||||
|
||||
if ($result['success']) {
|
||||
// Clear setup requirement
|
||||
unset($_SESSION['requires_setup']);
|
||||
logMessage('INFO', "Initial setup completed for user: '$newUsername'");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -27,9 +27,51 @@ define('SYSTEMD_SERVICE_FILE', '/etc/systemd/system/ziti-router.service');
|
|||
define('UI_LOG_DIR', __DIR__ . '/../logs');
|
||||
define('UI_TEMP_DIR', __DIR__ . '/../temp');
|
||||
|
||||
// Authentication
|
||||
define('ADMIN_USERNAME', 'admin');
|
||||
define('ADMIN_PASSWORD_HASH', password_hash('admin123', PASSWORD_DEFAULT)); // Change this in production
|
||||
// Load user configuration
|
||||
function loadUserConfig() {
|
||||
$userConfigFile = __DIR__ . '/user_config.php';
|
||||
|
||||
// Create default user config if it doesn't exist
|
||||
if (!file_exists($userConfigFile)) {
|
||||
$defaultConfig = [
|
||||
'username' => 'admin',
|
||||
'password_hash' => password_hash('admin123', PASSWORD_DEFAULT),
|
||||
'first_login' => true,
|
||||
'created_at' => time(),
|
||||
'last_updated' => time()
|
||||
];
|
||||
saveUserConfig($defaultConfig);
|
||||
return $defaultConfig;
|
||||
}
|
||||
|
||||
return include $userConfigFile;
|
||||
}
|
||||
|
||||
function saveUserConfig($config) {
|
||||
$userConfigFile = __DIR__ . '/user_config.php';
|
||||
|
||||
// Create backup if file exists
|
||||
if (file_exists($userConfigFile)) {
|
||||
copy($userConfigFile, $userConfigFile . '.backup');
|
||||
}
|
||||
|
||||
$configContent = "<?php\n/**\n * User Configuration File for ZitiNexus Router Enrollment UI\n * This file stores user-changeable settings like username and password\n */\n\nreturn " . var_export($config, true) . ";\n?>";
|
||||
|
||||
if (file_put_contents($userConfigFile, $configContent, LOCK_EX) === false) {
|
||||
throw new Exception('Failed to save user configuration');
|
||||
}
|
||||
|
||||
// Set proper permissions
|
||||
chmod($userConfigFile, 0644);
|
||||
}
|
||||
|
||||
// Load user configuration
|
||||
$userConfig = loadUserConfig();
|
||||
|
||||
// Authentication - Use user config values
|
||||
define('ADMIN_USERNAME', $userConfig['username']);
|
||||
define('ADMIN_PASSWORD_HASH', $userConfig['password_hash']);
|
||||
define('USER_FIRST_LOGIN', $userConfig['first_login']);
|
||||
|
||||
// Security settings
|
||||
define('SESSION_TIMEOUT', 3600); // 1 hour
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* User Configuration File for ZitiNexus Router Enrollment UI
|
||||
* This file stores user-changeable settings like username and password
|
||||
*/
|
||||
|
||||
return [
|
||||
'username' => 'admin',
|
||||
'password_hash' => password_hash('admin123', PASSWORD_DEFAULT),
|
||||
'first_login' => true,
|
||||
'created_at' => time(),
|
||||
'last_updated' => time()
|
||||
];
|
||||
?>
|
||||
|
|
@ -492,3 +492,124 @@ body {
|
|||
|
||||
.text-sm { font-size: 0.875rem; }
|
||||
.text-xs { font-size: 0.75rem; }
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--card-background);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: modalSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: #f8fafc;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background-color: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Password strength and match indicators */
|
||||
.password-strength,
|
||||
.password-match {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
min-height: 1.2rem;
|
||||
}
|
||||
|
||||
/* Responsive modal */
|
||||
@media (max-width: 768px) {
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,17 @@ class EnrollmentUI {
|
|||
this.showCleanupConfirmation();
|
||||
});
|
||||
}
|
||||
|
||||
// Settings button
|
||||
const settingsBtn = document.getElementById('settingsBtn');
|
||||
if (settingsBtn) {
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
this.showSettingsModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Settings modal events
|
||||
this.bindSettingsModalEvents();
|
||||
}
|
||||
|
||||
validateHashKey(input) {
|
||||
|
|
@ -574,6 +585,213 @@ class EnrollmentUI {
|
|||
formatTimestamp(timestamp) {
|
||||
return new Date(timestamp * 1000).toLocaleString();
|
||||
}
|
||||
|
||||
// Settings Modal Methods
|
||||
bindSettingsModalEvents() {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
const closeBtn = document.getElementById('closeSettingsModal');
|
||||
const cancelBtn = document.getElementById('cancelSettings');
|
||||
const settingsForm = document.getElementById('settingsForm');
|
||||
|
||||
// Close modal events
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.hideSettingsModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
this.hideSettingsModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
if (modal) {
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
this.hideSettingsModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Settings form submission
|
||||
if (settingsForm) {
|
||||
settingsForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleSettingsSubmit();
|
||||
});
|
||||
}
|
||||
|
||||
// Password strength and match validation
|
||||
const newPasswordInput = document.getElementById('settings_new_password');
|
||||
const confirmPasswordInput = document.getElementById('settings_confirm_password');
|
||||
|
||||
if (newPasswordInput) {
|
||||
newPasswordInput.addEventListener('input', () => {
|
||||
this.updatePasswordStrength('settings');
|
||||
this.checkPasswordMatch('settings');
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmPasswordInput) {
|
||||
confirmPasswordInput.addEventListener('input', () => {
|
||||
this.checkPasswordMatch('settings');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showSettingsModal() {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) {
|
||||
modal.classList.add('active');
|
||||
// Focus on first input
|
||||
const firstInput = modal.querySelector('input[type="password"]');
|
||||
if (firstInput) {
|
||||
setTimeout(() => firstInput.focus(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideSettingsModal() {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('active');
|
||||
// Reset form
|
||||
const form = document.getElementById('settingsForm');
|
||||
if (form) {
|
||||
form.reset();
|
||||
// Reset password indicators
|
||||
document.getElementById('settingsPasswordStrength').innerHTML = '';
|
||||
document.getElementById('settingsPasswordMatch').innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePasswordStrength(prefix) {
|
||||
const passwordInput = document.getElementById(`${prefix}_new_password`);
|
||||
const strengthDiv = document.getElementById(`${prefix}PasswordStrength`);
|
||||
|
||||
if (!passwordInput || !strengthDiv) return;
|
||||
|
||||
const password = passwordInput.value;
|
||||
let strength = 0;
|
||||
let feedback = '';
|
||||
|
||||
if (password.length >= 6) strength++;
|
||||
if (password.length >= 8) strength++;
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
||||
|
||||
switch (strength) {
|
||||
case 0:
|
||||
case 1:
|
||||
feedback = '<span style="color: #ef4444;">Weak</span>';
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
feedback = '<span style="color: #f59e0b;">Medium</span>';
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
feedback = '<span style="color: #10b981;">Strong</span>';
|
||||
break;
|
||||
}
|
||||
|
||||
strengthDiv.innerHTML = password.length > 0 ? 'Strength: ' + feedback : '';
|
||||
}
|
||||
|
||||
checkPasswordMatch(prefix) {
|
||||
const passwordInput = document.getElementById(`${prefix}_new_password`);
|
||||
const confirmInput = document.getElementById(`${prefix}_confirm_password`);
|
||||
const matchDiv = document.getElementById(`${prefix}PasswordMatch`);
|
||||
|
||||
if (!passwordInput || !confirmInput || !matchDiv) return;
|
||||
|
||||
const password = passwordInput.value;
|
||||
const confirm = confirmInput.value;
|
||||
|
||||
if (confirm.length === 0) {
|
||||
matchDiv.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (password === confirm) {
|
||||
matchDiv.innerHTML = '<span style="color: #10b981;">✓ Passwords match</span>';
|
||||
} else {
|
||||
matchDiv.innerHTML = '<span style="color: #ef4444;">✗ Passwords do not match</span>';
|
||||
}
|
||||
}
|
||||
|
||||
async handleSettingsSubmit() {
|
||||
const form = document.getElementById('settingsForm');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
|
||||
// Get form data
|
||||
const currentPassword = document.getElementById('settings_current_password').value;
|
||||
const newUsername = document.getElementById('settings_new_username').value;
|
||||
const newPassword = document.getElementById('settings_new_password').value;
|
||||
const confirmPassword = document.getElementById('settings_confirm_password').value;
|
||||
|
||||
// Validate passwords match
|
||||
if (newPassword !== confirmPassword) {
|
||||
this.showAlert('New passwords do not match', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (newPassword.length < 6) {
|
||||
this.showAlert('Password must be at least 6 characters long', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner"></span>Saving...';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'change_credentials');
|
||||
formData.append('current_password', currentPassword);
|
||||
formData.append('new_username', newUsername);
|
||||
formData.append('new_password', newPassword);
|
||||
formData.append('csrf_token', document.querySelector('input[name="csrf_token"]').value);
|
||||
|
||||
const response = await fetch('dashboard.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
this.showAlert('Credentials updated successfully! You will be logged out in 3 seconds.', 'success');
|
||||
this.hideSettingsModal();
|
||||
|
||||
// Redirect to login page after 3 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = 'index.php?message=credentials_changed';
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to update credentials');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Settings update failed:', error);
|
||||
this.showAlert(`Failed to update credentials: ${error.message}`, 'error');
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '💾 Save Changes';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is loaded
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ require_once '../includes/enrollment.php';
|
|||
// Require authentication
|
||||
AuthManager::requireAuth();
|
||||
|
||||
// Check if user requires initial setup
|
||||
if (AuthManager::requiresSetup()) {
|
||||
header('Location: setup.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get current user
|
||||
$currentUser = AuthManager::getCurrentUser();
|
||||
|
||||
|
|
@ -59,6 +65,19 @@ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQU
|
|||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'change_credentials') {
|
||||
// Handle credentials change request
|
||||
AuthManager::requireCSRF();
|
||||
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newUsername = $_POST['new_username'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
|
||||
$result = AuthManager::changeCredentials($currentPassword, $newUsername, $newPassword);
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Get system status for initial page load
|
||||
|
|
@ -89,6 +108,9 @@ $systemStatus = $enrollmentManager->getSystemStatus();
|
|||
<button id="refreshStatus" class="btn btn-secondary" title="Refresh System Status">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
<button id="settingsBtn" class="btn btn-secondary" title="Account Settings">
|
||||
⚙️ Settings
|
||||
</button>
|
||||
<a href="index.php?action=logout" class="btn btn-secondary">
|
||||
Logout
|
||||
</a>
|
||||
|
|
@ -319,6 +341,92 @@ $systemStatus = $enrollmentManager->getSystemStatus();
|
|||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div id="settingsModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>⚙️ Account Settings</h2>
|
||||
<button class="modal-close" id="closeSettingsModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="settingsForm">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings_current_password" class="form-label">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="settings_current_password"
|
||||
name="current_password"
|
||||
class="form-input"
|
||||
placeholder="Enter current password"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Required to verify your identity</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings_new_username" class="form-label">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="settings_new_username"
|
||||
name="new_username"
|
||||
class="form-input"
|
||||
placeholder="Enter new username"
|
||||
value="<?php echo htmlspecialchars($currentUser['username']); ?>"
|
||||
minlength="3"
|
||||
maxlength="20"
|
||||
pattern="[a-zA-Z0-9_]+"
|
||||
required
|
||||
autocomplete="username"
|
||||
>
|
||||
<small class="text-sm text-secondary">3-20 characters, letters, numbers, and underscores only</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings_new_password" class="form-label">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="settings_new_password"
|
||||
name="new_password"
|
||||
class="form-input"
|
||||
placeholder="Enter new password"
|
||||
minlength="6"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Minimum 6 characters</small>
|
||||
<div id="settingsPasswordStrength" class="password-strength"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings_confirm_password" class="form-label">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="settings_confirm_password"
|
||||
name="confirm_password"
|
||||
class="form-input"
|
||||
placeholder="Confirm new password"
|
||||
minlength="6"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Re-enter your new password</small>
|
||||
<div id="settingsPasswordMatch" class="password-match"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn btn-secondary" id="cancelSettings">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
💾 Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
/**
|
||||
* Initial Setup Page for ZitiNexus Router Enrollment UI
|
||||
* Forces users to change default credentials on first login
|
||||
*/
|
||||
|
||||
require_once '../includes/auth.php';
|
||||
|
||||
// Require authentication but allow setup
|
||||
AuthManager::requireAuth();
|
||||
|
||||
// If user doesn't require setup, redirect to dashboard
|
||||
if (!AuthManager::requiresSetup()) {
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle setup form submission
|
||||
$setupError = '';
|
||||
$setupSuccess = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'setup') {
|
||||
AuthManager::requireCSRF();
|
||||
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newUsername = $_POST['new_username'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
// Validate password confirmation
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
$setupError = 'New passwords do not match';
|
||||
} else {
|
||||
$result = AuthManager::completeSetup($currentPassword, $newUsername, $newPassword);
|
||||
|
||||
if ($result['success']) {
|
||||
$setupSuccess = $result['message'];
|
||||
// Redirect to login page after successful setup
|
||||
header('refresh:3;url=index.php?message=setup_complete');
|
||||
} else {
|
||||
$setupError = $result['error'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$currentUser = AuthManager::getCurrentUser();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo APP_NAME; ?> - Initial Setup</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<link rel="icon" type="image/x-icon" href="assets/images/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-card" style="max-width: 500px;">
|
||||
<div class="login-header">
|
||||
<h1>🔐 Security Setup Required</h1>
|
||||
<p>For security reasons, you must change the default credentials before proceeding.</p>
|
||||
</div>
|
||||
|
||||
<?php if ($setupError): ?>
|
||||
<div class="alert alert-error">
|
||||
<?php echo htmlspecialchars($setupError); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($setupSuccess): ?>
|
||||
<div class="alert alert-success">
|
||||
<?php echo htmlspecialchars($setupSuccess); ?>
|
||||
<br><small>You will be redirected to login with your new credentials...</small>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<form method="POST" id="setupForm">
|
||||
<input type="hidden" name="action" value="setup">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="current_password" class="form-label">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="current_password"
|
||||
name="current_password"
|
||||
class="form-input"
|
||||
placeholder="Enter current password (admin123)"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Enter your current password to verify identity</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new_username" class="form-label">New Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="new_username"
|
||||
name="new_username"
|
||||
class="form-input"
|
||||
placeholder="Enter new username"
|
||||
value="<?php echo htmlspecialchars($currentUser['username']); ?>"
|
||||
minlength="3"
|
||||
maxlength="20"
|
||||
pattern="[a-zA-Z0-9_]+"
|
||||
required
|
||||
autocomplete="username"
|
||||
>
|
||||
<small class="text-sm text-secondary">3-20 characters, letters, numbers, and underscores only</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new_password" class="form-label">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
class="form-input"
|
||||
placeholder="Enter new password"
|
||||
minlength="6"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Minimum 6 characters</small>
|
||||
<div id="passwordStrength" class="password-strength"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="form-label">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
class="form-input"
|
||||
placeholder="Confirm new password"
|
||||
minlength="6"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<small class="text-sm text-secondary">Re-enter your new password</small>
|
||||
<div id="passwordMatch" class="password-match"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-full">
|
||||
🔒 Complete Setup
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div style="margin-top: 2rem; padding: 1rem; background: #f8fafc; border-radius: 8px; border-left: 4px solid #2563eb;">
|
||||
<h4 style="margin: 0 0 0.5rem 0; color: #1e293b;">Why is this required?</h4>
|
||||
<p style="margin: 0; font-size: 0.875rem; color: #64748b;">
|
||||
Default credentials pose a security risk. Changing them ensures only authorized users can access your router enrollment system.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Password strength indicator
|
||||
document.getElementById('new_password').addEventListener('input', function() {
|
||||
const password = this.value;
|
||||
const strengthDiv = document.getElementById('passwordStrength');
|
||||
|
||||
let strength = 0;
|
||||
let feedback = '';
|
||||
|
||||
if (password.length >= 6) strength++;
|
||||
if (password.length >= 8) strength++;
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
||||
|
||||
switch (strength) {
|
||||
case 0:
|
||||
case 1:
|
||||
feedback = '<span style="color: #ef4444;">Weak</span>';
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
feedback = '<span style="color: #f59e0b;">Medium</span>';
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
feedback = '<span style="color: #10b981;">Strong</span>';
|
||||
break;
|
||||
}
|
||||
|
||||
strengthDiv.innerHTML = password.length > 0 ? 'Strength: ' + feedback : '';
|
||||
});
|
||||
|
||||
// Password match indicator
|
||||
function checkPasswordMatch() {
|
||||
const password = document.getElementById('new_password').value;
|
||||
const confirm = document.getElementById('confirm_password').value;
|
||||
const matchDiv = document.getElementById('passwordMatch');
|
||||
|
||||
if (confirm.length === 0) {
|
||||
matchDiv.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (password === confirm) {
|
||||
matchDiv.innerHTML = '<span style="color: #10b981;">✓ Passwords match</span>';
|
||||
} else {
|
||||
matchDiv.innerHTML = '<span style="color: #ef4444;">✗ Passwords do not match</span>';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('new_password').addEventListener('input', checkPasswordMatch);
|
||||
document.getElementById('confirm_password').addEventListener('input', checkPasswordMatch);
|
||||
|
||||
// Form validation
|
||||
document.getElementById('setupForm').addEventListener('submit', function(e) {
|
||||
const password = document.getElementById('new_password').value;
|
||||
const confirm = document.getElementById('confirm_password').value;
|
||||
|
||||
if (password !== confirm) {
|
||||
e.preventDefault();
|
||||
alert('Passwords do not match!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
e.preventDefault();
|
||||
alert('Password must be at least 6 characters long!');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue