diff --git a/frontend/src/styles/_admin.scss b/frontend/src/styles/_admin.scss new file mode 100644 index 000000000..b52be0d14 --- /dev/null +++ b/frontend/src/styles/_admin.scss @@ -0,0 +1,35 @@ +.sa-admin-navbar { + position: fixed; + top: 50px; + left: 0px; + width: 100%; + z-index: 1; + background: $sa-gray3; + + a { + color: $sa-dark-blue; + font-weight: 500; + text-transform: uppercase; + padding: 5px; + } + + .first-row, .second-row { + padding: 0px 20px; + } + + .first-row a { + font-size: 1.1em; + } + + .second-row a { + font-size: 0.85em; + } + + .second-row { + background: $sa-gray4; + } + + a.pt-button::after { + display: none !important; + } +} diff --git a/frontend/src/styles/_variables.scss b/frontend/src/styles/_variables.scss index a84e0fe96..6f90a8899 100644 --- a/frontend/src/styles/_variables.scss +++ b/frontend/src/styles/_variables.scss @@ -10,6 +10,12 @@ $pt-font-size: 14px !default; $sa-dark-blue: #101e35 !default; $sa-darker-blue: darken($sa-dark-blue, 5%); +$sa-gray5: #F2F4F7; +$sa-gray4: darken($sa-gray5, 5%); +$sa-gray3: darken($sa-gray5, 10%); +$sa-gray2: darken($sa-gray5, 15%); +$sa-gray1: darken($sa-gray5, 20%); + // Layout Variables $sa-header-bg-color: black; diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss index b2aeb7575..3765eed6d 100644 --- a/frontend/src/styles/index.scss +++ b/frontend/src/styles/index.scss @@ -2,3 +2,5 @@ @import 'layout'; @import 'session'; + +@import 'admin'; diff --git a/lib/cadet_web/controllers/admin_controller.ex b/lib/cadet_web/controllers/admin_controller.ex new file mode 100644 index 000000000..3520d940f --- /dev/null +++ b/lib/cadet_web/controllers/admin_controller.ex @@ -0,0 +1,7 @@ +defmodule CadetWeb.AdminController do + use CadetWeb, :controller + + def index(conn, _) do + render(conn, "index.html") + end +end diff --git a/lib/cadet_web/plug/ensure_roles.ex b/lib/cadet_web/plug/ensure_roles.ex new file mode 100644 index 000000000..8f14f834c --- /dev/null +++ b/lib/cadet_web/plug/ensure_roles.ex @@ -0,0 +1,25 @@ +defmodule CadetWeb.Plug.EnsureRoles do + @moduledoc """ + Ensures that :current_user's role is inside a list provided as option. + If the user is not inside the list, HTTP 403 response will be sent. + """ + + import Plug.Conn + + def init(opts), do: opts + + def call(conn, %{roles: roles}) do + if conn.assigns[:current_user].role in roles do + conn + else + body = + roles + |> Enum.map(&to_string/1) + |> Enum.join("/") + conn + |> put_resp_content_type("text/html") + |> send_resp(:forbidden, "Not #{body}") + |> halt() + end + end +end diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 5301a5b86..c4759045f 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -18,6 +18,10 @@ defmodule CadetWeb.Router do plug(Guardian.Plug.EnsureAuthenticated) end + pipeline :ensure_admin_staff do + plug(CadetWeb.Plug.EnsureRoles, %{roles: [:admin, :staff]}) + end + # Public Pages scope "/", CadetWeb do pipe_through([:browser, :auth]) @@ -33,6 +37,13 @@ defmodule CadetWeb.Router do get("/", PageController, :index) end + # Admin Pages + scope "/admin", CadetWeb do + pipe_through([:browser, :auth, :ensure_auth, :ensure_admin_staff]) + + get("/", AdminController, :index) + end + # Other scopes may use custom stacks. # scope "/api", CadetWeb do # pipe_through :api diff --git a/lib/cadet_web/templates/admin/index.html.eex b/lib/cadet_web/templates/admin/index.html.eex new file mode 100644 index 000000000..4a8566bde --- /dev/null +++ b/lib/cadet_web/templates/admin/index.html.eex @@ -0,0 +1,24 @@ +
diff --git a/lib/cadet_web/templates/layout/app.navbar.html.eex b/lib/cadet_web/templates/layout/app.navbar.html.eex index 35f4f640f..cf9a5ec87 100644 --- a/lib/cadet_web/templates/layout/app.navbar.html.eex +++ b/lib/cadet_web/templates/layout/app.navbar.html.eex @@ -11,7 +11,7 @@ - + <%= if is_roles?(@conn, [:admin, :staff]) do %> + <%= button "Admin", to: admin_path(@conn, :index), method: "get", class: "pt-button pt-minimal pt-icon-settings" %> + <% end %> + <%= if logged_in?(@conn) do %> <%= button "Logout", to: session_path(@conn, :logout), method: "get", class: "pt-button pt-minimal pt-intent-danger" %> <% else %> diff --git a/lib/cadet_web/views/admin_view.ex b/lib/cadet_web/views/admin_view.ex new file mode 100644 index 000000000..870665eaa --- /dev/null +++ b/lib/cadet_web/views/admin_view.ex @@ -0,0 +1,3 @@ +defmodule CadetWeb.AdminView do + use CadetWeb, :view +end diff --git a/lib/cadet_web/views/view_helpers.ex b/lib/cadet_web/views/view_helpers.ex index c829536cc..9b653fd19 100644 --- a/lib/cadet_web/views/view_helpers.ex +++ b/lib/cadet_web/views/view_helpers.ex @@ -7,4 +7,12 @@ defmodule CadetWeb.ViewHelpers do def logged_in?(conn) do conn.assigns[:current_user] != nil end + + def is_roles?(conn, roles) do + if logged_in?(conn) do + conn.assigns[:current_user].role in roles + else + false + end + end end diff --git a/test/cadet_web/controllers/admin_controller_test.exs b/test/cadet_web/controllers/admin_controller_test.exs new file mode 100644 index 000000000..b86770a36 --- /dev/null +++ b/test/cadet_web/controllers/admin_controller_test.exs @@ -0,0 +1,26 @@ +defmodule CadetWeb.AdminControllerTest do + use CadetWeb.ConnCase + + describe "Unauthenticated User" do + test "GET /admin", %{conn: conn} do + conn = get(conn, "/admin") + assert html_response(conn, 401) =~ "unauthenticated" + end + end + + @tag authenticate: :student + describe "Authenticated Student" do + test "GET /admin", %{conn: conn} do + conn = get(conn, "/admin") + assert html_response(conn, 403) =~ "Not admin" + end + end + + @tag authenticate: :admin + describe "Authenticated Admin" do + test "GET /admin", %{conn: conn} do + conn = get(conn, "/admin") + assert html_response(conn, 200) =~ "Admin" + end + end +end diff --git a/test/cadet_web/plug/ensure_roles_test.exs b/test/cadet_web/plug/ensure_roles_test.exs new file mode 100644 index 000000000..86d3cca9a --- /dev/null +++ b/test/cadet_web/plug/ensure_roles_test.exs @@ -0,0 +1,32 @@ +defmodule CadetWeb.Plug.EnsureRolesTest do + use CadetWeb.ConnCase + + alias CadetWeb.Plug.AssignCurrentUser + alias CadetWeb.Plug.EnsureRoles + + test "init" do + EnsureRoles.init(%{}) + # nothing to test + end + + @tag authenticate: :student + test "logged in as student", %{conn: conn} do + conn = AssignCurrentUser.call(conn, %{}) + conn = EnsureRoles.call(conn, %{roles: [:admin, :staff]}) + assert html_response(conn, 403) =~ "Not admin/staff" + end + + @tag authenticate: :staff + test "logged in as staff", %{conn: conn} do + conn = AssignCurrentUser.call(conn, %{}) + conn = EnsureRoles.call(conn, %{roles: [:admin, :staff]}) + refute conn.status # conn.status is not set yet + end + + @tag authenticate: :admin + test "logged in as admin", %{conn: conn} do + conn = AssignCurrentUser.call(conn, %{}) + conn = EnsureRoles.call(conn, %{roles: [:admin, :staff]}) + refute conn.status # conn.status is not set yet + end +end