148 lines
4.4 KiB
PHP
148 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Core;
|
|
|
|
use PDO;
|
|
|
|
final class Auth
|
|
{
|
|
public function __construct(
|
|
private readonly PDO $pdo,
|
|
private readonly Session $session
|
|
) {
|
|
}
|
|
|
|
public function attemptPlatformLogin(string $email, string $password): bool
|
|
{
|
|
$statement = $this->pdo->prepare(
|
|
'SELECT id, password_hash, platform_role FROM users WHERE email = :email LIMIT 1'
|
|
);
|
|
$statement->execute(['email' => mb_strtolower($email)]);
|
|
$user = $statement->fetch();
|
|
|
|
if (!$user || !password_verify($password, $user['password_hash'])) {
|
|
return false;
|
|
}
|
|
|
|
if (($user['platform_role'] ?? 'user') !== 'platform_admin') {
|
|
return false;
|
|
}
|
|
|
|
$this->rehashPasswordIfNeeded((int) $user['id'], (string) $user['password_hash'], $password);
|
|
|
|
$this->session->regenerate();
|
|
$this->session->put('auth', [
|
|
'user_id' => (int) $user['id'],
|
|
'platform_role' => $user['platform_role'],
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function attemptTenantLogin(string $tenantSlug, string $email, string $password): bool
|
|
{
|
|
$statement = $this->pdo->prepare(
|
|
'SELECT u.id,
|
|
u.password_hash,
|
|
m.tenant_id,
|
|
m.role,
|
|
t.slug AS tenant_slug,
|
|
member.id AS member_id
|
|
FROM users u
|
|
INNER JOIN tenant_memberships m ON m.user_id = u.id AND m.status = "active"
|
|
INNER JOIN tenants t ON t.id = m.tenant_id AND t.slug = :slug AND t.status = "active"
|
|
LEFT JOIN members member
|
|
ON member.user_id = u.id
|
|
AND member.tenant_id = m.tenant_id
|
|
AND member.status = "active"
|
|
WHERE u.email = :email
|
|
LIMIT 1'
|
|
);
|
|
$statement->execute([
|
|
'slug' => $tenantSlug,
|
|
'email' => mb_strtolower($email),
|
|
]);
|
|
$membership = $statement->fetch();
|
|
|
|
if (!$membership || !password_verify($password, $membership['password_hash'])) {
|
|
return false;
|
|
}
|
|
|
|
$this->rehashPasswordIfNeeded((int) $membership['user_id'], (string) $membership['password_hash'], $password);
|
|
|
|
$this->session->regenerate();
|
|
$this->session->put('auth', [
|
|
'user_id' => (int) $membership['user_id'],
|
|
'tenant_id' => (int) $membership['tenant_id'],
|
|
'tenant_slug' => $membership['tenant_slug'],
|
|
'tenant_role' => $membership['role'],
|
|
'member_id' => isset($membership['member_id']) ? (int) $membership['member_id'] : null,
|
|
'platform_role' => 'user',
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function user(): ?array
|
|
{
|
|
$auth = $this->session->get('auth');
|
|
|
|
return is_array($auth) ? $auth : null;
|
|
}
|
|
|
|
public function checkPlatformAdmin(): bool
|
|
{
|
|
return ($this->user()['platform_role'] ?? null) === 'platform_admin';
|
|
}
|
|
|
|
public function checkTenantAccess(string $tenantSlug): bool
|
|
{
|
|
$auth = $this->user();
|
|
|
|
return is_array($auth)
|
|
&& isset($auth['tenant_slug'])
|
|
&& hash_equals($auth['tenant_slug'], $tenantSlug);
|
|
}
|
|
|
|
public function requireRole(array $allowedRoles): bool
|
|
{
|
|
$role = $this->user()['tenant_role'] ?? null;
|
|
|
|
return is_string($role) && in_array($role, $allowedRoles, true);
|
|
}
|
|
|
|
public function tenantMemberId(): ?int
|
|
{
|
|
$memberId = $this->user()['member_id'] ?? null;
|
|
|
|
return is_int($memberId) ? $memberId : null;
|
|
}
|
|
|
|
public function canManageTenant(): bool
|
|
{
|
|
$role = $this->user()['tenant_role'] ?? null;
|
|
|
|
return is_string($role) && in_array($role, ['owner', 'admin'], true);
|
|
}
|
|
|
|
public function logout(): void
|
|
{
|
|
$this->session->invalidate();
|
|
}
|
|
|
|
private function rehashPasswordIfNeeded(int $userId, string $currentHash, string $password): void
|
|
{
|
|
if (!password_needs_rehash($currentHash, defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT)) {
|
|
return;
|
|
}
|
|
|
|
$statement = $this->pdo->prepare(
|
|
'UPDATE users SET password_hash = :password_hash WHERE id = :id'
|
|
);
|
|
$statement->execute([
|
|
'password_hash' => secure_password_hash($password),
|
|
'id' => $userId,
|
|
]);
|
|
}
|
|
}
|