This commit is contained in:
2025-12-07 02:49:46 +01:00
parent 37318e69fb
commit 6414802ce8
7 changed files with 1190 additions and 9 deletions

View File

@@ -770,6 +770,39 @@ class ApiKernel
$this->authService->logout();
$this->respond(['ok' => true]);
break;
case 'account.profile.get':
$this->handleAccountProfileGet();
break;
case 'account.profile.update':
$this->handleAccountProfileUpdate();
break;
case 'account.password.update':
$this->handleAccountPasswordUpdate();
break;
case 'account.settings.get':
$this->handleAccountSettingsGet();
break;
case 'account.settings.update':
$this->handleAccountSettingsUpdate();
break;
case 'account.users.list':
$this->handleAccountUsersList();
break;
case 'account.users.create':
$this->handleAccountUsersCreate();
break;
case 'account.users.update':
$this->handleAccountUsersUpdate();
break;
case 'account.users.delete':
$this->handleAccountUsersDelete();
break;
case 'downloads.bridge':
$this->handleDownloadFile('bridge');
break;
case 'downloads.sender':
$this->handleDownloadFile('sender');
break;
case 'placeholders.schema':
$this->handlePlaceholderSchema();
break;
@@ -1030,8 +1063,9 @@ class ApiKernel
private function handlePlaceholderSchema(): void
{
$this->requireAuth();
$bridge = $this->conf['placeholders']['bridge'] ?? [];
$user = $this->authService->requireAuth();
$customerId = (int)($user['customer_id'] ?? 0);
$bridge = $this->resolveBridgeConfig($customerId);
$url = trim((string)($bridge['url'] ?? ''));
$token = trim((string)($bridge['token'] ?? ''));
if ($url === '' || $token === '') {
@@ -1097,4 +1131,603 @@ class ApiKernel
$hash = md5($url . '|' . $token);
return sys_get_temp_dir() . '/emailtemplate_placeholder_' . $hash . '.json';
}
// -----------------------------------------------------------------
// Account & User Management
// -----------------------------------------------------------------
private function handleAccountProfileGet(): void
{
$user = $this->authService->requireAuth();
$customerId = (int)($user['customer_id'] ?? 0);
$settings = $customerId ? $this->ensureSettingsTokens($customerId, $this->getCustomerSettings($customerId)) : [];
$this->respond([
'ok' => true,
'user' => $user,
'customer' => $user['customer'] ?? null,
'settings' => $settings,
]);
}
private function handleAccountProfileUpdate(): void
{
$user = $this->authService->requireAuth();
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$name = trim((string)($this->in['name'] ?? ''));
$email = trim(strtolower((string)($this->in['email'] ?? '')));
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->fail('Valid email required', null, 422);
}
if ($name === '') $this->fail('Name required', null, 422);
$userId = (int)($user['id'] ?? 0);
$customerId = (int)($user['customer_id'] ?? 0);
if (strtolower($email) !== strtolower((string)$user['email'])) {
$this->assertEmailUnique($email, $customerId, $userId);
}
$set = [];
$params = [':id' => $userId];
if ($this->columnExists($dbCols, $cols['col_name'])) {
$set[] = sprintf('`%s` = :name', $cols['col_name']);
$params[':name'] = $name;
}
if ($this->columnExists($dbCols, $cols['col_email'])) {
$set[] = sprintf('`%s` = :email', $cols['col_email']);
$params[':email'] = $email;
}
if (!$set) {
$this->fail('Profile update not supported', null, 500);
}
$sql = sprintf(
'UPDATE `%s` SET %s WHERE `%s` = :id LIMIT 1',
$table,
implode(',', $set),
$cols['col_id']
);
$stmt = $this->pdo->prepare($sql);
foreach ($params as $k => $v) $stmt->bindValue($k, $v);
$stmt->execute();
$_SESSION['auth']['name'] = $name;
$_SESSION['auth']['email'] = $email;
$this->respond(['ok' => true, 'user' => $_SESSION['auth']]);
}
private function handleAccountPasswordUpdate(): void
{
$user = $this->authService->requireAuth();
$current = (string)($this->in['current_password'] ?? '');
$new = (string)($this->in['new_password'] ?? '');
if ($current === '' || $new === '') {
$this->fail('Current and new password required', null, 422);
}
if (strlen($new) < 8) {
$this->fail('Password must be at least 8 characters', null, 422);
}
$cols = $this->authUserColumns();
$table = $cols['table'];
$sql = sprintf(
'SELECT `%1$s` FROM `%2$s` WHERE `%3$s` = :id LIMIT 1',
$cols['col_pass'],
$table,
$cols['col_id']
);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $user['id']]);
$row = $stmt->fetch();
if (!$row) $this->fail('User not found', null, 404);
$stored = (string)$row[$cols['col_pass']];
if (!$this->verifyUserPasswordValue($current, $stored)) {
$this->fail('Current password incorrect', null, 403);
}
$hash = $this->hashUserPassword($new);
$update = $this->pdo->prepare(
sprintf('UPDATE `%s` SET `%s` = :pwd WHERE `%s` = :id LIMIT 1', $table, $cols['col_pass'], $cols['col_id'])
);
$update->execute([':pwd' => $hash, ':id' => $user['id']]);
$this->respond(['ok' => true]);
}
private function handleAccountSettingsGet(): void
{
$user = $this->authService->requireAuth();
$this->ensureOwner($user);
$customerId = (int)($user['customer_id'] ?? 0);
$settings = $this->ensureSettingsTokens($customerId, $this->getCustomerSettings($customerId));
$this->respond(['ok' => true, 'settings' => $settings]);
}
private function handleAccountSettingsUpdate(): void
{
$user = $this->authService->requireAuth();
$this->ensureOwner($user);
$customerId = (int)($user['customer_id'] ?? 0);
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
$bridgeUrl = trim((string)($this->in['bridge_url'] ?? ''));
$bridgeToken = trim((string)($this->in['bridge_token'] ?? ''));
$senderToken = trim((string)($this->in['sender_token'] ?? ''));
$externalToken = trim((string)($this->in['external_api_token'] ?? ''));
$rotateBridge = !empty($this->in['rotate_bridge_token']);
$rotateSender = !empty($this->in['rotate_sender_token']);
$rotateExternal = !empty($this->in['rotate_external_token']);
if ($bridgeUrl && !filter_var($bridgeUrl, FILTER_VALIDATE_URL)) {
$this->fail('Ungültige Bridge-URL', null, 422);
}
$settings = $this->getCustomerSettings($customerId);
if ($rotateBridge || $bridgeToken === '') $bridgeToken = $this->generateToken();
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
if ($rotateExternal || $externalToken === '') $externalToken = $this->generateToken();
$settings = $this->saveCustomerSettings($customerId, [
'bridge_url' => $bridgeUrl,
'bridge_token' => $bridgeToken,
'sender_token' => $senderToken,
'external_api_token' => $externalToken,
]);
$this->respond(['ok' => true, 'settings' => $settings]);
}
private function handleAccountUsersList(): void
{
$user = $this->authService->requireAuth();
$this->ensureOwner($user);
$customerId = (int)($user['customer_id'] ?? 0);
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$select = [
sprintf('`%s` AS user_id', $cols['col_id']),
sprintf('`%s` AS name', $cols['col_name']),
sprintf('`%s` AS email', $cols['col_email']),
];
if ($this->columnExists($dbCols, $cols['col_role'])) {
$select[] = sprintf('`%s` AS role', $cols['col_role']);
} else {
$select[] = "'user' AS role";
}
if ($this->columnExists($dbCols, $cols['col_status'])) {
$select[] = sprintf('`%s` AS is_active', $cols['col_status']);
} else {
$select[] = '1 AS is_active';
}
if ($this->columnExists($dbCols, 'created_at')) $select[] = '`created_at`';
if ($this->columnExists($dbCols, 'updated_at')) $select[] = '`updated_at`';
$sql = sprintf(
'SELECT %s FROM `%s` WHERE `%s` = :cid ORDER BY `%s` ASC',
implode(',', $select),
$table,
$cols['col_customer'],
$cols['col_name']
);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':cid' => $customerId]);
$items = [];
while ($row = $stmt->fetch()) {
$items[] = $this->formatUserOutput($row);
}
$this->respond(['ok' => true, 'items' => $items]);
}
private function handleAccountUsersCreate(): void
{
$owner = $this->authService->requireAuth();
$this->ensureOwner($owner);
$customerId = (int)($owner['customer_id'] ?? 0);
$name = trim((string)($this->in['name'] ?? ''));
$email = trim(strtolower((string)($this->in['email'] ?? '')));
$role = $this->sanitizeRole((string)($this->in['role'] ?? 'user'));
if ($name === '' || $email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->fail('Name und gültige E-Mail sind erforderlich', null, 422);
}
$this->assertEmailUnique($email, $customerId, null);
$password = $this->generateReadablePassword();
$hash = $this->hashUserPassword($password);
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$data = [];
$data[$cols['col_name']] = $name;
$data[$cols['col_email']] = $email;
$data[$cols['col_pass']] = $hash;
if ($this->columnExists($dbCols, $cols['col_role'])) $data[$cols['col_role']] = $role;
if ($this->columnExists($dbCols, $cols['col_status'])) $data[$cols['col_status']] = 1;
if ($this->columnExists($dbCols, $cols['col_customer'])) $data[$cols['col_customer']] = $customerId;
if ($this->columnExists($dbCols, 'created_at')) $data['created_at'] = date('Y-m-d H:i:s');
if ($this->columnExists($dbCols, 'updated_at')) $data['updated_at'] = date('Y-m-d H:i:s');
$columns = array_keys($data);
$insertCols = implode(',', array_map(fn($c) => "`$c`", $columns));
$placeholders = implode(',', array_map(fn($c) => ":$c", $columns));
$sql = sprintf('INSERT INTO `%s` (%s) VALUES (%s)', $table, $insertCols, $placeholders);
$stmt = $this->pdo->prepare($sql);
foreach ($data as $col => $value) $stmt->bindValue(":$col", $value);
$stmt->execute();
$newId = (int)$this->pdo->lastInsertId();
$newUser = $this->fetchUserRow($newId, $customerId);
$this->respond(['ok' => true, 'user' => $newUser, 'temp_password' => $password]);
}
private function handleAccountUsersUpdate(): void
{
$owner = $this->authService->requireAuth();
$this->ensureOwner($owner);
$customerId = (int)($owner['customer_id'] ?? 0);
$userId = (int)($this->in['user_id'] ?? 0);
if ($userId <= 0) $this->fail('Ungültige Nutzer-ID', null, 422);
$target = $this->fetchUserRow($userId, $customerId);
if (!$target) $this->fail('Nutzer nicht gefunden', null, 404);
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$set = [];
$params = [':id' => $userId];
$name = trim((string)($this->in['name'] ?? $target['name']));
$email = trim(strtolower((string)($this->in['email'] ?? $target['email'])));
$role = $this->sanitizeRole((string)($this->in['role'] ?? $target['role']));
$isActive = isset($this->in['is_active']) ? (int)(bool)$this->in['is_active'] : (int)$target['is_active'];
$resetPassword = !empty($this->in['reset_password']);
if ($name === '' || $email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->fail('Name und gültige E-Mail sind erforderlich', null, 422);
}
if (strtolower($email) !== strtolower($target['email'])) {
$this->assertEmailUnique($email, $customerId, $userId);
}
if ($this->columnExists($dbCols, $cols['col_name'])) {
$set[] = sprintf('`%s` = :name', $cols['col_name']);
$params[':name'] = $name;
}
if ($this->columnExists($dbCols, $cols['col_email'])) {
$set[] = sprintf('`%s` = :email', $cols['col_email']);
$params[':email'] = $email;
}
if ($this->columnExists($dbCols, $cols['col_role'])) {
if ($target['role'] === 'owner' && $role !== 'owner' && $this->countOwners($customerId, $userId) < 1) {
$this->fail('Mindestens ein Owner muss bestehen bleiben', null, 422);
}
$set[] = sprintf('`%s` = :role', $cols['col_role']);
$params[':role'] = $role;
}
if ($this->columnExists($dbCols, $cols['col_status'])) {
if ($target['role'] === 'owner' && !$isActive && $this->countOwners($customerId, $userId) < 1) {
$this->fail('Mindestens ein Owner muss aktiv bleiben', null, 422);
}
$set[] = sprintf('`%s` = :status', $cols['col_status']);
$params[':status'] = $isActive;
}
$tempPassword = null;
if ($resetPassword) {
$tempPassword = $this->generateReadablePassword();
$hash = $this->hashUserPassword($tempPassword);
$set[] = sprintf('`%s` = :pwd', $cols['col_pass']);
$params[':pwd'] = $hash;
}
if ($this->columnExists($dbCols, 'updated_at')) {
$set[] = '`updated_at` = :updated_at';
$params[':updated_at'] = date('Y-m-d H:i:s');
}
if (!$set) $this->fail('Keine Änderungen erkannt', null, 422);
$sql = sprintf('UPDATE `%s` SET %s WHERE `%s` = :id LIMIT 1', $table, implode(',', $set), $cols['col_id']);
$stmt = $this->pdo->prepare($sql);
foreach ($params as $k => $v) $stmt->bindValue($k, $v);
$stmt->execute();
$updated = $this->fetchUserRow($userId, $customerId);
$resp = ['ok' => true, 'user' => $updated];
if ($tempPassword !== null) $resp['temp_password'] = $tempPassword;
$this->respond($resp);
}
private function handleAccountUsersDelete(): void
{
$owner = $this->authService->requireAuth();
$this->ensureOwner($owner);
$customerId = (int)($owner['customer_id'] ?? 0);
$userId = (int)($this->in['user_id'] ?? 0);
if ($userId <= 0) $this->fail('Ungültige Nutzer-ID', null, 422);
if ($userId === (int)($owner['id'] ?? 0)) $this->fail('Du kannst dich nicht selbst löschen', null, 422);
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$target = $this->fetchUserRow($userId, $customerId);
if (!$target) $this->fail('Nutzer nicht gefunden', null, 404);
if ($target['role'] === 'owner' && $this->countOwners($customerId, $userId) < 1) {
$this->fail('Mindestens ein Owner muss bestehen bleiben', null, 422);
}
if ($this->columnExists($dbCols, $cols['col_status'])) {
$sql = sprintf('UPDATE `%s` SET `%s` = 0 WHERE `%s` = :id LIMIT 1', $table, $cols['col_status'], $cols['col_id']);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $userId]);
} else {
$sql = sprintf('DELETE FROM `%s` WHERE `%s` = :id LIMIT 1', $table, $cols['col_id']);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $userId]);
}
$this->respond(['ok' => true, 'deleted' => true]);
}
private function handleDownloadFile(string $type): void
{
$user = $this->authService->requireAuth();
$this->ensureOwner($user);
$customerId = (int)($user['customer_id'] ?? 0);
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
$settings = $this->ensureSettingsTokens($customerId, $this->getCustomerSettings($customerId));
$baseDir = dirname(__DIR__);
if ($type === 'bridge') {
$path = $baseDir . '/download/emailtemplate_bridge.php';
} elseif ($type === 'sender') {
$path = $baseDir . '/download/emailtemplate_sender.php';
} else {
$this->fail('Unknown download type', $type, 404);
}
if (!is_file($path)) {
$this->fail('Datei nicht gefunden', basename($path), 404);
}
$content = (string)file_get_contents($path);
if ($type === 'bridge') {
$content = str_replace('REPLACE_WITH_SHARED_TOKEN', $settings['bridge_token'] ?? '', $content);
} else {
$apiBase = $this->defaultApiBase();
$content = str_replace('REPLACE_WITH_SHARED_TOKEN', $settings['sender_token'] ?? '', $content);
$content = str_replace('REPLACE_WITH_TEMPLATE_API_TOKEN', $settings['external_api_token'] ?? '', $content);
if ($apiBase) {
$content = str_replace('https://api.emailtemplate.it/external/render', $apiBase, $content);
}
}
$this->respond([
'ok' => true,
'file_name' => basename($path),
'content' => base64_encode($content),
]);
}
private function resolveBridgeConfig(?int $customerId): array
{
$fileConf = $this->conf['placeholders']['bridge'] ?? [];
$settings = $customerId ? $this->getCustomerSettings($customerId) : [];
$url = $settings['bridge_url'] ?? ($fileConf['url'] ?? '');
$token = $settings['bridge_token'] ?? ($fileConf['token'] ?? '');
$ttl = $fileConf['cache_ttl'] ?? 300;
return ['url' => $url, 'token' => $token, 'cache_ttl' => $ttl];
}
private function getCustomerSettings(int $customerId): array
{
if ($customerId <= 0) return [];
$table = $this->customerSettingsTable();
$stmt = $this->pdo->prepare("SELECT * FROM `$table` WHERE `customer_id` = :id LIMIT 1");
$stmt->execute([':id' => $customerId]);
$row = $stmt->fetch();
return $row ?: [];
}
private function saveCustomerSettings(int $customerId, array $data): array
{
if ($customerId <= 0) return [];
$allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token'];
$fields = array_intersect_key($data, array_flip($allowed));
if (!$fields) return $this->getCustomerSettings($customerId);
$fields['customer_id'] = $customerId;
$columns = array_keys($fields);
$insertCols = implode(',', array_map(fn($c) => "`$c`", $columns));
$placeholders = implode(',', array_map(fn($c) => ":$c", $columns));
$updates = [];
foreach ($columns as $col) {
if ($col === 'customer_id') continue;
$updates[] = "`$col` = VALUES(`$col`)";
}
$table = $this->customerSettingsTable();
$sql = "INSERT INTO `$table` ($insertCols) VALUES ($placeholders) ON DUPLICATE KEY UPDATE " . implode(',', $updates);
$stmt = $this->pdo->prepare($sql);
foreach ($fields as $col => $value) {
$stmt->bindValue(":$col", $value);
}
$stmt->execute();
return $this->getCustomerSettings($customerId);
}
private function ensureSettingsTokens(int $customerId, array $settings): array
{
if ($customerId <= 0) return $settings;
$changed = false;
foreach (['bridge_token', 'sender_token', 'external_api_token'] as $key) {
if (empty($settings[$key])) {
$settings[$key] = $this->generateToken();
$changed = true;
}
}
if ($changed) {
$settings = $this->saveCustomerSettings($customerId, $settings);
}
return $settings;
}
private function customerSettingsTable(): string
{
return 'emailtemplate_customer_settings';
}
private function generateToken(int $length = 48): string
{
return rtrim(strtr(base64_encode(random_bytes($length)), '+/', '-_'), '=');
}
private function generateReadablePassword(int $length = 12): string
{
$bytes = bin2hex(random_bytes($length));
return substr($bytes, 0, $length);
}
private function authUserColumns(): array
{
$db = $this->conf['auth']['db'] ?? [];
return [
'table' => $db['table'] ?? 'customer_users',
'col_id' => $db['col_id'] ?? 'id',
'col_email' => $db['col_user'] ?? 'email',
'col_pass' => $db['col_pass'] ?? 'password_hash',
'col_name' => $db['col_name'] ?? 'name',
'col_role' => $db['col_role'] ?? 'role',
'col_status' => $db['col_status'] ?? 'is_active',
'col_customer' => $db['customer_fk'] ?? 'customer_id',
];
}
private function columnExists(array $columns, string $name): bool
{
if ($name === '') return false;
return in_array($name, $columns, true);
}
private function sanitizeRole(string $role): string
{
$role = strtolower($role);
$valid = ['owner', 'admin', 'editor', 'viewer'];
return in_array($role, $valid, true) ? $role : 'user';
}
private function assertEmailUnique(string $email, int $customerId, ?int $excludeId): void
{
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$conditions = [sprintf('`%s` = :email', $cols['col_email'])];
if ($this->columnExists($dbCols, $cols['col_customer'])) {
$conditions[] = sprintf('`%s` = :cid', $cols['col_customer']);
}
if ($excludeId) {
$conditions[] = sprintf('`%s` != :exclude', $cols['col_id']);
}
$sql = sprintf('SELECT COUNT(*) FROM `%s` WHERE %s', $table, implode(' AND ', $conditions));
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':email', $email);
if ($this->columnExists($dbCols, $cols['col_customer'])) {
$stmt->bindValue(':cid', $customerId);
}
if ($excludeId) $stmt->bindValue(':exclude', $excludeId, PDO::PARAM_INT);
$stmt->execute();
if ((int)$stmt->fetchColumn() > 0) {
$this->fail('E-Mail-Adresse ist bereits vergeben', null, 422);
}
}
private function fetchUserRow(int $userId, int $customerId): array
{
$cols = $this->authUserColumns();
$table = $cols['table'];
$sql = sprintf(
'SELECT * FROM `%s` WHERE `%s` = :id AND `%s` = :cid LIMIT 1',
$table,
$cols['col_id'],
$cols['col_customer']
);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $userId, ':cid' => $customerId]);
$row = $stmt->fetch();
if (!$row) $this->fail('Nutzer nicht gefunden', null, 404);
return $this->formatUserOutput([
'user_id' => $row[$cols['col_id']],
'name' => $row[$cols['col_name']] ?? '',
'email' => $row[$cols['col_email']] ?? '',
'role' => $row[$cols['col_role']] ?? 'user',
'is_active' => $row[$cols['col_status']] ?? 1,
'created_at' => $row['created_at'] ?? null,
'updated_at' => $row['updated_at'] ?? null,
]);
}
private function formatUserOutput(array $row): array
{
return [
'id' => (int)($row['user_id'] ?? $row['id'] ?? 0),
'name' => $row['name'] ?? '',
'email' => $row['email'] ?? '',
'role' => $row['role'] ?? 'user',
'is_active' => (int)($row['is_active'] ?? 1),
'created_at' => $row['created_at'] ?? null,
'updated_at' => $row['updated_at'] ?? null,
];
}
private function countOwners(int $customerId, ?int $excludeId = null): int
{
$cols = $this->authUserColumns();
$table = $cols['table'];
$dbCols = $this->tableColumns($table);
$conditions = [
sprintf('`%s` = :cid', $cols['col_customer']),
sprintf('`%s` = :role', $cols['col_role']),
];
if ($this->columnExists($dbCols, $cols['col_status'])) {
$conditions[] = sprintf('`%s` = 1', $cols['col_status']);
}
if ($excludeId) {
$conditions[] = sprintf('`%s` != :exclude', $cols['col_id']);
}
$sql = sprintf('SELECT COUNT(*) FROM `%s` WHERE %s', $table, implode(' AND ', $conditions));
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':cid', $customerId);
$stmt->bindValue(':role', 'owner');
if ($excludeId) $stmt->bindValue(':exclude', $excludeId, PDO::PARAM_INT);
$stmt->execute();
return (int)$stmt->fetchColumn();
}
private function verifyUserPasswordValue(string $input, string $stored): bool
{
if (preg_match('~^\$2[aby]\$~', $stored) || strpos($stored, '$argon2') === 0) return password_verify($input, $stored);
$legacy = strtolower($this->conf['auth']['db']['legacy'] ?? '');
if ($legacy === 'md5') return hash_equals($stored, md5($input));
if ($legacy === 'sha1') return hash_equals($stored, sha1($input));
if (password_get_info($stored)['algo'] !== 0) return password_verify($input, $stored);
return hash_equals($stored, $input);
}
private function hashUserPassword(string $password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
private function ensureOwner(array $user): void
{
if (($user['role'] ?? '') !== 'owner') {
$this->fail('Nur Owner dürfen diese Aktion ausführen', null, 403);
}
}
private function defaultApiBase(): string
{
$base = $this->conf['base_url'] ?? '';
return $base ? rtrim($base, '/') . '/api.php' : '/api.php';
}
}

