diff --git a/lib/algora/organizations/organizations.ex b/lib/algora/organizations/organizations.ex index f2cf6861f..3caeb824d 100644 --- a/lib/algora/organizations/organizations.ex +++ b/lib/algora/organizations/organizations.ex @@ -97,6 +97,10 @@ defmodule Algora.Organizations do ) end + def fetch_member(org_id, user_id) do + Repo.fetch_by(Member, org_id: org_id, user_id: user_id) + end + def list_org_contractors(org) do Repo.all( from u in User, diff --git a/lib/algora/organizations/schemas/member.ex b/lib/algora/organizations/schemas/member.ex index 2d59d157f..875dd44c5 100644 --- a/lib/algora/organizations/schemas/member.ex +++ b/lib/algora/organizations/schemas/member.ex @@ -4,8 +4,10 @@ defmodule Algora.Organizations.Member do alias Algora.Accounts.User + @roles [:admin, :mod, :expert] + typed_schema "members" do - field :role, Ecto.Enum, values: [:admin, :mod, :expert] + field :role, Ecto.Enum, values: @roles belongs_to :org, User belongs_to :user, User @@ -13,6 +15,8 @@ defmodule Algora.Organizations.Member do timestamps() end + def roles, do: @roles + def changeset(member, params) do member |> cast(params, [:role]) diff --git a/lib/algora_web/controllers/org_auth.ex b/lib/algora_web/controllers/org_auth.ex new file mode 100644 index 000000000..31b9094bc --- /dev/null +++ b/lib/algora_web/controllers/org_auth.ex @@ -0,0 +1,25 @@ +defmodule AlgoraWeb.OrgAuth do + @moduledoc false + import Phoenix.LiveView + + alias Algora.Organizations + + def on_mount(:ensure_admin, params, session, socket) do + ensure_role([:admin], params, session, socket) + end + + def on_mount(:ensure_mod, params, session, socket) do + ensure_role([:mod, :admin], params, session, socket) + end + + defp ensure_role(allowed_roles, _params, _session, socket) do + %{current_org: current_org, current_user: current_user} = socket.assigns + + with {:ok, member} <- Organizations.fetch_member(current_org.id, current_user.id), + true <- member.role in allowed_roles do + {:cont, socket} + else + _ -> {:halt, redirect(socket, to: "/org/#{current_org.handle}")} + end + end +end diff --git a/lib/algora_web/router.ex b/lib/algora_web/router.ex index e682928f4..b754dc340 100644 --- a/lib/algora_web/router.ex +++ b/lib/algora_web/router.ex @@ -82,13 +82,23 @@ defmodule AlgoraWeb.Router do # live "/org/:org_handle/projects/:id", Project.ViewLive live "/org/:org_handle/jobs", Org.JobsLive, :index live "/org/:org_handle/jobs/:id", Org.JobLive, :index - live "/org/:org_handle/transactions", Org.TransactionsLive, :index live "/org/:org_handle/chat", ChatLive, :index - live "/org/:org_handle/settings", Org.SettingsLive, :edit live "/org/:org_handle/team", Org.TeamLive, :index live "/org/:org_handle/leaderboard", Org.LeaderboardLive, :index end + live_session :org_admin, + layout: {AlgoraWeb.Layouts, :org}, + on_mount: [ + {AlgoraWeb.UserAuth, :ensure_authenticated}, + {AlgoraWeb.UserAuth, :current_user}, + AlgoraWeb.Org.Nav, + {AlgoraWeb.OrgAuth, :ensure_admin} + ] do + live "/org/:org_handle/settings", Org.SettingsLive, :edit + live "/org/:org_handle/transactions", Org.TransactionsLive, :index + end + live_session :org2, on_mount: [{AlgoraWeb.UserAuth, :current_user}, AlgoraWeb.Org.Nav] do live "/org/:org_handle/projects/:id", DevLive diff --git a/test/algora_web/live/org_auth_test.exs b/test/algora_web/live/org_auth_test.exs new file mode 100644 index 000000000..9889c6f6a --- /dev/null +++ b/test/algora_web/live/org_auth_test.exs @@ -0,0 +1,59 @@ +defmodule AlgoraWeb.Org.SettingsLiveTest do + use AlgoraWeb.ConnCase, async: true + + import Algora.Factory + import Ecto.Changeset + import Phoenix.LiveViewTest + + alias Algora.Organizations.Member + alias Algora.Repo + + setup %{conn: conn} do + conn = Phoenix.ConnTest.init_test_session(conn, %{}) + + %{ + conn: conn, + org: insert!(:organization) + } + end + + # Helper function to test auth requirements for org routes + defp assert_org_route_auth(conn, org, path, allowed_roles) do + test_path = "/org/#{org.handle}#{path}" + + # Test unauthorized access + assert {:error, {:redirect, %{to: to}}} = live(conn, test_path) + assert to == ~p"/auth/login?return_to=#{test_path}" + + # # Test non-member access + user = insert!(:user) + conn_with_user = AlgoraWeb.UserAuth.put_current_user(conn, user) + assert {:error, {:redirect, %{to: to}}} = live(conn_with_user, test_path) + assert to == "/org/#{org.handle}" + + # # Test access for each role + member = insert!(:member, user: user, org: org) + + for role <- Member.roles() do + member |> change(role: role) |> Repo.update!() + + if role in allowed_roles do + assert {:ok, _view, _html} = live(conn_with_user, test_path) + else + assert {:error, {:redirect, %{to: to}}} = live(conn_with_user, test_path) + assert to == "/org/#{org.handle}" + end + end + end + + describe "protected org routes" do + for {path, roles} <- %{ + "/settings" => [:admin], + "/transactions" => [:admin] + } do + test "#{path} page", %{conn: conn, org: org} do + assert_org_route_auth(conn, org, unquote(path), unquote(Macro.escape(roles))) + end + end + end +end