You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
6.1 KiB
248 lines
6.1 KiB
<?php
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
function ensureConfigDirectory() {
|
|
if (!is_dir(CONFIG_PATH)) {
|
|
if (!mkdir(CONFIG_PATH, 0755, true) && !is_dir(CONFIG_PATH)) {
|
|
throw new RuntimeException('Unable to create config directory.');
|
|
}
|
|
}
|
|
}
|
|
|
|
function getConfigData() {
|
|
ensureConfigDirectory();
|
|
|
|
if (!file_exists(CONFIG_FILE)) {
|
|
return ['defaultModel' => '', 'users' => []];
|
|
}
|
|
|
|
$content = @file_get_contents(CONFIG_FILE);
|
|
if ($content === false) {
|
|
return ['defaultModel' => '', 'users' => []];
|
|
}
|
|
|
|
$data = json_decode($content, true);
|
|
if (!is_array($data)) {
|
|
return ['defaultModel' => '', 'users' => []];
|
|
}
|
|
|
|
if (!isset($data['users']) || !is_array($data['users'])) {
|
|
$data['users'] = [];
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
function saveConfigData($config) {
|
|
ensureConfigDirectory();
|
|
|
|
$data = getConfigData();
|
|
if (array_key_exists('defaultModel', $config)) {
|
|
$data['defaultModel'] = (string) $config['defaultModel'];
|
|
}
|
|
|
|
if (array_key_exists('users', $config)) {
|
|
$data['users'] = is_array($config['users']) ? $config['users'] : [];
|
|
}
|
|
|
|
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
return file_put_contents(CONFIG_FILE, $json) !== false;
|
|
}
|
|
|
|
function readConfig() {
|
|
$data = getConfigData();
|
|
|
|
return [
|
|
'defaultModel' => isset($data['defaultModel']) ? (string) $data['defaultModel'] : '',
|
|
'users' => isset($data['users']) && is_array($data['users']) ? $data['users'] : []
|
|
];
|
|
}
|
|
|
|
function writeConfig($config) {
|
|
return saveConfigData($config);
|
|
}
|
|
|
|
function hashPassword($password) {
|
|
return password_hash($password, PASSWORD_DEFAULT);
|
|
}
|
|
|
|
function verifyPassword($password, $hash) {
|
|
return password_verify($password, $hash);
|
|
}
|
|
|
|
function loadUsers() {
|
|
$config = readConfig();
|
|
return $config['users'];
|
|
}
|
|
|
|
function saveUsers($users) {
|
|
return writeConfig(['users' => $users]);
|
|
}
|
|
|
|
function findUserByUsername($username) {
|
|
$username = trim((string) $username);
|
|
if ($username === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (loadUsers() as $user) {
|
|
if (($user['username'] ?? '') === $username) {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function findUserByEmail($email) {
|
|
$email = trim((string) $email);
|
|
if ($email === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (loadUsers() as $user) {
|
|
if (strtolower((string) ($user['email'] ?? '')) === strtolower($email)) {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function createUser($username, $password, $role = 'user', $email = '') {
|
|
$username = trim((string) $username);
|
|
$email = trim((string) $email);
|
|
if ($username === '' || $password === '') {
|
|
return false;
|
|
}
|
|
|
|
$users = loadUsers();
|
|
if (findUserByUsername($username) !== null) {
|
|
return false;
|
|
}
|
|
|
|
if ($email !== '' && findUserByEmail($email) !== null) {
|
|
return false;
|
|
}
|
|
|
|
$users[] = [
|
|
'username' => $username,
|
|
'email' => $email,
|
|
'passwordHash' => hashPassword($password),
|
|
'role' => $role
|
|
];
|
|
|
|
return saveUsers($users);
|
|
}
|
|
|
|
function requestPasswordReset($email) {
|
|
$email = trim((string) $email);
|
|
if ($email === '') {
|
|
return false;
|
|
}
|
|
|
|
$user = findUserByEmail($email);
|
|
if ($user === null) {
|
|
return false;
|
|
}
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
$users = loadUsers();
|
|
foreach ($users as &$entry) {
|
|
if (($entry['username'] ?? '') === ($user['username'] ?? '')) {
|
|
$entry['resetTokenHash'] = password_hash($token, PASSWORD_DEFAULT);
|
|
$entry['resetTokenExpires'] = time() + 3600;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!saveUsers($users)) {
|
|
return false;
|
|
}
|
|
|
|
$link = APP_BASE_URL . '/reset.php?token=' . urlencode($token);
|
|
$subject = 'Password reset for KaI';
|
|
$message = "Hello {$user['username']},\n\n"
|
|
. "You requested a password reset for your KaI account.\n"
|
|
. "Please use the following link to set a new password:\n\n"
|
|
. $link . "\n\n"
|
|
. "If you did not request this, you can ignore this email.";
|
|
$headers = "From: " . MAIL_FROM_NAME . " <" . MAIL_FROM . ">\r\n"
|
|
. "Reply-To: " . MAIL_FROM . "\r\n"
|
|
. "X-Mailer: PHP/" . phpversion();
|
|
|
|
return mail($email, $subject, $message, $headers);
|
|
}
|
|
|
|
function validatePasswordResetToken($token) {
|
|
$token = (string) $token;
|
|
if ($token === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (loadUsers() as $user) {
|
|
$expiresAt = (int) ($user['resetTokenExpires'] ?? 0);
|
|
if ($expiresAt > time() && !empty($user['resetTokenHash']) && password_verify($token, (string) $user['resetTokenHash'])) {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function completePasswordReset($token, $newPassword) {
|
|
$token = (string) $token;
|
|
$newPassword = (string) $newPassword;
|
|
if ($token === '' || $newPassword === '') {
|
|
return false;
|
|
}
|
|
|
|
$users = loadUsers();
|
|
foreach ($users as &$entry) {
|
|
$expiresAt = (int) ($entry['resetTokenExpires'] ?? 0);
|
|
if ($expiresAt > time() && !empty($entry['resetTokenHash']) && password_verify($token, (string) $entry['resetTokenHash'])) {
|
|
$entry['passwordHash'] = hashPassword($newPassword);
|
|
unset($entry['resetTokenHash'], $entry['resetTokenExpires']);
|
|
return saveUsers($users);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isLoggedIn() {
|
|
return !empty($_SESSION['auth_user']['username']);
|
|
}
|
|
|
|
function getAuthenticatedUser() {
|
|
if (!isLoggedIn()) {
|
|
return null;
|
|
}
|
|
|
|
return $_SESSION['auth_user'];
|
|
}
|
|
|
|
function requireAuthentication() {
|
|
if (!isLoggedIn()) {
|
|
http_response_code(403);
|
|
echo json_encode(['error' => 'Authentication required.']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function requireAdmin() {
|
|
$user = getAuthenticatedUser();
|
|
if (!$user || (($user['role'] ?? 'user') !== 'admin')) {
|
|
http_response_code(403);
|
|
echo json_encode(['error' => 'Admin access required.']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function logoutUser() {
|
|
unset($_SESSION['auth_user']);
|
|
}
|