Files
PassKeeper/app/templates/auth/login.html
T
nngo b84a6d9245
CI / Python lint (flake8) (push) Has been cancelled
CI / Python syntax check (push) Has been cancelled
CI / Alembic migration chain (push) Has been cancelled
CI / JavaScript syntax check (push) Has been cancelled
CI / Pytest (push) Has been cancelled
CI / Build extension zip (push) Has been cancelled
Aug 26 - Enhance security 4
2026-08-26 14:19:25 -04:00

143 lines
4.6 KiB
HTML

{% extends "base.html" %} {% block title %}Sign In — PassKeeper{% endblock %} {%
block body_class %}auth-page{% endblock %} {% block body %}
<div class="auth-container">
<div class="auth-card">
<div class="auth-logo">
<span class="logo-icon">🔒</span>
<span class="logo-text">PassKeeper</span>
</div>
<!-- Deliberately non-committal: the server returns the same response whether
or not the address was already registered, so that registration cannot
be used to probe which addresses have PassKeeper accounts. Asserting
"account created" here would leak what the API withholds. -->
<p id="register-notice" class="notice-success hidden">
If that email address was available, your account has been created — please sign in below.
Already had an account? Sign in with your existing master password.
</p>
<!-- Step 1: Email + Master Password -->
<div id="login-step-1">
<h1 class="auth-title">Sign in</h1>
<form id="login-form" novalidate>
<div class="form-group">
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
required
autocomplete="email"
placeholder="you@example.com"
/>
</div>
<div class="form-group">
<label for="password">Master password</label>
<div class="input-with-toggle">
<input
type="password"
id="password"
name="password"
required
autocomplete="current-password"
placeholder="Enter master password"
/>
<button
type="button"
class="btn-show-pass"
id="toggle-login-pass"
aria-label="Toggle password visibility"
>
👁
</button>
</div>
</div>
<button
type="submit"
class="btn-primary btn-full"
data-loading-text="Verifying…"
>
Sign in
</button>
</form>
<div class="auth-divider"><span>or</span></div>
<button
type="button"
class="btn-secondary btn-full"
id="btn-passkey-login"
>
🔑 Sign in with Passkey
</button>
<p id="passkey-error" class="form-error hidden"></p>
<p class="auth-footer">
Don't have an account? <a href="/register">Create one</a><br />
Forgot your password? <a href="/recover">Use recovery code</a>
</p>
</div>
<!-- Step 2: TOTP verification (hidden until MFA required) -->
<div id="login-step-2" class="hidden">
<h1 class="auth-title">Two-factor authentication</h1>
<p class="auth-step-hint">
Enter the 6-digit code from your authenticator app.
</p>
<form id="mfa-form" novalidate>
<p id="mfa-error" class="form-error hidden">
Invalid or expired code. Try again.
</p>
<!-- TOTP input (default) -->
<div id="mfa-totp-section" class="form-group">
<label for="mfa-code">Authenticator code</label>
<input
type="text"
id="mfa-code"
name="mfa_code"
inputmode="numeric"
pattern="[0-9]{6}"
maxlength="6"
autocomplete="one-time-code"
placeholder="000000"
/>
</div>
<!-- Backup code input (hidden until user switches) -->
<div id="mfa-backup-section" class="form-group hidden">
<label for="mfa-backup-code">Backup code</label>
<input
type="text"
id="mfa-backup-code"
name="mfa_backup_code"
autocomplete="off"
placeholder="e.g. ab3rqxyz9p"
style="letter-spacing: 0.05em"
/>
</div>
<button
type="submit"
class="btn-primary btn-full"
data-loading-text="Verifying…"
>
Verify
</button>
</form>
<div class="auth-footer">
<button class="btn-text" id="btn-use-backup-code">
Use a backup code instead
</button>
<button class="btn-text hidden" id="btn-use-totp-code">
Use authenticator code instead
</button>
<button class="btn-text" id="btn-back-to-password">
← Back to sign in
</button>
</div>
</div>
</div>
</div>
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='js/crypto.js') }}"></script>
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
{% endblock %}