WebOrbiton
v1.0.0.0

StocketBase

159 lines · 5.1 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/auth.php';
  8. require_once __DIR__ . '/includes/csrf.php';
  9. require_once __DIR__ . '/includes/asset.php';
  10. require_once __DIR__ . '/includes/site-front.php';
  11. require_once __DIR__ . '/includes/antibot.php';
  12. require_once __DIR__ . '/includes/login-throttle.php';
  13. require_once __DIR__ . '/includes/two-factor.php';
  14. ​
  15. Auth::boot();
  16. AntiBot::boot('team');
  17. if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  18. AntiBot::refresh('team');
  19. }
  20. ​
  21. if (isset($_GET['logout'])) {
  22. Auth::logout();
  23. header('Location: team-login.php');
  24. exit;
  25. }
  26. ​
  27. if (isset($_GET['cancel'])) {
  28. Auth::cancelTwoFactor();
  29. header('Location: team-login.php');
  30. exit;
  31. }
  32. ​
  33. if (Auth::check()) {
  34. header('Location: dashboard.php');
  35. exit;
  36. }
  37. ​
  38. $error = null;
  39. $pending = Auth::pendingTwoFactorAccount();
  40. ​
  41. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  42. if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
  43. $error = 'Security check failed, please try again.';
  44. } elseif ($pending !== null) {
  45. $identifier = (string) $pending['id'];
  46. $wait = LoginThrottle::secondsUntilAllowed('2fa', $identifier);
  47. ​
  48. if ($wait > 0) {
  49. $error = LoginThrottle::message($wait);
  50. } elseif (TwoFactor::verify($pending, (string) ($_POST['code'] ?? ''))) {
  51. LoginThrottle::clear('2fa', $identifier);
  52. Auth::completeLogin($pending);
  53. header('Location: dashboard.php');
  54. exit;
  55. } else {
  56. LoginThrottle::recordFailure('2fa', $identifier);
  57. $error = 'Invalid code.';
  58. }
  59. } elseif (!AntiBot::verify('team', $_POST['antibot_answer'] ?? null, $_POST['antibot_started'] ?? null, $_POST['website'] ?? null)) {
  60. $error = 'Security verification failed, please try again.';
  61. } else {
  62. $username = trim((string) ($_POST['username'] ?? ''));
  63. $password = (string) ($_POST['password'] ?? '');
  64. ​
  65. if ($username === '' || $password === '') {
  66. $error = 'Please enter your username and password.';
  67. } else {
  68. $wait = LoginThrottle::secondsUntilAllowed('team', $username);
  69. ​
  70. if ($wait > 0) {
  71. $error = LoginThrottle::message($wait);
  72. } else {
  73. $account = Auth::verifyCredentials($username, $password);
  74. ​
  75. if ($account === null) {
  76. LoginThrottle::recordFailure('team', $username);
  77. $error = 'Invalid credentials.';
  78. } else {
  79. LoginThrottle::clear('team', $username);
  80. ​
  81. if (TwoFactor::isEnabled($account)) {
  82. Auth::startTwoFactor($account);
  83. header('Location: team-login.php');
  84. exit;
  85. }
  86. ​
  87. Auth::completeLogin($account);
  88. header('Location: dashboard.php');
  89. exit;
  90. }
  91. }
  92. }
  93. }
  94. }
  95. ​
  96. $siteName = Config::get('APP_NAME', 'StocketBase');
  97. ​
  98. ?>
  99. <!DOCTYPE html>
  100. <html lang="en">
  101. <head>
  102. <meta charset="utf-8">
  103. <meta name="viewport" content="width=device-width, initial-scale=1">
  104. <title>Team login - <?= htmlspecialchars($siteName) ?></title>
  105. <?= Asset::favicon() ?>
  106. <?= Asset::css('assets/site.css') ?>
  107. <style>
  108. <?= SiteFront::renderFontFaces() ?><?= SiteFront::fontVariablesCss() ?>
  109. </style>
  110. </head>
  111. <body class="auth-page" data-theme="light">
  112. <script>
  113. (function() {
  114. try {
  115. var t = localStorage.getItem('stocketbase_theme');
  116. if (t === 'dark' || t === 'light') {
  117. document.body.setAttribute('data-theme', t);
  118. } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
  119. document.body.setAttribute('data-theme', 'dark');
  120. }
  121. } catch (e) {}
  122. })();
  123. </script>
  124. <div class="auth-card">
  125. <h1>Team login</h1>
  126. <p class="auth-subtitle"><?= htmlspecialchars($siteName) ?> editorial access</p>
  127. ​
  128. <?php if ($error !== null): ?>
  129. <div class="auth-error"><?= htmlspecialchars($error) ?></div>
  130. <?php endif; ?>
  131. ​
  132. <?php if ($pending !== null): ?>
  133. <form method="post">
  134. <?= Csrf::field() ?>
  135. <label>Authentication code (or a recovery code)</label>
  136. <input type="text" name="code" required autofocus autocomplete="one-time-code" inputmode="text" maxlength="16" spellcheck="false">
  137. <button type="submit">Verify</button>
  138. <p style="margin-top:14px;font-size:13px;"><a href="team-login.php?cancel=1">Back to login</a></p>
  139. </form>
  140. <?php else: ?>
  141. <form method="post">
  142. <?= Csrf::field() ?>
  143. <?= AntiBot::field('team') ?>
  144. <label>Username or email</label>
  145. <input type="text" name="username" required autofocus>
  146. <label>Password</label>
  147. <input type="password" name="password" required>
  148. <div class="antibot-box">
  149. <div class="antibot-image"><?= AntiBot::image('team') ?></div>
  150. <label for="team-antibot-answer">Security code</label>
  151. <input id="team-antibot-answer" type="text" name="antibot_answer" required inputmode="text" autocomplete="off" maxlength="6" spellcheck="false">
  152. </div>
  153. <button type="submit">Log in</button>
  154. </form>
  155. <?php endif; ?>
  156. </div>
  157. </body>
  158. </html>
  159. ​