feat: add encrypted sync user management

Adds full CRUD for sync users (create/list/detail/edit) behind admin
auth and CSRF protection. Passwords are encrypted at rest and never
decrypted into a template context; only emails may be decrypted for
display on the edit form. Blank password fields on edit preserve the
existing encrypted password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-15 09:41:21 +02:00
parent da6b94ca2f
commit b39842fe2c
7 changed files with 484 additions and 3 deletions

View File

@@ -4,9 +4,16 @@
{% block content %}
<h1>Dashboard</h1>
<p><a href="/users/new">Add user</a></p>
<ul>
{% for user in users %}
<li>{{ user }}</li>
<li>
<a href="/users/{{ user.id }}">{{ user.name }}</a>
&mdash; {{ "enabled" if user.enabled else "disabled" }}
&mdash; {{ user.health_state.value }}
</li>
{% else %}
<li>No users yet.</li>
{% endfor %}
</ul>
{% endblock %}

View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}{{ user.name }} - MyWhoosh Garmin Sync{% endblock %}
{% block content %}
<h1>{{ user.name }}</h1>
<p><a href="/users/{{ user.id }}/edit">Edit</a> | <a href="/">Back to dashboard</a></p>
<dl>
<dt>Enabled</dt>
<dd>{{ "Yes" if user.enabled else "No" }}</dd>
<dt>Health state</dt>
<dd>{{ user.health_state.value }}</dd>
<dt>MyWhoosh state</dt>
<dd>{{ user.mywhoosh_state }}</dd>
<dt>Garmin state</dt>
<dd>{{ user.garmin_state }}</dd>
<dt>Action reason</dt>
<dd>{{ user.action_reason or "-" }}</dd>
<dt>Created at</dt>
<dd>{{ user.created_at }}</dd>
<dt>Updated at</dt>
<dd>{{ user.updated_at }}</dd>
</dl>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block title %}{% if user %}Edit {{ user.name }}{% else %}New User{% endif %} - MyWhoosh Garmin Sync{% endblock %}
{% block content %}
<h1>{% if user %}Edit User{% else %}New User{% endif %}</h1>
<form method="post" action="{{ form_action }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{ user.name if user else '' }}" required>
<label for="mywhoosh_email">MyWhoosh Email</label>
<input type="email" id="mywhoosh_email" name="mywhoosh_email" value="{{ mywhoosh_email }}" required>
<label for="mywhoosh_password">MyWhoosh Password</label>
<input type="password" id="mywhoosh_password" name="mywhoosh_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="garmin_email">Garmin Email</label>
<input type="email" id="garmin_email" name="garmin_email" value="{{ garmin_email }}" required>
<label for="garmin_password">Garmin Password</label>
<input type="password" id="garmin_password" name="garmin_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="enabled">
<input type="checkbox" id="enabled" name="enabled" {% if not user or user.enabled %}checked{% endif %}>
Enabled
</label>
<button type="submit">{% if user %}Save{% else %}Create{% endif %}</button>
</form>
{% endblock %}