View File

@@ -67,6 +67,9 @@ class AuthService
$colName = $authDb['col_name'] ?? 'name';
$colId = $authDb['col_id'] ?? 'id';
$colStatus = $authDb['col_status']?? null;
$colRole = $authDb['col_role'] ?? 'role';
$colCustomer = $authDb['customer_fk'] ?? 'customer_id';
$customerTable = $authDb['customer_table'] ?? null;
$activeValues = $authDb['active_values'] ?? ['active','1',1];
$table = $authDb['table'] ?? 'emailtemplate_users';
@@ -92,14 +95,38 @@ class AuthService
$this->fail('Invalid credentials', null, 401);
}
$customerId = isset($row[$colCustomer]) ? (int)$row[$colCustomer] : null;
$customerData = $customerId ? $this->fetchCustomerData($customerId, $customerTable, $authDb) : null;
$_SESSION['auth'] = [
'id' => $row[$colId] ?? null,
'name' => $row[$colName] ?? ($row[$colUser] ?? $identifier),
'email' => $row[$colUser] ?? $identifier,
'at' => time(),
'id' => $row[$colId] ?? null,
'name' => $row[$colName] ?? ($row[$colUser] ?? $identifier),
'email' => $row[$colUser] ?? $identifier,
'role' => $row[$colRole] ?? 'user',
'customer_id' => $customerId,
'customer' => $customerData,
'permissions' => [
'owner' => ($row[$colRole] ?? '') === 'owner',
],
'at' => time(),
];
$token = base64_encode(hash('sha256', ($_SESSION['auth']['id'] ?? $identifier).'|'.session_id(), true));
return ['user'=>$_SESSION['auth'], 'token'=>$token];
}
private function fetchCustomerData(?int $customerId, ?string $table, array $authDb): ?array
{
if (!$customerId || !$table) return null;
$cols = $authDb['customer_cols'] ?? [];
$select = ['`id`'];
foreach ($cols as $alias => $column) {
$select[] = sprintf('`%s` AS `%s`', $column, $alias);
}
$sql = sprintf('SELECT %s FROM `%s` WHERE `id` = :id LIMIT 1', implode(',', $select), $table);
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $customerId]);
$row = $stmt->fetch();
return $row ?: null;
}
}