WebOrbiton
v1.0.0.0

StocketBase

99 lines · 4.8 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. if (count(get_included_files()) === 1) {
  6. http_response_code(403);
  7. exit;
  8. }
  9. ​
  10. $twoFactorEnabled = TwoFactor::isEnabled($currentUser);
  11. $setupSecret = $twoFactorEnabled ? '' : (string) ($_SESSION['totp_setup_secret'] ?? '');
  12. $newCodes = $_SESSION['totp_new_codes'] ?? null;
  13. unset($_SESSION['totp_new_codes']);
  14. $issuer = (string) Config::get('APP_NAME', 'StocketBase');
  15. ​
  16. ?>
  17. <h1 class="dash-title"><?= Icons::icon('lock', 'icon icon-lg') ?>Security</h1>
  18. ​
  19. <?php if (is_array($newCodes) && !empty($newCodes)): ?>
  20. <div class="sidebar-box" style="max-width:640px;margin-bottom:20px;">
  21. <h2 class="dash-subtitle">Recovery codes</h2>
  22. <p>Save these codes in a safe place. Each one works once if you lose access to your authenticator app. They will not be shown again.</p>
  23. <pre style="font-size:16px;line-height:1.8;"><?= htmlspecialchars(implode("\n", $newCodes)) ?></pre>
  24. </div>
  25. <?php endif; ?>
  26. ​
  27. <div class="sidebar-box" style="max-width:640px;">
  28. <h2 class="dash-subtitle">Two-factor authentication</h2>
  29. ​
  30. <?php if ($twoFactorEnabled): ?>
  31. <p>Status: <span class="status-pill status-published">Enabled</span> &middot; recovery codes left: <?= TwoFactor::remainingRecoveryCodes($currentUser) ?></p>
  32. ​
  33. <form method="post" style="margin-top:16px;">
  34. <?= Csrf::field() ?>
  35. <input type="hidden" name="action" value="totp_regenerate">
  36. <label>Password</label>
  37. <input type="password" name="password" required autocomplete="current-password">
  38. <label>Authentication code or a recovery code</label>
  39. <input type="text" name="code" required autocomplete="one-time-code" maxlength="16" spellcheck="false">
  40. <button type="submit" class="dash-btn" style="margin-top:12px;"><?= Icons::icon('refresh', 'icon icon-sm') ?>Generate new recovery codes</button>
  41. </form>
  42. ​
  43. <form method="post" style="margin-top:24px;" onsubmit="return confirm('Turn off two-factor authentication?');">
  44. <?= Csrf::field() ?>
  45. <input type="hidden" name="action" value="totp_disable">
  46. <label>Password</label>
  47. <input type="password" name="password" required autocomplete="current-password">
  48. <label>Authentication code or a recovery code</label>
  49. <input type="text" name="code" required autocomplete="one-time-code" maxlength="16" spellcheck="false">
  50. <button type="submit" class="dash-btn dash-btn-danger" style="margin-top:12px;"><?= Icons::icon('x', 'icon icon-sm') ?>Turn off two-factor authentication</button>
  51. </form>
  52. ​
  53. <?php elseif ($setupSecret !== ''): ?>
  54. <p>1. In your authenticator app (Google Authenticator, Authy, 1Password, Aegis…) scan this QR code. The code is drawn in your browser and the key never leaves this page.</p>
  55. <div id="totp-qr" data-uri="<?= htmlspecialchars(Totp::uri($setupSecret, (string) $currentUser['username'], $issuer), ENT_QUOTES) ?>" style="width:220px;max-width:100%;margin:12px 0;border-radius:8px;overflow:hidden;background:#fff;"></div>
  56. <p style="font-size:13px;color:var(--muted);">Cannot scan it? Add the account manually (time based) with this key:</p>
  57. <p style="font-size:20px;letter-spacing:.08em;margin:8px 0;"><code><?= htmlspecialchars(Totp::formatSecret($setupSecret)) ?></code></p>
  58. <p style="font-size:12px;color:var(--muted);word-break:break-all;">Account: <?= htmlspecialchars((string) $currentUser['username']) ?> &middot; Issuer: <?= htmlspecialchars($issuer) ?></p>
  59. <?= Asset::js('assets/vendor/qrcode.js') ?>
  60. <script>
  61. (function () {
  62. var box = document.getElementById('totp-qr');
  63. if (!box || typeof qrcode !== 'function') { return; }
  64. ​
  65. var code = qrcode(0, 'M');
  66. code.addData(box.getAttribute('data-uri'));
  67. code.make();
  68. box.innerHTML = code.createSvgTag({ cellSize: 4, margin: 16, scalable: true });
  69. })();
  70. </script>
  71. <p style="margin-top:14px;">2. Enter the 6-digit code shown by the app to finish.</p>
  72. ​
  73. <form method="post">
  74. <?= Csrf::field() ?>
  75. <input type="hidden" name="action" value="totp_confirm">
  76. <label>Authentication code</label>
  77. <input type="text" name="code" required inputmode="numeric" autocomplete="one-time-code" maxlength="8" pattern="[0-9 ]*" spellcheck="false">
  78. <div class="publish-actions">
  79. <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Turn on</button>
  80. </div>
  81. </form>
  82. ​
  83. <form method="post" style="margin-top:8px;">
  84. <?= Csrf::field() ?>
  85. <input type="hidden" name="action" value="totp_cancel">
  86. <button type="submit" class="dash-btn"><?= Icons::icon('x', 'icon icon-sm') ?>Cancel</button>
  87. </form>
  88. ​
  89. <?php else: ?>
  90. <p>Status: <span class="status-pill status-draft">Off</span></p>
  91. <p style="margin:10px 0 14px;">Add a second step to your login: a 6-digit code from an authenticator app, in addition to your password.</p>
  92. <form method="post">
  93. <?= Csrf::field() ?>
  94. <input type="hidden" name="action" value="totp_begin">
  95. <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('lock', 'icon icon-sm') ?>Set up</button>
  96. </form>
  97. <?php endif; ?>
  98. </div>
  99. ​