#5427 "A password reset doesn't end the sessions already signed in with the old password": users.sessions_since (migration 0038) is set on reset; refresh_session_user ends a session whose login_at is earlier. An admin resetting their own password carries on signed in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
6.4 KiB
HTML
122 lines
6.4 KiB
HTML
{# settings/user.html — one account (#5336): who it is and how it signs in,
|
||
then its name, role and password, disabling it, and Delete at the foot.
|
||
A refusal (yourself, the last admin, records that name it) is said beside
|
||
the control rather than discovered by pressing it. #}
|
||
{% extends "base.html" %}
|
||
{% from "_macros.html" import crumbs, page_header, status_badge %}
|
||
{% from "settings/_tabs.html" import settings_tabs %}
|
||
{% from "account/_email_choices.html" import email_section with context %}
|
||
{% block title %}{{ user.username }} — Users — Steward{% endblock %}
|
||
{% block section_tabs %}{{ settings_tabs("users") }}{% endblock %}
|
||
{% block breadcrumb %}{{ crumbs([("Settings", "/settings/"), ("Users", "/settings/users/"), (user.username, "")]) }}{% endblock %}
|
||
{% block content %}
|
||
{% set local = user.auth_source == "local" %}
|
||
{{ page_header(user.username) }}
|
||
|
||
<p class="page-verdict">
|
||
{% if user.is_active %}{{ user.role.value|capitalize }}{% else %}{{ status_badge("inactive", "Disabled") }} {{ user.role.value }}{% endif %}
|
||
· signs in with {{ source }}
|
||
· {% if last_sign_in %}last signed in {{ last_sign_in|ago }}{% else %}<span class="tone-dim">no sign-in on record</span>{% endif %}
|
||
· {% if not tokens %}no AI tokens{% elif user.id == session.user_id %}<a href="/ai-access/">{{ tokens }} AI token{{ 's' if tokens != 1 }}</a>{% else %}{{ tokens }} AI token{{ 's' if tokens != 1 }}{% endif %}
|
||
· added {{ user.created_at|when }}
|
||
</p>
|
||
|
||
{% if error %}<div class="alert alert-error" role="alert">{{ error }}</div>
|
||
{% elif done %}<div class="alert alert-success" role="status">{{ done }}</div>{% endif %}
|
||
|
||
<form method="post" action="/settings/users/{{ user.id }}" class="form-page">{{ csrf_field() }}
|
||
<section class="card form-section" aria-labelledby="u-account">
|
||
<h2 id="u-account" class="section-title">Name</h2>
|
||
<div class="form-pair">
|
||
<div class="field">
|
||
<label class="field-label" for="u-username">Username</label>
|
||
<input class="input" id="u-username" type="text" name="username" maxlength="64" value="{{ user.username }}" required>
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label" for="u-email">Email</label>
|
||
<input class="input" id="u-email" type="email" name="email" maxlength="255" value="{{ user.email }}" required>
|
||
</div>
|
||
</div>
|
||
{% if not local %}<p class="field-hint">{{ source }} finds this account by its own id, not by name, so renaming it here does not stop it signing in.</p>{% endif %}
|
||
</section>
|
||
|
||
{% set zone = (user.time_zone or "UTC").split("/", 1)[-1]|replace("_", " ") %}
|
||
{{ email_section(user, defaults, "this account's own zone (" ~ zone ~ ")", with_address=false, unreachable=not reachable) }}
|
||
|
||
<div class="form-actions"><button type="submit" class="btn">Save</button></div>
|
||
</form>
|
||
|
||
<section class="card form-section" aria-labelledby="u-role">
|
||
<h2 id="u-role" class="section-title">Role</h2>
|
||
{% if not local %}
|
||
<p>{{ user.role.value|capitalize }}. {{ source }} sets it from the account's groups at every sign-in; the mapping is in Settings › <a href="/settings/auth/">Authentication</a>.</p>
|
||
{% else %}
|
||
<form method="post" action="/settings/users/{{ user.id }}/role" class="inline-form">{{ csrf_field() }}
|
||
<select class="input" name="role" aria-label="Role">
|
||
{% for r in roles %}
|
||
<option value="{{ r.value }}" {% if user.role == r %}selected{% endif %}
|
||
{% if demote_refused and r.value != 'admin' %}disabled{% endif %}>{{ r.value|capitalize }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<button type="submit" class="btn btn-ghost">Change role</button>
|
||
</form>
|
||
{% if demote_refused %}<p class="field-hint">{{ demote_refused }}</p>{% endif %}
|
||
{% endif %}
|
||
</section>
|
||
|
||
{% if local %}
|
||
<form method="post" action="/settings/users/{{ user.id }}/password" class="form-page">{{ csrf_field() }}
|
||
<section class="card form-section" aria-labelledby="u-password">
|
||
<h2 id="u-password" class="section-title">Password</h2>
|
||
<div class="form-pair">
|
||
<div class="field">
|
||
<label class="field-label" for="u-pw">New password</label>
|
||
<input class="input" id="u-pw" type="password" name="password" minlength="{{ min_password }}" required autocomplete="new-password">
|
||
<span class="field-hint">At least {{ min_password }} characters. Give it to them yourself; Steward sends no email for it. Anywhere they're signed in now, they're signed out.</span>
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label" for="u-pw2">Again</label>
|
||
<input class="input" id="u-pw2" type="password" name="confirm" required autocomplete="new-password">
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="btn btn-ghost">Set password</button>
|
||
</section>
|
||
</form>
|
||
{% endif %}
|
||
|
||
<section class="card form-section" aria-labelledby="u-access">
|
||
<h2 id="u-access" class="section-title">{{ "Disable" if user.is_active else "Enable" }}</h2>
|
||
{% if user.is_active %}
|
||
<p class="field-hint">A disabled account is signed out at its next click and its AI tokens stop working. Everything it made stays.</p>
|
||
{% if disable_refused %}
|
||
<p class="field-hint">{{ disable_refused }}</p>
|
||
{% else %}
|
||
<form method="post" action="/settings/users/{{ user.id }}/active"
|
||
data-confirm="Disable {{ user.username }}? They are signed out at their next click and can't sign in until an admin enables the account.">{{ csrf_field() }}
|
||
<button type="submit" class="btn btn-ghost">Disable account</button>
|
||
</form>
|
||
{% endif %}
|
||
{% else %}
|
||
<p class="field-hint">This account can't sign in, and its AI tokens don't work.</p>
|
||
<form method="post" action="/settings/users/{{ user.id }}/active">{{ csrf_field() }}
|
||
<button type="submit" class="btn">Enable account</button>
|
||
</form>
|
||
{% endif %}
|
||
</section>
|
||
|
||
<section class="danger-zone" aria-labelledby="u-delete">
|
||
<h2 id="u-delete" class="section-title">Delete this account</h2>
|
||
{% if delete_refused %}
|
||
<p class="field-hint">{{ delete_refused }}</p>
|
||
{% elif holds %}
|
||
<p class="field-hint">{{ user.username }} made {{ holds|join(", ") }}, and Steward keeps who did, so this account can't be deleted. Disable it instead.</p>
|
||
{% else %}
|
||
<p class="field-hint">Its AI tokens go with it. The audit log keeps its name.</p>
|
||
<form method="post" action="/settings/users/{{ user.id }}/delete"
|
||
data-confirm="Delete {{ user.username }}? Their AI tokens go too. This can't be undone.">{{ csrf_field() }}
|
||
<button class="btn btn-danger" type="submit">Delete account</button>
|
||
</form>
|
||
{% endif %}
|
||
</section>
|
||
{% endblock %}
|