[FEATURE]: Page Permission Router Plug #388

Closed
opened 2026-01-29 14:20:09 +01:00 by moritz · 0 comments
Owner

Description

Create a Phoenix plug that checks if the current user has permission to access the requested page/route. This runs before LiveView mounts.

Tasks

  1. Create lib/mv_web/plugs/check_page_permission.ex
  2. Implement init/1 and call/2
  3. Extract page path: Phoenix does not set conn.private[:phoenix_route]. Instead: get the route template (e.g. "/members/:id") via Phoenix.Router.route_info(conn.private[:phoenix_router], conn.method, conn.request_path, conn.host); the :route key holds the template. Fallback to conn.request_path when there is no match (e.g. for forward routes).
  4. Get user from conn.assigns[:current_user]
  5. Get user's role and permission_set_name
  6. Call PermissionSets.get_permissions/1 to get allowed pages list
  7. Match requested path against allowed patterns:
    • Exact match: "/members" == "/members"
    • Dynamic match: "/members/:id" matches "/members/123"
    • Wildcard: "*" matches everything (admin)
  8. If unauthorized: redirect to "/" with flash error "You don't have permission to access this page."
  9. If authorized: continue (conn not halted)
  10. Router: This app does not have a pipeline :require_authenticated_user; auth is done via ash_authentication_live_session with on_mount. Instead: define a pipeline :require_page_permission that includes the plug, and use it in the same scope as the protected live routes, e.g. pipe_through [:browser, :require_page_permission]. Note: If the plug runs for the whole scope (including auth routes), exempt public paths (e.g. /auth, /register, /reset, /confirm) from the page-permission check so unauthenticated users can reach the login page.

Acceptance Criteria

  • Plug checks page permissions from PermissionSets
  • Static routes work ("/members")
  • Dynamic routes work ("/members/:id" matches "/members/123")
  • Wildcard works for admin ("*")
  • Unauthorized users redirected with flash message
  • Plug added to the appropriate router pipeline(s)

Test Strategy (TDD)

Static Route Tests

  • User with permission for "/members" can access (conn not halted)
  • User without permission for "/members" is denied (conn halted, redirected to "/")
  • Flash error message present after denial

Dynamic Route Tests

  • User with "/members/:id" permission can access "/members/123"
  • User with "/members/:id/edit" permission can access "/members/456/edit"
  • User with only "/members/:id" cannot access "/members/123/edit"
  • Pattern matching works correctly

Wildcard Tests

  • Admin with "*" permission can access any page
  • Wildcard overrides all other checks

Unauthenticated User Tests

  • Nil current_user is redirected to login
  • Login redirect preserves attempted path (optional feature)

Error Handling Tests

  • User with invalid permission_set_name is denied
  • User with no role is denied
  • Error is logged but user sees generic message

Test File

test/mv_web/plugs/check_page_permission_test.exs

## Description Create a Phoenix plug that checks if the current user has permission to access the requested page/route. This runs before LiveView mounts. ## Tasks 1. Create `lib/mv_web/plugs/check_page_permission.ex` 2. Implement `init/1` and `call/2` 3. **Extract page path:** Phoenix does **not** set `conn.private[:phoenix_route]`. Instead: get the route template (e.g. "/members/:id") via `Phoenix.Router.route_info(conn.private[:phoenix_router], conn.method, conn.request_path, conn.host)`; the `:route` key holds the template. Fallback to `conn.request_path` when there is no match (e.g. for `forward` routes). 4. Get user from `conn.assigns[:current_user]` 5. Get user's role and permission_set_name 6. Call `PermissionSets.get_permissions/1` to get allowed pages list 7. Match requested path against allowed patterns: - Exact match: "/members" == "/members" - Dynamic match: "/members/:id" matches "/members/123" - Wildcard: "*" matches everything (admin) 8. If unauthorized: redirect to "/" with flash error "You don't have permission to access this page." 9. If authorized: continue (conn not halted) 10. **Router:** This app does **not** have a pipeline `:require_authenticated_user`; auth is done via `ash_authentication_live_session` with `on_mount`. Instead: define a pipeline `:require_page_permission` that includes the plug, and use it in the **same** scope as the protected live routes, e.g. `pipe_through [:browser, :require_page_permission]`. **Note:** If the plug runs for the whole scope (including auth routes), exempt public paths (e.g. `/auth`, `/register`, `/reset`, `/confirm`) from the page-permission check so unauthenticated users can reach the login page. ## Acceptance Criteria - [ ] Plug checks page permissions from PermissionSets - [ ] Static routes work ("/members") - [ ] Dynamic routes work ("/members/:id" matches "/members/123") - [ ] Wildcard works for admin ("*") - [ ] Unauthorized users redirected with flash message - [ ] Plug added to the appropriate router pipeline(s) ## Test Strategy (TDD) ### Static Route Tests - User with permission for "/members" can access (conn not halted) - User without permission for "/members" is denied (conn halted, redirected to "/") - Flash error message present after denial ### Dynamic Route Tests - User with "/members/:id" permission can access "/members/123" - User with "/members/:id/edit" permission can access "/members/456/edit" - User with only "/members/:id" cannot access "/members/123/edit" - Pattern matching works correctly ### Wildcard Tests - Admin with "*" permission can access any page - Wildcard overrides all other checks ### Unauthenticated User Tests - Nil current_user is redirected to login - Login redirect preserves attempted path (optional feature) ### Error Handling Tests - User with invalid permission_set_name is denied - User with no role is denied - Error is logged but user sees generic message ## Test File `test/mv_web/plugs/check_page_permission_test.exs`
moritz added this to the Accounts & Logins milestone 2026-01-29 14:20:09 +01:00
moritz self-assigned this 2026-01-29 14:20:09 +01:00
moritz added this to the Sprint 12: 29.01.- 19.02 project 2026-01-29 14:20:10 +01:00
moritz modified the milestone from Accounts & Logins to We have different roles and permissions 2026-02-03 16:38:37 +01:00
Sign in to join this conversation.
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: local-it/mitgliederverwaltung#388
No description provided.