defmodule Rsh26poolWeb.Auth do @moduledoc """ Token-based authentication. When a user creates or joins a room we generate a random token, persist it on their `Participant` row, and store that token in the session cookie. On every request the token is looked up to identify the current participant. This is the token scheme described in the proposal (a random string whose owner is recorded in the database, kept in a cookie). """ use Rsh26poolWeb, :verified_routes import Plug.Conn alias Phoenix.LiveView alias Rsh26pool.Voting @token_key "participant_token" ## Plug (controllers) --------------------------------------------------- @doc "Assigns `:current_participant` from the session token (may be nil)." def fetch_current_participant(conn, _opts) do participant = participant_from_token(get_session(conn, @token_key)) assign(conn, :current_participant, participant) end @doc "Persists a participant's token in the session and assigns it." def log_in_participant(conn, participant) do conn |> put_session(@token_key, participant.token) |> assign(:current_participant, participant) end @doc "Clears the session token (logout)." def log_out_participant(conn) do conn |> delete_session(@token_key) |> assign(:current_participant, nil) end ## LiveView on_mount hooks --------------------------------------------- @doc """ `on_mount` hooks: * `:mount_current_participant` — assigns `:current_participant` (may be nil) * `:require_participant` — requires the viewer to belong to the room in the URL, otherwise redirects to the enter page * `:require_owner` — requires the viewer to be the owner of the room """ def on_mount(:mount_current_participant, _params, session, socket) do {:cont, assign_current_participant(socket, session)} end def on_mount(:require_participant, params, session, socket) do socket = assign_current_participant(socket, session) participant = socket.assigns.current_participant room_number = params["room_number"] if participant && participant.room && participant.room.room_number == room_number do {:cont, socket} else socket = socket |> LiveView.put_flash(:error, "このルームに参加するには入室してください。") |> LiveView.redirect(to: ~p"/enter?#{[room: room_number]}") {:halt, socket} end end def on_mount(:require_owner, params, session, socket) do socket = assign_current_participant(socket, session) participant = socket.assigns.current_participant room_number = params["room_number"] if participant && participant.room && participant.room.room_number == room_number && participant.role == "owner" do {:cont, socket} else socket = socket |> LiveView.put_flash(:error, "この管理ページにアクセスする権限がありません。") |> LiveView.redirect(to: ~p"/") {:halt, socket} end end defp assign_current_participant(socket, session) do Phoenix.Component.assign_new(socket, :current_participant, fn -> participant_from_token(session[@token_key]) end) end defp participant_from_token(nil), do: nil defp participant_from_token(token), do: Voting.get_participant_by_token(token) end