WebOrbiton
v1.0.0.0

StocketBase

235 lines · 10.0 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/includes/config.php';
  6. require_once __DIR__ . '/includes/database.php';
  7. require_once __DIR__ . '/includes/user-auth.php';
  8. require_once __DIR__ . '/includes/csrf.php';
  9. require_once __DIR__ . '/includes/language.php';
  10. require_once __DIR__ . '/includes/site-front.php';
  11. require_once __DIR__ . '/includes/asset.php';
  12. require_once __DIR__ . '/includes/antibot.php';
  13. require_once __DIR__ . '/includes/login-throttle.php';
  14. ​
  15. UserAuth::boot();
  16. AntiBot::boot('user');
  17. if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  18. AntiBot::refresh('user');
  19. }
  20. ​
  21. $settings = SiteFront::settings();
  22. $loginEnabled = ($settings['login_enabled'] ?? '1') !== '0';
  23. ​
  24. function sanitizeRedirectTarget(string $target): string
  25. {
  26. if ($target !== '' && preg_match('#^[a-zA-Z0-9_\-]+\.php(\?[a-zA-Z0-9_\-\.=&%]*)?$#', $target) === 1) {
  27. return $target;
  28. }
  29. return '';
  30. }
  31. ​
  32. $redirectTarget = sanitizeRedirectTarget((string) ($_GET['redirect'] ?? ''));
  33. ​
  34. if (isset($_GET['logout'])) {
  35. UserAuth::logout();
  36. header('Location: user-login.php');
  37. exit;
  38. }
  39. ​
  40. if (UserAuth::check()) {
  41. header('Location: ' . ($redirectTarget !== '' ? $redirectTarget : 'index.php'));
  42. exit;
  43. }
  44. ​
  45. if (!$loginEnabled) {
  46. $siteName = Config::get('APP_NAME', 'StocketBase');
  47. ?>
  48. <!DOCTYPE html>
  49. <html lang="en">
  50. ​
  51. <head>
  52. <meta charset="utf-8">
  53. <meta name="viewport" content="width=device-width, initial-scale=1">
  54. <title><?= htmlspecialchars(Language::get('auth_login_button', 'Log in')) ?> - <?= htmlspecialchars($siteName) ?></title>
  55. <?= Asset::favicon() ?>
  56. <?= Asset::css('assets/site.css') ?>
  57. <style>
  58. <?= SiteFront::renderFontFaces() ?><?= SiteFront::fontVariablesCss() ?>
  59. </style>
  60. <?php if (!empty($settings['custom_css'])): ?>
  61. <style>
  62. <?= $settings['custom_css'] ?>
  63. </style>
  64. <?php endif; ?>
  65. </head>
  66. ​
  67. <body class="auth-page" data-theme="light">
  68. <script>
  69. (function() {
  70. try {
  71. var t = localStorage.getItem('stocketbase_theme');
  72. if (t === 'dark' || t === 'light') {
  73. document.body.setAttribute('data-theme', t);
  74. } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  75. document.body.setAttribute('data-theme', 'dark');
  76. }
  77. } catch (e) {}
  78. })();
  79. </script>
  80. <div class="auth-card auth-card-disabled">
  81. <h1><?= htmlspecialchars($siteName) ?></h1>
  82. <p class="auth-subtitle"><?= htmlspecialchars(Language::get('auth_login_disabled', 'Customer login is temporarily disabled. Please check back later.')) ?></p>
  83. </div>
  84. </body>
  85. ​
  86. </html>
  87. <?php
  88. exit;
  89. }
  90. ​
  91. $error = null;
  92. $mode = ($_GET['mode'] ?? 'login') === 'register' ? 'register' : 'login';
  93. ​
  94. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  95. if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
  96. $error = Language::get('form_security_failed', 'Security check failed, please try again.');
  97. } elseif (!AntiBot::verify('user', $_POST['antibot_answer'] ?? null, $_POST['antibot_started'] ?? null, $_POST['website'] ?? null)) {
  98. $error = Language::get('auth_security_failed', 'Security verification failed, please try again.');
  99. } else {
  100. $formAction = $_POST['form_action'] ?? '';
  101. ​
  102. if ($formAction === 'login') {
  103. $email = trim((string) ($_POST['email'] ?? ''));
  104. $password = (string) ($_POST['password'] ?? '');
  105. ​
  106. if ($email === '' || $password === '') {
  107. $error = Language::get('auth_missing_credentials', 'Please enter your email and password.');
  108. $mode = 'login';
  109. } elseif (($wait = LoginThrottle::secondsUntilAllowed('reader', $email)) > 0) {
  110. $error = LoginThrottle::message($wait);
  111. $mode = 'login';
  112. } elseif (UserAuth::attemptLogin($email, $password)) {
  113. LoginThrottle::clear('reader', $email);
  114. header('Location: ' . ($redirectTarget !== '' ? $redirectTarget : 'index.php'));
  115. exit;
  116. } else {
  117. LoginThrottle::recordFailure('reader', $email);
  118. $error = Language::get('auth_invalid_credentials', 'Invalid email or password.');
  119. $mode = 'login';
  120. }
  121. }
  122. ​
  123. if ($formAction === 'register') {
  124. $email = trim((string) ($_POST['email'] ?? ''));
  125. $password = (string) ($_POST['password'] ?? '');
  126. $passwordConfirm = (string) ($_POST['password_confirm'] ?? '');
  127. $displayName = trim((string) ($_POST['display_name'] ?? ''));
  128. ​
  129. if ($password !== $passwordConfirm) {
  130. $error = Language::get('auth_password_mismatch', 'Passwords do not match.');
  131. $mode = 'register';
  132. } else {
  133. [$success, $registerError] = UserAuth::register($email, $password, $displayName);
  134. if ($success) {
  135. header('Location: ' . ($redirectTarget !== '' ? $redirectTarget : 'index.php'));
  136. exit;
  137. }
  138. $error = $registerError;
  139. $mode = 'register';
  140. }
  141. }
  142. }
  143. }
  144. ​
  145. $siteName = Config::get('APP_NAME', 'StocketBase');
  146. ​
  147. ?>
  148. <!DOCTYPE html>
  149. <html lang="en">
  150. ​
  151. <head>
  152. <meta charset="utf-8">
  153. <meta name="viewport" content="width=device-width, initial-scale=1">
  154. <title><?= $mode === 'register' ? htmlspecialchars(Language::get('auth_register_button', 'Create account')) : htmlspecialchars(Language::get('auth_login_button', 'Log in')) ?> - <?= htmlspecialchars($siteName) ?></title>
  155. <?= Asset::favicon() ?>
  156. <?= Asset::css('assets/site.css') ?>
  157. <style>
  158. <?= SiteFront::renderFontFaces() ?><?= SiteFront::fontVariablesCss() ?>
  159. </style>
  160. <?php if (!empty($settings['custom_css'])): ?>
  161. <style>
  162. <?= $settings['custom_css'] ?>
  163. </style>
  164. <?php endif; ?>
  165. </head>
  166. ​
  167. <body class="auth-page" data-theme="light">
  168. <script>
  169. (function() {
  170. try {
  171. var t = localStorage.getItem('stocketbase_theme');
  172. if (t === 'dark' || t === 'light') {
  173. document.body.setAttribute('data-theme', t);
  174. } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  175. document.body.setAttribute('data-theme', 'dark');
  176. }
  177. } catch (e) {}
  178. })();
  179. </script>
  180. <div class="auth-card">
  181. <h1><?= htmlspecialchars($siteName) ?></h1>
  182. <p class="auth-subtitle"><?= htmlspecialchars(Language::get('auth_account_subtitle', 'Customer account')) ?></p>
  183. ​
  184. <div class="auth-tabs">
  185. <a href="user-login.php?mode=login<?= $redirectTarget !== '' ? '&redirect=' . urlencode($redirectTarget) : '' ?>" class="<?= $mode === 'login' ? 'active' : '' ?>"><?= htmlspecialchars(Language::get('auth_login_button', 'Log in')) ?></a>
  186. <a href="user-login.php?mode=register<?= $redirectTarget !== '' ? '&redirect=' . urlencode($redirectTarget) : '' ?>" class="<?= $mode === 'register' ? 'active' : '' ?>"><?= htmlspecialchars(Language::get('auth_register_button', 'Create account')) ?></a>
  187. </div>
  188. ​
  189. <?php if ($error !== null): ?>
  190. <div class="auth-error"><?= htmlspecialchars($error) ?></div>
  191. <?php endif; ?>
  192. ​
  193. <?php if ($mode === 'login'): ?>
  194. <form method="post">
  195. <?= Csrf::field() ?>
  196. <?= AntiBot::field('user') ?>
  197. <input type="hidden" name="form_action" value="login">
  198. <label><?= htmlspecialchars(Language::get('auth_email', 'Email')) ?></label>
  199. <input type="email" name="email" required autofocus>
  200. <label><?= htmlspecialchars(Language::get('auth_password', 'Password')) ?></label>
  201. <input type="password" name="password" required>
  202. <div class="antibot-box">
  203. <div class="antibot-image"><?= AntiBot::image('user') ?></div>
  204. <label for="antibot-answer"><?= htmlspecialchars(Language::get('auth_security_code', 'Security code')) ?></label>
  205. <input id="antibot-answer" type="text" name="antibot_answer" required inputmode="text" autocomplete="off" maxlength="6" spellcheck="false">
  206. </div>
  207. <button type="submit"><?= htmlspecialchars(Language::get('auth_login_button', 'Log in')) ?></button>
  208. </form>
  209. <?php else: ?>
  210. <form method="post">
  211. <?= Csrf::field() ?>
  212. <?= AntiBot::field('user') ?>
  213. <input type="hidden" name="form_action" value="register">
  214. <label><?= htmlspecialchars(Language::get('auth_display_name', 'Display name')) ?></label>
  215. <input type="text" name="display_name">
  216. <label><?= htmlspecialchars(Language::get('auth_email', 'Email')) ?></label>
  217. <input type="email" name="email" required>
  218. <label><?= htmlspecialchars(Language::get('auth_password', 'Password')) ?></label>
  219. <input type="password" name="password" required minlength="8">
  220. <label><?= htmlspecialchars(Language::get('auth_confirm_password', 'Confirm password')) ?></label>
  221. <input type="password" name="password_confirm" required minlength="8">
  222. <div class="antibot-box">
  223. <div class="antibot-image"><?= AntiBot::image('user') ?></div>
  224. <label for="antibot-answer"><?= htmlspecialchars(Language::get('auth_security_code', 'Security code')) ?></label>
  225. <input id="antibot-answer" type="text" name="antibot_answer" required inputmode="text" autocomplete="off" maxlength="6" spellcheck="false">
  226. </div>
  227. <button type="submit"><?= htmlspecialchars(Language::get('auth_register_button', 'Create account')) ?></button>
  228. </form>
  229. <?php endif; ?>
  230. ​
  231. </div>
  232. </body>
  233. ​
  234. </html>
  235. ​