berrypod/lib/berrypod_web/live/setup/recover.ex

119 lines
3.4 KiB
Elixir
Raw Normal View History

defmodule BerrypodWeb.Setup.Recover do
use BerrypodWeb, :live_view
require Logger
alias Berrypod.{Accounts, Setup}
@impl true
def mount(_params, _session, socket) do
cond do
get_user(socket) ->
{:ok, push_navigate(socket, to: ~p"/admin")}
not Accounts.has_admin?() ->
{:ok, push_navigate(socket, to: ~p"/setup")}
true ->
Logger.warning("Account recovery requested. Setup secret: #{Setup.setup_secret()}")
{:ok,
socket
|> assign(:page_title, "Account recovery")
|> assign(:require_secret, Application.get_env(:berrypod, :env, :dev) == :prod)
|> assign(:form, to_form(%{}, as: :recover))}
end
end
defp get_user(socket) do
case socket.assigns do
%{current_scope: %{user: user}} when not is_nil(user) -> user
_ -> nil
end
end
@impl true
def handle_event("recover", %{"recover" => params}, socket) do
secret = params["secret"] || ""
password = params["password"] || ""
cond do
socket.assigns.require_secret and
not Plug.Crypto.secure_compare(secret, Setup.setup_secret()) ->
{:noreply, put_flash(socket, :error, "Wrong setup secret")}
String.length(password) < 12 ->
{:noreply, put_flash(socket, :error, "Password must be at least 12 characters")}
true ->
user = Accounts.get_first_admin()
case Accounts.update_user_password(user, %{password: password}) do
{:ok, {_user, _tokens}} ->
token = Accounts.generate_login_token(user)
{:noreply, redirect(socket, to: ~p"/recover/login/#{token}")}
{:error, changeset} ->
message =
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, _} -> msg end)
|> Enum.flat_map(fn {_field, msgs} -> msgs end)
|> Enum.join(", ")
{:noreply, put_flash(socket, :error, message)}
end
end
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="mx-auto max-w-sm flex flex-col gap-4">
<.header>
Account recovery
<:subtitle>
Reset your admin password using the setup secret from your server logs.
</:subtitle>
</.header>
<div class="admin-alert admin-alert-info">
<.icon name="hero-information-circle" class="size-6 shrink-0" />
<p>A recovery secret has been printed to your server logs.</p>
</div>
<.form for={@form} phx-submit="recover">
<.input
:if={@require_secret}
name="recover[secret]"
value=""
type="password"
label="Setup secret"
autocomplete="off"
required
/>
<.input
name="recover[password]"
value=""
type="password"
label="New password"
autocomplete="new-password"
required
/>
<p class="text-xs text-base-content/60 -mt-2">Minimum 12 characters</p>
<.button variant="primary" class="w-full">
Reset password and log in
</.button>
</.form>
<p class="text-sm text-center text-base-content/60">
<.link navigate={~p"/users/log-in"} class="font-semibold text-brand hover:underline">
Back to login
</.link>
</p>
</div>
</Layouts.app>
"""
end
end