v1.0.0.0
StocketBase
- <?php
-
- declare(strict_types=1);
-
- if (count(get_included_files()) === 1) {
- http_response_code(403);
- exit;
- }
-
- $twoFactorEnabled = TwoFactor::isEnabled($currentUser);
- $setupSecret = $twoFactorEnabled ? '' : (string) ($_SESSION['totp_setup_secret'] ?? '');
- $newCodes = $_SESSION['totp_new_codes'] ?? null;
- unset($_SESSION['totp_new_codes']);
- $issuer = (string) Config::get('APP_NAME', 'StocketBase');
-
- ?>
- <h1 class="dash-title"><?= Icons::icon('lock', 'icon icon-lg') ?>Security</h1>
-
- <?php if (is_array($newCodes) && !empty($newCodes)): ?>
- <div class="sidebar-box" style="max-width:640px;margin-bottom:20px;">
- <h2 class="dash-subtitle">Recovery codes</h2>
- <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>
- <pre style="font-size:16px;line-height:1.8;"><?= htmlspecialchars(implode("\n", $newCodes)) ?></pre>
- </div>
- <?php endif; ?>
-
- <div class="sidebar-box" style="max-width:640px;">
- <h2 class="dash-subtitle">Two-factor authentication</h2>
-
- <?php if ($twoFactorEnabled): ?>
- <p>Status: <span class="status-pill status-published">Enabled</span> · recovery codes left: <?= TwoFactor::remainingRecoveryCodes($currentUser) ?></p>
-
- <form method="post" style="margin-top:16px;">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="totp_regenerate">
- <label>Password</label>
- <input type="password" name="password" required autocomplete="current-password">
- <label>Authentication code or a recovery code</label>
- <input type="text" name="code" required autocomplete="one-time-code" maxlength="16" spellcheck="false">
- <button type="submit" class="dash-btn" style="margin-top:12px;"><?= Icons::icon('refresh', 'icon icon-sm') ?>Generate new recovery codes</button>
- </form>
-
- <form method="post" style="margin-top:24px;" onsubmit="return confirm('Turn off two-factor authentication?');">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="totp_disable">
- <label>Password</label>
- <input type="password" name="password" required autocomplete="current-password">
- <label>Authentication code or a recovery code</label>
- <input type="text" name="code" required autocomplete="one-time-code" maxlength="16" spellcheck="false">
- <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>
- </form>
-
- <?php elseif ($setupSecret !== ''): ?>
- <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>
- <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>
- <p style="font-size:13px;color:var(--muted);">Cannot scan it? Add the account manually (time based) with this key:</p>
- <p style="font-size:20px;letter-spacing:.08em;margin:8px 0;"><code><?= htmlspecialchars(Totp::formatSecret($setupSecret)) ?></code></p>
- <p style="font-size:12px;color:var(--muted);word-break:break-all;">Account: <?= htmlspecialchars((string) $currentUser['username']) ?> · Issuer: <?= htmlspecialchars($issuer) ?></p>
- <?= Asset::js('assets/vendor/qrcode.js') ?>
- <script>
- (function () {
- var box = document.getElementById('totp-qr');
- if (!box || typeof qrcode !== 'function') { return; }
-
- var code = qrcode(0, 'M');
- code.addData(box.getAttribute('data-uri'));
- code.make();
- box.innerHTML = code.createSvgTag({ cellSize: 4, margin: 16, scalable: true });
- })();
- </script>
- <p style="margin-top:14px;">2. Enter the 6-digit code shown by the app to finish.</p>
-
- <form method="post">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="totp_confirm">
- <label>Authentication code</label>
- <input type="text" name="code" required inputmode="numeric" autocomplete="one-time-code" maxlength="8" pattern="[0-9 ]*" spellcheck="false">
- <div class="publish-actions">
- <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Turn on</button>
- </div>
- </form>
-
- <form method="post" style="margin-top:8px;">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="totp_cancel">
- <button type="submit" class="dash-btn"><?= Icons::icon('x', 'icon icon-sm') ?>Cancel</button>
- </form>
-
- <?php else: ?>
- <p>Status: <span class="status-pill status-draft">Off</span></p>
- <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>
- <form method="post">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="totp_begin">
- <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('lock', 'icon icon-sm') ?>Set up</button>
- </form>
- <?php endif; ?>
- </div>
-