diff --git a/lib/cms_web/components/layouts/app.html.heex b/lib/cms_web/components/layouts/app.html.heex index 3c0707f..fbe0799 100644 --- a/lib/cms_web/components/layouts/app.html.heex +++ b/lib/cms_web/components/layouts/app.html.heex @@ -11,7 +11,9 @@ </section> <section class="flex flex-row"> - <.link href={~p"/admin/session"} method="delete" class="hover:underline">sign out</.link> + <.link href={~p"/admin/session/destroy?return_to=#{@current_path}"} class="hover:underline"> + sign out + </.link> </section> </div> <div class="flex flex-col md:flex-row mx-auto max-w-4xl"> @@ -28,3 +30,9 @@ {@inner_content} </main> </div> +<div + :if={not @admin?} + class="fixed right-0 bottom-0 p-2 text-transparent underline hover:text-current" +> + <.link href={~p"/sign-in?return_to=#{@current_path}"}>sign in</.link> +</div> diff --git a/lib/cms_web/controllers/admin_auth.ex b/lib/cms_web/controllers/admin_auth.ex index 3f8e346..e671dc6 100644 --- a/lib/cms_web/controllers/admin_auth.ex +++ b/lib/cms_web/controllers/admin_auth.ex @@ -5,21 +5,33 @@ defmodule CMSWeb.AdminAuth do import Phoenix.Controller import Plug.Conn - def log_in_admin(conn) do + def log_in_admin(conn, params) do conn |> renew_session() |> put_session(:admin?, true) - |> redirect(to: ~p"/") + |> redirect(to: params["return_to"] || ~p"/admin") end - def log_out_admin(conn) do + def log_out_admin(conn, params) do if live_socket_id = get_session(conn, :live_socket_id) do CMSWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) end conn |> renew_session() - |> redirect(to: ~p"/") + |> redirect(to: params["return_to"] || ~p"/") + end + + def mount_admin(%Plug.Conn{} = conn, _opts) do + assign(conn, :admin?, admin?(conn)) + end + + def require_admin(%Plug.Conn{assigns: %{admin?: true}} = conn, _opts) do + conn + end + + def require_admin(conn, _opts) do + redirect(conn, to: ~p"/sign-in?return_to=#{conn.request_path}") end def correct_password?(password) do @@ -28,6 +40,10 @@ defmodule CMSWeb.AdminAuth do Argon2.verify_pass(password, password_hash) end + def on_mount(:default, _params, session, socket) do + {:cont, Phoenix.Component.assign(socket, :admin?, admin?(session))} + end + ## private defp renew_session(conn) do @@ -37,4 +53,10 @@ defmodule CMSWeb.AdminAuth do |> configure_session(renew: true) |> clear_session() end + + defp admin?(%Plug.Conn{} = conn) do + Plug.Conn.get_session(conn, :admin?, false) == true + end + + defp admin?(%{} = session), do: Map.get(session, "admin?", false) == true end diff --git a/lib/cms_web/controllers/admin_mode.ex b/lib/cms_web/controllers/admin_mode.ex deleted file mode 100644 index 2c67d43..0000000 --- a/lib/cms_web/controllers/admin_mode.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule CMSWeb.AdminMode do - @moduledoc false - use CMSWeb, :live_view - - def admin_mode(%Plug.Conn{} = conn, _opts) do - Plug.Conn.assign(conn, :admin?, admin?(conn)) - end - - def on_mount(:default, _params, session, socket) do - {:cont, assign(socket, :admin?, admin?(session))} - end - - defp admin?(%Plug.Conn{} = conn) do - Plug.Conn.get_session(conn, :admin?, false) == true - end - - defp admin?(%{} = session), do: Map.get(session, :admin?, false) == true -end diff --git a/lib/cms_web/controllers/admin_session_controller.ex b/lib/cms_web/controllers/admin_session_controller.ex index 139d944..a205f39 100644 --- a/lib/cms_web/controllers/admin_session_controller.ex +++ b/lib/cms_web/controllers/admin_session_controller.ex @@ -3,21 +3,19 @@ defmodule CMSWeb.AdminSessionController do alias CMSWeb.AdminAuth - def create(conn, %{"password" => password}) do + def create(conn, %{"password" => password} = params) do if AdminAuth.correct_password?(password) do - AdminAuth.log_in_admin(conn) + AdminAuth.log_in_admin(conn, params) else - redirect(conn, to: ~p"/admin/sign-in") + redirect(conn, to: ~p"/sign-in") end end def create(conn, _params) do - redirect(conn, to: ~p"/admin/sign-in") + redirect(conn, to: ~p"/sign-in") end - def destroy(conn, _params) do - conn - |> AdminAuth.log_out_admin() - |> redirect(to: ~p"/") + def destroy(conn, params) do + AdminAuth.log_out_admin(conn, params) end end diff --git a/lib/cms_web/controllers/globals.ex b/lib/cms_web/controllers/globals.ex new file mode 100644 index 0000000..5001685 --- /dev/null +++ b/lib/cms_web/controllers/globals.ex @@ -0,0 +1,18 @@ +defmodule CMSWeb.Globals do + @moduledoc false + use CMSWeb, :live_view + + def assign_globals(%Plug.Conn{} = conn, _opts) do + Plug.Conn.assign(conn, :current_path, conn.request_path) + end + + def on_mount(:default, _params, _session, socket) do + socket = + attach_hook(socket, :assign_handle_params_globals, :handle_params, fn _params, uri, socket -> + %URI{path: current_path} = URI.parse(uri) + {:cont, assign(socket, :current_path, current_path)} + end) + + {:cont, socket} + end +end diff --git a/lib/cms_web/live/admin_login_live.ex b/lib/cms_web/live/admin_login_live.ex index 2d97891..ef229cd 100644 --- a/lib/cms_web/live/admin_login_live.ex +++ b/lib/cms_web/live/admin_login_live.ex @@ -3,27 +3,37 @@ defmodule CMSWeb.AdminLoginLive do use CMSWeb, :live_view @impl true - def mount(_params, _session, socket) do - socket = assign(socket, :form, to_form(%{"password" => ""})) + def mount(params, _session, socket) do + socket = assign(socket, :form, to_form(%{"password" => "", "return_to" => params["return_to"]})) - {:ok, socket} + {:ok, socket, layout: false} end @impl true def render(assigns) do ~H""" - <h1 class="font-bold text-lg mb-4">Sign in</h1> - - <.form for={@form} action={~p"/admin/session"}> - <input - type="password" - placeholder="password" - id={@form[:password].id} - name={@form[:password].name} - value={@form[:password].value} - required - /> - </.form> + <main class="flex flex-col w-screen h-screen fixed justify-center items-center"> + <.form for={@form} action={~p"/admin/session/create"} class="flex flex-col gap-y-2"> + <input + type="hidden" + id={@form[:return_to].id} + name={@form[:return_to].name} + value={@form[:return_to].value} + /> + <input + type="password" + placeholder="password" + id={@form[:password].id} + name={@form[:password].name} + value={@form[:password].value} + required + /> + <div class="flex flex-col items-end"> + <button type="submit" class="font-bold hover:underline">sign in</button> + <.link href={~p"/"} class="hover:underline">cancel</.link> + </div> + </.form> + </main> """ end end diff --git a/lib/cms_web/router.ex b/lib/cms_web/router.ex index e5c97de..6a5885b 100644 --- a/lib/cms_web/router.ex +++ b/lib/cms_web/router.ex @@ -1,9 +1,11 @@ defmodule CMSWeb.Router do use CMSWeb, :router - import CMSWeb.AdminMode + import CMSWeb.AdminAuth + import CMSWeb.Globals - alias CMSWeb.AdminMode + alias CMSWeb.AdminAuth + alias CMSWeb.Globals pipeline :browser do plug :accepts, ["html"] @@ -12,23 +14,37 @@ defmodule CMSWeb.Router do plug :put_root_layout, html: {CMSWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers - plug :admin_mode + plug :assign_globals end - live_session :default, on_mount: AdminMode do + pipeline :supports_admin_action do + plug :mount_admin + end + + pipeline :requires_admin do + plug :mount_admin + plug :require_admin + end + + live_session :default, on_mount: [AdminAuth, Globals] do scope "/", CMSWeb do pipe_through :browser + pipe_through :supports_admin_action get "/", PageController, :home get "/writing", PageController, :writing get "/microblog", PageController, :microblog - live "/admin", AdminLive + live "/sign-in", AdminLoginLive + post "/admin/session/create", AdminSessionController, :create + get "/admin/session/destroy", AdminSessionController, :destroy + end - live "/admin/sign-in", AdminLoginLive + scope "/admin", CMSWeb do + pipe_through :browser + pipe_through :requires_admin - post "/admin/session", AdminSessionController, :create - delete "/admin/session", AdminSessionController, :destroy + live "/", AdminLive end end