Files
Moon-Household-Budget/templates/settings.html
tonym b5a136ed41 Add WebAuthn passkey auth (dormant behind AUTH_ENABLED)
- auth.py: passkey credential store (public keys only) + env-driven RP config.
- auth_routes.py: /login, /enroll, /logout + /auth/* ceremony endpoints +
  before_request gate (no-op unless AUTH_ENABLED=1).
- login/enroll pages (SimpleWebAuthn browser); settings page lists + removes
  enrolled devices. requirements: webauthn==2.7.1.
- Deploys OFF: no behavior change until enabled after the hostname/cert exist.
  Closes groundwork for #5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:03:09 -05:00

84 lines
4.3 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div style="max-width:640px;">
<h1>⚙️ Settings</h1>
<p style="color:#7A6E68;margin-bottom:1.25rem;">
Secrets are stored on the server (in the data volume) and are never shown
here once saved. Leave a password field blank to keep the current value.
</p>
{% if saved %}
<div class="alert-banner" style="background:#E7F3E9;border-color:#BFE0C5;margin-bottom:1rem;">
<span>✓ Settings saved.</span>
</div>
{% endif %}
<form method="POST" action="{{ url_for('settings_view') }}">
<fieldset style="border:1px solid #EDE8E2;border-radius:10px;padding:1rem 1.25rem;margin-bottom:1.25rem;">
<legend style="padding:0 .5rem;font-weight:600;">📧 Email reminders (Gmail)</legend>
<label style="display:block;margin-bottom:.9rem;">
<span style="display:block;font-size:.85rem;color:#7A6E68;margin-bottom:.25rem;">Gmail address</span>
<input type="email" name="gmail_address" value="{{ settings.gmail_address }}"
placeholder="you@gmail.com" style="width:100%;padding:.55rem;border:1px solid #cfcfd4;border-radius:8px;">
</label>
<label style="display:block;margin-bottom:.9rem;">
<span style="display:block;font-size:.85rem;color:#7A6E68;margin-bottom:.25rem;">
Gmail app password
{% if secrets_status.gmail_app_password %}
<strong style="color:#5A9E68;">· set ✓</strong>
{% else %}
<strong style="color:#C57A6E;">· not set</strong>
{% endif %}
</span>
<input type="password" name="gmail_app_password" autocomplete="new-password"
placeholder="{% if secrets_status.gmail_app_password %}•••••••••••• (leave blank to keep){% else %}16-character app password{% endif %}"
style="width:100%;padding:.55rem;border:1px solid #cfcfd4;border-radius:8px;">
<span style="display:block;font-size:.78rem;color:#B0A89E;margin-top:.25rem;">
Not your normal password — create an
<a href="https://myaccount.google.com/apppasswords" target="_blank" rel="noopener">app password</a> in your Google account.
</span>
</label>
<label style="display:block;margin-bottom:.9rem;">
<span style="display:block;font-size:.85rem;color:#7A6E68;margin-bottom:.25rem;">Send reminders to (optional)</span>
<input type="email" name="reminder_recipient" value="{{ settings.reminder_recipient }}"
placeholder="defaults to the Gmail address above" style="width:100%;padding:.55rem;border:1px solid #cfcfd4;border-radius:8px;">
</label>
<label style="display:flex;align-items:center;gap:.5rem;">
<input type="checkbox" name="reminders_enabled" {% if settings.reminders_enabled %}checked{% endif %}>
<span>Email reminders enabled</span>
</label>
</fieldset>
<button type="submit" class="btn btn-primary">Save settings</button>
</form>
<fieldset style="border:1px solid #EDE8E2;border-radius:10px;padding:1rem 1.25rem;margin-top:1.5rem;">
<legend style="padding:0 .5rem;font-weight:600;">🔑 Passkeys (sign-in)</legend>
<p style="font-size:.85rem;color:#7A6E68;margin-top:0;">
Login is currently <strong>{{ 'ON' if auth_enabled else 'OFF' }}</strong>.
Devices enrolled with a passkey (Touch ID / Face ID / security key):
</p>
{% if passkeys %}
<ul style="list-style:none;padding:0;margin:0 0 .75rem;">
{% for d in passkeys %}
<li style="display:flex;align-items:center;justify-content:space-between;border:1px solid #EDE8E2;border-radius:8px;padding:.5rem .75rem;margin-bottom:.4rem;">
<span>🔑 {{ d.label }}</span>
<form method="POST" action="{{ url_for('auth.device_remove') }}" onsubmit="return confirm('Remove this passkey?');" style="margin:0;">
<input type="hidden" name="id" value="{{ d.id }}">
<button type="submit" style="background:none;border:0;color:#C57A6E;cursor:pointer;font-size:.8rem;">Remove</button>
</form>
</li>
{% endfor %}
</ul>
{% else %}
<p style="font-size:.85rem;color:#B0A89E;">No devices enrolled yet.</p>
{% endif %}
<a href="{{ url_for('auth.enroll') }}" class="btn btn-primary" style="display:inline-block;text-decoration:none;">+ Add a device</a>
</fieldset>
</div>
{% endblock %}