Raiju/admin/api/users.php
2026-10-25 06:05:42 +03:00

243 lines
8.7 KiB
PHP

<?php
if (!defined('RAIJU_ADMIN_DIR')) { http_response_code(404); exit; }
// Authenticated dispatcher supplies shared helpers and request context.
switch ($action) {
case 'change-password':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$newPasswordConfirm = $_POST['new_password_confirm'] ?? '';
if (empty($currentPassword) || empty($newPassword) || empty($newPasswordConfirm)) {
jsonResponse(false, null, 'All fields are required');
}
$userId = $_SESSION['admin_user_id'] ?? '';
$currentUser = findUserById($userId);
if (!$currentUser) {
jsonResponse(false, null, 'User not found');
}
if (!password_verify($currentPassword, $currentUser['passwordHash'])) {
jsonResponse(false, null, 'Current password is incorrect');
}
if ($newPassword !== $newPasswordConfirm) {
jsonResponse(false, null, 'New passwords do not match');
}
if ($currentPassword === $newPassword) {
jsonResponse(false, null, 'New password must be different from current password');
}
// Password strength check
if (strlen($newPassword) < 8 ||
!preg_match('/[A-Z]/', $newPassword) ||
!preg_match('/[a-z]/', $newPassword) ||
!preg_match('/[0-9]/', $newPassword) ||
!preg_match('/[^A-Za-z0-9]/', $newPassword)) {
jsonResponse(false, null, 'Password does not meet requirements: at least 8 characters with uppercase, lowercase, digits, and special characters');
}
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
if (!updateUserPassword($userId, $newHash)) {
jsonResponse(false, null, 'Could not update password');
}
// Keep this session valid; other sessions must authenticate again.
$_SESSION['admin_password_fingerprint'] = hash('sha256', $newHash);
session_regenerate_id(true);
// Clear password warning
unset($_SESSION['password_warning']);
jsonResponse(true, null, 'Password changed successfully');
break;
// ============================================================
// SETTINGS
// ============================================================
case 'list-users':
if (!isAdmin()) {
jsonResponse(false, null, 'Forbidden');
}
jsonResponse(true, getUsersForApi());
break;
case 'create-user':
if (!isAdmin()) {
jsonResponse(false, null, 'Forbidden');
}
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$newUsername = trim($_POST['username'] ?? '');
$newEmail = trim($_POST['email'] ?? '');
$newRole = $_POST['role'] ?? 'editor';
$newPw = $_POST['password'] ?? '';
if (empty($newUsername) || strlen($newUsername) < 3) {
jsonResponse(false, null, 'Username must be at least 3 characters');
}
if (findUserByUsername($newUsername)) {
jsonResponse(false, null, 'Username already exists');
}
if (!empty($newEmail) && findUserByEmail($newEmail)) {
jsonResponse(false, null, 'Email already in use');
}
if (empty($newPw)) {
jsonResponse(false, null, 'Password is required');
}
if (strlen($newPw) < 8 ||
!preg_match('/[A-Z]/', $newPw) ||
!preg_match('/[a-z]/', $newPw) ||
!preg_match('/[0-9]/', $newPw) ||
!preg_match('/[^A-Za-z0-9]/', $newPw)) {
jsonResponse(false, null, 'Password does not meet requirements');
}
$createdBy = $_SESSION['admin_username'] ?? 'admin';
try {
$newUser = createUser($newUsername, $newEmail, $newPw, $newRole, $createdBy);
} catch (RuntimeException $error) {
jsonResponse(false, null, $error->getMessage());
}
jsonResponse(true, [
'id' => $newUser['id'],
'username' => $newUser['username'],
'email' => $newUser['email'],
'role' => $newUser['role'],
], 'User created');
break;
case 'update-user':
if (!isAdmin()) {
jsonResponse(false, null, 'Forbidden');
}
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$editUserId = $_POST['user_id'] ?? '';
$editUser = findUserById($editUserId);
if (!$editUser) {
jsonResponse(false, null, 'User not found');
}
$fields = [];
if (isset($_POST['username'])) {
$uname = trim($_POST['username']);
if (strlen($uname) < 3) {
jsonResponse(false, null, 'Username must be at least 3 characters');
}
$existing = findUserByUsername($uname);
if ($existing && $existing['id'] !== $editUserId) {
jsonResponse(false, null, 'Username already exists');
}
$fields['username'] = $uname;
}
if (isset($_POST['email'])) {
$uemail = trim($_POST['email']);
if (!empty($uemail)) {
$existing = findUserByEmail($uemail);
if ($existing && $existing['id'] !== $editUserId) {
jsonResponse(false, null, 'Email already in use');
}
}
$fields['email'] = $uemail;
}
if (isset($_POST['role'])) {
$newRole = $_POST['role'];
if (!in_array($newRole, ['admin', 'editor'])) {
jsonResponse(false, null, 'Invalid role');
}
// Prevent demoting the last admin
if ($editUser['role'] === 'admin' && $newRole === 'editor' && countUsersByRole('admin') <= 1) {
jsonResponse(false, null, 'Cannot demote the last admin');
}
$fields['role'] = $newRole;
}
if (!empty($fields)) {
if (!updateUser($editUserId, $fields)) jsonResponse(false, null, 'Could not update user');
// Update session if editing self
if ($editUserId === ($_SESSION['admin_user_id'] ?? '')) {
if (isset($fields['username'])) $_SESSION['admin_username'] = $fields['username'];
if (isset($fields['role'])) $_SESSION['admin_role'] = $fields['role'];
}
}
jsonResponse(true, null, 'User updated');
break;
case 'delete-user':
if (!isAdmin()) {
jsonResponse(false, null, 'Forbidden');
}
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$delUserId = $_POST['user_id'] ?? '';
if ($delUserId === ($_SESSION['admin_user_id'] ?? '')) {
jsonResponse(false, null, 'Cannot delete yourself');
}
$delUser = findUserById($delUserId);
if (!$delUser) {
jsonResponse(false, null, 'User not found');
}
if ($delUser['role'] === 'admin' && countUsersByRole('admin') <= 1) {
jsonResponse(false, null, 'Cannot delete the last admin');
}
if (!deleteUser($delUserId)) jsonResponse(false, null, 'Could not delete user');
jsonResponse(true, null, 'User deleted');
break;
case 'admin-reset-password':
if (!isAdmin()) {
jsonResponse(false, null, 'Forbidden');
}
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$resetUserId = $_POST['user_id'] ?? '';
$resetNewPw = $_POST['password'] ?? '';
$resetUser = findUserById($resetUserId);
if (!$resetUser) {
jsonResponse(false, null, 'User not found');
}
if (empty($resetNewPw)) {
jsonResponse(false, null, 'Password is required');
}
if (strlen($resetNewPw) < 8 ||
!preg_match('/[A-Z]/', $resetNewPw) ||
!preg_match('/[a-z]/', $resetNewPw) ||
!preg_match('/[0-9]/', $resetNewPw) ||
!preg_match('/[^A-Za-z0-9]/', $resetNewPw)) {
jsonResponse(false, null, 'Password does not meet requirements');
}
$resetHash = password_hash($resetNewPw, PASSWORD_DEFAULT);
if (!updateUserPassword($resetUserId, $resetHash)) jsonResponse(false, null, 'Could not save password');
jsonResponse(true, null, 'Password reset successfully');
break;
// ============================================================
// MENU ORDER
// ============================================================
}