WebOrbiton
v1.0.0.9

Publisium

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