133 lines
5.4 KiB
PHP
133 lines
5.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
// -----------------------------------------------------------------
|
|
// AuthService.php: Kapselt die gesamte Authentifizierungslogik.
|
|
// -----------------------------------------------------------------
|
|
|
|
class AuthService
|
|
{
|
|
private array $conf;
|
|
private PDO $pdo;
|
|
|
|
// Abhängigkeiten (Konfiguration und PDO) werden per Konstruktor übergeben
|
|
public function __construct(array $conf, PDO $pdo)
|
|
{
|
|
$this->conf = $conf;
|
|
$this->pdo = $pdo;
|
|
}
|
|
|
|
// --- Private Utility Methoden ---
|
|
|
|
private function fail(string $msg, $detail = null, int $code = 400): void
|
|
{
|
|
// Wir müssen hier direkt antworten, da wir das Fail-Verhalten des Kernels benötigen.
|
|
// Im ApiKernel werden wir die respond/fail-Methoden als public lassen,
|
|
// um sie hier injizieren zu können, oder wir lassen sie hier im Global Scope
|
|
// (WENN Sie die ursprünglichen globalen Funktionen respond/fail wieder zulassen).
|
|
// Für eine saubere Kapselung injizieren wir die Respond-Logik.
|
|
// HIER verwenden wir eine einfache JSON-Antwort, da die fail-Methode
|
|
// normalerweise den gesamten Kernel stoppt. Wir nutzen exit.
|
|
|
|
http_response_code($code);
|
|
echo json_encode(['ok'=>false,'error'=>$msg,'detail'=>$detail], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
private function verifyPassword(string $input, string $stored, array $authDbConf): bool
|
|
{
|
|
if (preg_match('~^\$2[aby]\$~', $stored) || strpos($stored, '$argon2') === 0) return password_verify($input, $stored);
|
|
$legacy = strtolower($authDbConf['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);
|
|
}
|
|
|
|
// --- Public Service Methoden ---
|
|
|
|
public function requireAuth(): array
|
|
{
|
|
if (empty($_SESSION['auth'])) $this->fail('Not authenticated', null, 401);
|
|
return $_SESSION['auth'];
|
|
}
|
|
|
|
public function logout(): bool
|
|
{
|
|
$_SESSION = [];
|
|
if (session_id() !== '') session_destroy();
|
|
return true;
|
|
}
|
|
|
|
public function login(array $in): array
|
|
{
|
|
$authDb = $this->conf['auth']['db'] ?? [];
|
|
$colUser = $authDb['col_user'] ?? 'email';
|
|
$colPass = $authDb['col_pass'] ?? 'password';
|
|
$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';
|
|
|
|
$identifier = trim((string)($in['username'] ?? $in['user'] ?? $in['email'] ?? $in['login'] ?? ''));
|
|
$password = (string)($in['password'] ?? $in['pass'] ?? $in['pwd'] ?? '');
|
|
|
|
if ($identifier === '' || $password === '') $this->fail('username/password required', null, 422);
|
|
|
|
$stmt = $this->pdo->prepare("SELECT * FROM `$table` WHERE `$colUser` = :u LIMIT 1");
|
|
$stmt->execute([':u'=>$identifier]);
|
|
$row = $stmt->fetch();
|
|
|
|
if (!$row) $this->fail('Invalid credentials', null, 401);
|
|
|
|
if ($colStatus && isset($row[$colStatus])) {
|
|
if (!in_array($row[$colStatus], $activeValues, true)) {
|
|
$this->fail('Account inactive', null, 403);
|
|
}
|
|
}
|
|
|
|
$stored = (string)($row[$colPass] ?? '');
|
|
if ($stored === '' || !$this->verifyPassword($password, $stored, $authDb)) {
|
|
$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,
|
|
'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;
|
|
}
|
|
}
|