mirror of
git://f0xx.org/ac/ac-be-auth
synced 2026-07-29 05:19:04 +03:00
feat(2fa): cross-device approval UI, polling JS, approve view
- two_factor_challenge.php: QR now shown above the code field with data-poll-token/url attrs; approval status banner; updated caption - two_factor_approve.php: new mobile confirmation page (Approve/Cancel) - two_factor.js: adds poll loop every 2.5s; auto-redirect on approval; expires QR visually; stops polling on manual TOTP submit Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
71
views/two_factor_approve.php
Normal file
71
views/two_factor_approve.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
$bp = Auth::basePath();
|
||||
$auth = Auth::authUrl();
|
||||
$user = Auth::user();
|
||||
$rawToken = trim($_GET['token'] ?? $_POST['token'] ?? '');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Approve sign-in — <?= h(cfg('app_name')) ?></title>
|
||||
<script>
|
||||
(function () {
|
||||
var t = localStorage.getItem('crash_console_theme');
|
||||
if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t);
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="login-card" style="text-align:center;">
|
||||
<h1 style="font-size:1.5rem;margin-bottom:0.5rem;">
|
||||
<?php if (!empty($approveResult)): ?>
|
||||
<?= $approveResult === 'ok' ? '✅' : '⚠️' ?>
|
||||
<?php else: ?>
|
||||
🔐
|
||||
<?php endif; ?>
|
||||
</h1>
|
||||
|
||||
<?php if (!empty($approveResult)): ?>
|
||||
|
||||
<?php if ($approveResult === 'ok'): ?>
|
||||
<p style="font-size:1.1rem;font-weight:600;" data-i18n="twofa.approve.success">
|
||||
Sign-in approved.
|
||||
</p>
|
||||
<p class="muted">Your other device should now be logged in automatically.</p>
|
||||
<?php else: ?>
|
||||
<p class="alert">This approval link has expired or is invalid.</p>
|
||||
<?php endif; ?>
|
||||
<p style="margin-top:1.5rem;">
|
||||
<a href="<?= h($auth) ?>/" class="button">Back to hub</a>
|
||||
</p>
|
||||
|
||||
<?php elseif ($approvalInfo): ?>
|
||||
|
||||
<p style="font-size:1.1rem;font-weight:600;">Approve sign-in on another device?</p>
|
||||
<p class="muted">
|
||||
Tap <b>Approve</b> to log in the browser that showed you this QR code.<br>
|
||||
This link expires in a few minutes.
|
||||
</p>
|
||||
<form method="post" action="<?= h($auth) ?>/two-factor/approve" style="margin-top:1.5rem;">
|
||||
<input type="hidden" name="token" value="<?= h($rawToken) ?>">
|
||||
<button type="submit" style="width:100%;font-size:1.1rem;">Approve</button>
|
||||
</form>
|
||||
<p style="margin-top:1rem;">
|
||||
<a href="<?= h($auth) ?>/" class="muted" style="font-size:0.9rem;">Cancel</a>
|
||||
</p>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<p class="alert">This approval link has expired or has already been used.</p>
|
||||
<p style="margin-top:1rem;">
|
||||
<a href="<?= h($auth) ?>/" class="button">Back to hub</a>
|
||||
</p>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php platform_render_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -19,7 +19,12 @@ $twofaQr = AuthTwoFactorPage::qrPayload();
|
||||
</script>
|
||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/two_factor.js" defer></script>
|
||||
<script src="<?= h($bp) ?>/assets/js/two_factor.js" defer
|
||||
<?php if (is_array($twofaQr) && !empty($twofaQr['raw_token'])): ?>
|
||||
data-poll-token="<?= h($twofaQr['raw_token']) ?>"
|
||||
data-poll-url="<?= h($auth) ?>/api/two-factor/poll"
|
||||
<?php endif; ?>
|
||||
></script>
|
||||
</head>
|
||||
<body class="login-page" data-base-path="<?= h($bp) ?>">
|
||||
<div class="login-locale">
|
||||
@@ -33,16 +38,26 @@ $twofaQr = AuthTwoFactorPage::qrPayload();
|
||||
</div>
|
||||
<form class="login-card" method="post" action="<?= h($auth) ?>/two-factor" id="twofa-form">
|
||||
<h1 data-i18n="twofa.title">Two-factor authentication</h1>
|
||||
<p class="muted" data-i18n="twofa.hint">Enter the 6-digit code from your authenticator app.</p>
|
||||
<?php if (is_array($twofaQr) && !empty($twofaQr['qr_url'])): ?>
|
||||
<p class="muted" data-i18n="twofa.hint_scan">Scan the QR code with your phone to approve this sign-in, or enter the 6-digit code below.</p>
|
||||
<figure class="twofa-qr-wrap" id="twofa-qr-wrap">
|
||||
<img src="<?= h((string) $twofaQr['qr_url']) ?>" width="160" height="160" alt="QR code — scan to approve sign-in on this device" class="twofa-qr">
|
||||
<figcaption class="muted">
|
||||
Scan to approve on mobile
|
||||
<?php if (!empty($twofaQr['short_url'])): ?>
|
||||
· <a href="<?= h((string) $twofaQr['short_url']) ?>" rel="noopener"><?= h((string) $twofaQr['short_url']) ?></a>
|
||||
<?php endif; ?>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<div id="twofa-approval-status" class="muted" style="margin-bottom:0.75rem;display:none;">
|
||||
⏳ Waiting for mobile approval…
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="muted" data-i18n="twofa.hint">Enter the 6-digit code from your authenticator app.</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($twofaError)): ?>
|
||||
<div class="alert" data-i18n="twofa.error"><?= h($twofaError) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (is_array($twofaQr) && !empty($twofaQr['qr_url'])): ?>
|
||||
<figure class="twofa-qr-wrap">
|
||||
<img src="<?= h((string) $twofaQr['qr_url']) ?>" width="160" height="160" alt="QR code for mobile sign-in link" class="twofa-qr">
|
||||
<figcaption class="muted">Scan to open the hub on your phone<?php if (!empty($twofaQr['short_url'])): ?> · <a href="<?= h((string) $twofaQr['short_url']) ?>" rel="noopener"><?= h((string) $twofaQr['short_url']) ?></a><?php endif; ?></figcaption>
|
||||
</figure>
|
||||
<?php endif; ?>
|
||||
<label><span data-i18n="twofa.code">Authentication code</span>
|
||||
<input id="twofa-code-input" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code" required autofocus></label>
|
||||
<button type="submit" data-i18n="twofa.submit">Continue</button>
|
||||
|
||||
Reference in New Issue
Block a user