add admin account recovery via setup secret
All checks were successful
deploy / deploy (push) Successful in 1m33s

When email isn't configured, the login page now hides the magic link
form and shows a recovery link. The /recover page logs the setup secret
to server logs and lets the admin reset their password with it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-21 21:40:53 +00:00
parent 194fec8240
commit b0607621f3
7 changed files with 343 additions and 25 deletions

View File

@@ -1,7 +1,7 @@
defmodule BerrypodWeb.Auth.Login do
use BerrypodWeb, :live_view
alias Berrypod.Accounts
alias Berrypod.{Accounts, Mailer}
@impl true
def render(assigns) do
@@ -38,28 +38,30 @@ defmodule BerrypodWeb.Auth.Login do
</div>
</div>
<.form
:let={f}
for={@form}
id="login_form_magic"
action={~p"/users/log-in"}
phx-submit="submit_magic"
>
<.input
readonly={!!@current_scope}
field={f[:email]}
type="email"
label="Email"
autocomplete="email"
required
phx-mounted={JS.focus()}
/>
<.button variant="primary" class="w-full">
Log in with email <span aria-hidden="true">→</span>
</.button>
</.form>
<%= if @email_configured do %>
<.form
:let={f}
for={@form}
id="login_form_magic"
action={~p"/users/log-in"}
phx-submit="submit_magic"
>
<.input
readonly={!!@current_scope}
field={f[:email]}
type="email"
label="Email"
autocomplete="email"
required
phx-mounted={JS.focus()}
/>
<.button variant="primary" class="w-full">
Log in with email <span aria-hidden="true">→</span>
</.button>
</.form>
<div class="admin-divider">or</div>
<div class="admin-divider">or</div>
<% end %>
<.form
:let={f}
@@ -90,6 +92,13 @@ defmodule BerrypodWeb.Auth.Login do
Log in only this time
</.button>
</.form>
<p :if={!@email_configured} class="text-sm text-center text-base-content/60">
Locked out?
<.link navigate={~p"/recover"} class="font-semibold text-brand hover:underline">
Recover with setup secret
</.link>
</p>
</div>
</Layouts.app>
"""
@@ -104,7 +113,12 @@ defmodule BerrypodWeb.Auth.Login do
form = to_form(%{"email" => email}, as: "user")
{:ok,
assign(socket, form: form, trigger_submit: false, registration_open: !Accounts.has_admin?())}
assign(socket,
form: form,
trigger_submit: false,
registration_open: !Accounts.has_admin?(),
email_configured: Mailer.email_configured?()
)}
end
@impl true