vienalatina/apps/board/templates/recover.html
Claude e11016a8e9
Refuse before the form, not after the password
A member followed his invitation link, chose a password, typed it twice,
pressed save, and was shown the name of an environment variable. The
server knew from the first byte of that request that it could not save
anything: without GITEA_ADMIN_TOKEN it cannot set a password in Gitea.
It asked him to do the work anyway.

Three places had the same shape, all now checked up front through a new
gitea.admin_configured(), mirroring mail.configured():

- the invitation page answers 503 with an explanation and no password
  field, identically for a real and an invented token so it cannot be
  used to probe for live ones
- /recuperar refuses instead of mailing a link to a page that could only
  apologise — and its deliberately identical answer would have hidden
  that from the admin as well as the member
- the sign-in page stops offering recovery it cannot complete

Also: a 404 from admin_set_password now names the real cause. A member
added without "crear también su cuenta" has no Gitea account, so the
password change is aimed at nothing, and "Gitea rechazó el cambio de
contraseña (404)" blames Gitea for an account that was never made.

deploy-board.sh warns about settings that are present but empty. The
previous check looked for missing names, and GITEA_ADMIN_TOKEN= has a
name — which is why the deploy that led to this said nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NizVpJ2dwzCbjCrTLCjeHn
2026-09-25 19:20:44 +00:00

49 lines
1.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Recuperar contraseña{% endblock %}
{% block main %}
{% if unavailable %}
{# Refusing out loud rather than accepting the address and sending nothing.
Silence here would be indistinguishable from success — that is the point of
the identical answer below — so the one case where the server knows in
advance that it cannot help has to be said plainly. #}
<article class="card card--center">
<h1>Todavía no</h1>
<p class="muted">
Este servidor aún no puede cambiar contraseñas, así que un enlace de
recuperación no serviría de nada. Escribe a un administrador y te darán
acceso a mano.
</p>
<p><a class="linkish" href="{{ url_for('auth.login') }}">Volver a entrar</a></p>
</article>
{% elif sent %}
<article class="card card--center">
<h1>Revisa tu correo</h1>
{# Says the same thing whether or not that address belongs to a member.
Confirming it would turn this form into a way to find out who is one. #}
<p class="muted">
Si esa dirección pertenece a un miembro, le enviamos un enlace para elegir
una contraseña nueva. Caduca en una hora.
</p>
<p><a class="linkish" href="{{ url_for('auth.login') }}">Volver a entrar</a></p>
</article>
{% else %}
<form class="card" method="post" action="{{ url_for('auth.recover') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<h1>¿Olvidaste tu contraseña?</h1>
<p class="muted">
Escribe el correo con el que te dieron de alta y te enviamos un enlace para
elegir una nueva.
</p>
<label for="email">Correo</label>
<input id="email" name="email" type="email" required autocomplete="email">
<div class="actions">
<button class="btn" type="submit">Enviar enlace</button>
<a class="linkish" href="{{ url_for('auth.login') }}">Cancelar</a>
</div>
</form>
{% endif %}
{% endblock %}