WebOrbiton
v2.0.0.1

Publisium

186 lines · 5.4 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/database.php';
  6. ​
  7. final class Auth
  8. {
  9. public const ROLE_SUPER_ADMIN = 'super_admin';
  10. public const ROLE_EDITOR_IN_CHIEF = 'editor_in_chief';
  11. public const ROLE_MANAGING_EDITOR = 'managing_editor';
  12. public const ROLE_SENIOR_WRITER = 'senior_writer';
  13. public const ROLE_WRITER = 'writer';
  14. public const ROLE_PROOFREADER = 'proofreader';
  15. ​
  16. private const ROLE_LEVELS = [
  17. self::ROLE_SUPER_ADMIN => 60,
  18. self::ROLE_EDITOR_IN_CHIEF => 50,
  19. self::ROLE_MANAGING_EDITOR => 40,
  20. self::ROLE_SENIOR_WRITER => 30,
  21. self::ROLE_WRITER => 20,
  22. self::ROLE_PROOFREADER => 10,
  23. ];
  24. ​
  25. private static ?array $current = null;
  26. ​
  27. public static function boot(): void
  28. {
  29. Config::startSession(Config::get('SESSION_COOKIE_NAME', 'publisium_session'));
  30. }
  31. ​
  32. public static function verifyCredentials(string $username, string $password): ?array
  33. {
  34. $statement = Database::site()->prepare(
  35. 'SELECT * FROM team_accounts WHERE (username = :username OR email = :email) AND status = :status LIMIT 1'
  36. );
  37. $statement->execute(['username' => $username, 'email' => $username, 'status' => 'active']);
  38. $account = $statement->fetch();
  39. ​
  40. if (!$account || !password_verify($password, $account['password_hash'])) {
  41. return null;
  42. }
  43. ​
  44. return $account;
  45. }
  46. ​
  47. public static function completeLogin(array $account): void
  48. {
  49. session_regenerate_id(true);
  50. unset($_SESSION['team_2fa']);
  51. $_SESSION['team_account_id'] = (int) $account['id'];
  52. $_SESSION['team_role'] = $account['role'];
  53. ​
  54. $update = Database::site()->prepare('UPDATE team_accounts SET last_login_at = NOW() WHERE id = :id');
  55. $update->execute(['id' => $account['id']]);
  56. }
  57. ​
  58. public static function startTwoFactor(array $account): void
  59. {
  60. session_regenerate_id(true);
  61. $_SESSION['team_2fa'] = ['id' => (int) $account['id'], 'expires' => time() + 300];
  62. }
  63. ​
  64. public static function pendingTwoFactorAccount(): ?array
  65. {
  66. $pending = $_SESSION['team_2fa'] ?? null;
  67. if (!is_array($pending) || (int) ($pending['expires'] ?? 0) < time()) {
  68. unset($_SESSION['team_2fa']);
  69. return null;
  70. }
  71. ​
  72. $statement = Database::site()->prepare('SELECT * FROM team_accounts WHERE id = :id AND status = :status LIMIT 1');
  73. $statement->execute(['id' => (int) $pending['id'], 'status' => 'active']);
  74. $account = $statement->fetch();
  75. ​
  76. if (!$account) {
  77. unset($_SESSION['team_2fa']);
  78. return null;
  79. }
  80. ​
  81. return $account;
  82. }
  83. ​
  84. public static function cancelTwoFactor(): void
  85. {
  86. unset($_SESSION['team_2fa']);
  87. }
  88. ​
  89. public static function logout(): void
  90. {
  91. self::$current = null;
  92. $_SESSION = [];
  93. session_destroy();
  94. }
  95. ​
  96. public static function check(): bool
  97. {
  98. return self::user() !== null;
  99. }
  100. ​
  101. public static function user(): ?array
  102. {
  103. if (!isset($_SESSION['team_account_id'])) {
  104. return null;
  105. }
  106. ​
  107. if (self::$current !== null) {
  108. return self::$current;
  109. }
  110. ​
  111. $statement = Database::site()->prepare('SELECT * FROM team_accounts WHERE id = :id LIMIT 1');
  112. $statement->execute(['id' => $_SESSION['team_account_id']]);
  113. $account = $statement->fetch();
  114. ​
  115. if (!$account || $account['status'] !== 'active') {
  116. self::logout();
  117. return null;
  118. }
  119. ​
  120. $_SESSION['team_role'] = $account['role'];
  121. self::$current = $account;
  122. return self::$current;
  123. }
  124. ​
  125. public static function role(): ?string
  126. {
  127. return self::user()['role'] ?? null;
  128. }
  129. ​
  130. public static function hasRoleAtLeast(string $role): bool
  131. {
  132. $current = self::role();
  133. if ($current === null || !isset(self::ROLE_LEVELS[$current]) || !isset(self::ROLE_LEVELS[$role])) {
  134. return false;
  135. }
  136. ​
  137. return self::ROLE_LEVELS[$current] >= self::ROLE_LEVELS[$role];
  138. }
  139. ​
  140. public static function isAdministrative(): bool
  141. {
  142. return self::hasRoleAtLeast(self::ROLE_MANAGING_EDITOR);
  143. }
  144. ​
  145. public static function canPublishDirectly(): bool
  146. {
  147. return self::hasRoleAtLeast(self::ROLE_SENIOR_WRITER);
  148. }
  149. ​
  150. public static function canModerate(): bool
  151. {
  152. return self::hasRoleAtLeast(self::ROLE_MANAGING_EDITOR);
  153. }
  154. ​
  155. public static function requireLogin(): void
  156. {
  157. if (!self::check()) {
  158. header('Location: team-login.php');
  159. exit;
  160. }
  161. }
  162. ​
  163. public static function requireRoleAtLeast(string $role): void
  164. {
  165. self::requireLogin();
  166. if (!self::hasRoleAtLeast($role)) {
  167. http_response_code(403);
  168. echo 'You don’t have access to this page. Ask an administrator if you need it.';
  169. exit;
  170. }
  171. }
  172. ​
  173. public static function roleLabel(string $role): string
  174. {
  175. return match ($role) {
  176. self::ROLE_SUPER_ADMIN => 'Super Admin',
  177. self::ROLE_EDITOR_IN_CHIEF => 'Editor-in-Chief',
  178. self::ROLE_MANAGING_EDITOR => 'Managing Editor',
  179. self::ROLE_SENIOR_WRITER => 'Senior Writer',
  180. self::ROLE_WRITER => 'Writer',
  181. self::ROLE_PROOFREADER => 'Proofreader',
  182. default => 'Unknown',
  183. };
  184. }
  185. }
  186. ​