auth.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. /**
  3. * lib/auth.php
  4. *
  5. * Authentication and authorisation functions.
  6. * Requires config/database.php to be included before use.
  7. */
  8. if (session_status() === PHP_SESSION_NONE) {
  9. session_start();
  10. }
  11. // ---------------------------------------------------------------------------
  12. // Session helpers
  13. // ---------------------------------------------------------------------------
  14. function isLoggedIn(): bool
  15. {
  16. return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
  17. }
  18. function getCurrentUserId(): ?int
  19. {
  20. return isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : null;
  21. }
  22. function getCurrentUser(): ?array
  23. {
  24. if (!isLoggedIn()) {
  25. return null;
  26. }
  27. return [
  28. 'id' => (int) $_SESSION['user_id'],
  29. 'fullname' => $_SESSION['user_name'] ?? '',
  30. 'email' => $_SESSION['user_email'] ?? '',
  31. 'user_type' => $_SESSION['user_type'] ?? 'client',
  32. ];
  33. }
  34. function getCurrentUserType(): string
  35. {
  36. return $_SESSION['user_type'] ?? 'client';
  37. }
  38. function isConsultant(): bool
  39. {
  40. return isLoggedIn() && in_array($_SESSION['user_type'] ?? '', ['consultant', 'admin'], true);
  41. }
  42. function isAdmin(): bool
  43. {
  44. return isLoggedIn() && ($_SESSION['user_type'] ?? '') === 'admin';
  45. }
  46. function requireLogin(): void
  47. {
  48. if (!isLoggedIn()) {
  49. $current = $_SERVER['REQUEST_URI'] ?? '';
  50. $redirect = $current !== '' ? '?redirect=' . urlencode($current) : '';
  51. header('Location: /login/login.php' . $redirect);
  52. exit;
  53. }
  54. }
  55. function requireConsultant(): void
  56. {
  57. requireLogin();
  58. if (!isConsultant()) {
  59. http_response_code(403);
  60. die('Access denied. Consultant account required.');
  61. }
  62. }
  63. function requireAdmin(): void
  64. {
  65. requireLogin();
  66. if (!isAdmin()) {
  67. http_response_code(403);
  68. die('Access denied. Administrator account required.');
  69. }
  70. }
  71. function hasPermission(string $permission): bool
  72. {
  73. return isLoggedIn();
  74. }
  75. // ---------------------------------------------------------------------------
  76. // Login / Logout
  77. // ---------------------------------------------------------------------------
  78. /**
  79. * Attempt login with email + plain-text password.
  80. * Returns user row array on success, null on failure.
  81. */
  82. function loginUser(string $email, string $password): ?array
  83. {
  84. $pdo = getDBConnection();
  85. $stmt = $pdo->prepare(
  86. 'SELECT id, fullname, email, password, user_type FROM users WHERE email = ? AND active = 1 LIMIT 1'
  87. );
  88. $stmt->execute([strtolower(trim($email))]);
  89. $user = $stmt->fetch();
  90. if (!$user || !password_verify($password, $user['password'])) {
  91. return null;
  92. }
  93. // Rehash on cost/algorithm upgrade
  94. if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
  95. $pdo->prepare('UPDATE users SET password = ? WHERE id = ?')
  96. ->execute([password_hash($password, PASSWORD_DEFAULT), $user['id']]);
  97. }
  98. session_regenerate_id(true);
  99. $_SESSION['user_id'] = $user['id'];
  100. $_SESSION['user_name'] = $user['fullname'];
  101. $_SESSION['user_email'] = $user['email'];
  102. $_SESSION['user_type'] = $user['user_type'] ?? 'client';
  103. return $user;
  104. }
  105. /**
  106. * Destroy session completely and clear the session cookie.
  107. */
  108. function logoutUser(): void
  109. {
  110. $_SESSION = [];
  111. if (ini_get('session.use_cookies')) {
  112. $p = session_get_cookie_params();
  113. setcookie(
  114. session_name(), '', time() - 42000,
  115. $p['path'], $p['domain'], $p['secure'], $p['httponly']
  116. );
  117. }
  118. session_destroy();
  119. }
  120. // ---------------------------------------------------------------------------
  121. // Registration
  122. // ---------------------------------------------------------------------------
  123. /**
  124. * Register a new user.
  125. * Returns ['success' => true, 'user_id' => int]
  126. * or ['success' => false, 'error' => string]
  127. */
  128. function registerUser(array $data): array
  129. {
  130. $pdo = getDBConnection();
  131. $email = strtolower(trim($data['email'] ?? ''));
  132. // Duplicate email check
  133. $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
  134. $stmt->execute([$email]);
  135. if ($stmt->fetch()) {
  136. return ['success' => false, 'error' => 'An account with that email already exists.'];
  137. }
  138. $stmt = $pdo->prepare('
  139. INSERT INTO users
  140. (fullname, email, password, company, mobilephone, industry, role, city, state, postcode, country, active, created_at)
  141. VALUES
  142. (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())
  143. ');
  144. $stmt->execute([
  145. trim($data['fullname'] ?? ''),
  146. $email,
  147. password_hash($data['password'], PASSWORD_DEFAULT),
  148. trim($data['company'] ?? ''),
  149. trim($data['mobilephone'] ?? ''),
  150. $data['industry'] ?? '',
  151. $data['role'] ?? '',
  152. trim($data['city'] ?? ''),
  153. $data['state'] ?? '',
  154. trim($data['postcode'] ?? ''),
  155. $data['country'] ?? 'Australia',
  156. ]);
  157. return ['success' => true, 'user_id' => (int) $pdo->lastInsertId()];
  158. }
  159. // ---------------------------------------------------------------------------
  160. // Password reset
  161. // ---------------------------------------------------------------------------
  162. /**
  163. * Create a password-reset token for $email (1-hour expiry).
  164. * Returns the raw token string, or null if the email doesn't exist.
  165. */
  166. function createPasswordResetToken(string $email): ?string
  167. {
  168. $pdo = getDBConnection();
  169. $email = strtolower(trim($email));
  170. $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? AND active = 1 LIMIT 1');
  171. $stmt->execute([$email]);
  172. if (!$stmt->fetch()) {
  173. return null;
  174. }
  175. // Remove any previous tokens for this email
  176. $pdo->prepare('DELETE FROM password_resets WHERE email = ?')->execute([$email]);
  177. $token = bin2hex(random_bytes(32));
  178. $pdo->prepare(
  179. 'INSERT INTO password_resets (email, token, created_at, expires_at)
  180. VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 HOUR))'
  181. )->execute([$email, $token]);
  182. return $token;
  183. }
  184. /**
  185. * Validate a reset token. Returns the associated email on success, null if invalid/expired.
  186. */
  187. function validatePasswordResetToken(string $token): ?string
  188. {
  189. $pdo = getDBConnection();
  190. $stmt = $pdo->prepare(
  191. 'SELECT email FROM password_resets WHERE token = ? AND expires_at > NOW() LIMIT 1'
  192. );
  193. $stmt->execute([$token]);
  194. $row = $stmt->fetch();
  195. return $row ? $row['email'] : null;
  196. }
  197. /**
  198. * Update the user's password and delete the reset token.
  199. */
  200. function resetPassword(string $token, string $newPassword): bool
  201. {
  202. $pdo = getDBConnection();
  203. $email = validatePasswordResetToken($token);
  204. if (!$email) {
  205. return false;
  206. }
  207. $pdo->prepare('UPDATE users SET password = ? WHERE email = ?')
  208. ->execute([password_hash($newPassword, PASSWORD_DEFAULT), $email]);
  209. $pdo->prepare('DELETE FROM password_resets WHERE email = ?')->execute([$email]);
  210. return true;
  211. }
  212. /**
  213. * Change password for currently logged-in user after verifying old password.
  214. * Returns true on success, false if old password is wrong.
  215. */
  216. function changePassword(int $userId, string $oldPassword, string $newPassword): bool
  217. {
  218. $pdo = getDBConnection();
  219. $stmt = $pdo->prepare('SELECT password FROM users WHERE id = ? LIMIT 1');
  220. $stmt->execute([$userId]);
  221. $user = $stmt->fetch();
  222. if (!$user || !password_verify($oldPassword, $user['password'])) {
  223. return false;
  224. }
  225. $pdo->prepare('UPDATE users SET password = ? WHERE id = ?')
  226. ->execute([password_hash($newPassword, PASSWORD_DEFAULT), $userId]);
  227. return true;
  228. }