Add role-based tab visibility, route enforcement, and audit trail #136

Merged
ldraney merged 2 commits from 107-role-based-tabs into main 2026-06-07 01:33:27 +00:00
Owner

Summary

Enforce authentication globally when Keycloak is configured, gate every controller by role, make the bottom nav show only the tabs each role is allowed to see, and add PaperTrail audit logging on all domain models.

Changes

  • app/controllers/application_controller.rb -- Add global before_action :authenticate_user! (gated on keycloak_configured?), add require_role(*roles, **options) class method with only/except support, add visible_tabs helper with TAB_ROLES mapping, add PaperTrail user_for_paper_trail whodunnit
  • app/controllers/sessions_controller.rb -- Add skip_before_action :authenticate_user! so login/logout routes remain accessible
  • app/controllers/client_errors_controller.rb -- Add skip_before_action :authenticate_user! for client error reporting
  • app/controllers/properties_controller.rb -- Add require_role :lead, :admin, :super_admin
  • app/controllers/work_queue_items_controller.rb -- Add require_role :member, :lead, :admin, :super_admin, except: :index; index action shows "need crew access" message for clients instead of redirecting
  • app/controllers/weeks_controller.rb -- Add require_role :admin, :super_admin
  • app/controllers/uploads_controller.rb -- Add require_role :lead, :admin, :super_admin
  • app/controllers/crew_controller.rb -- New controller with require_role :admin, :super_admin and placeholder index
  • app/views/crew/index.html.erb -- New placeholder view
  • app/views/layouts/application.html.erb -- Role-based bottom nav: Today (slot 1), Week (slot 2), Person (slot 3, always visible), Properties (slot 4), Crew (slot 5). Removed New and Photos tabs
  • app/views/work_queue_items/index.html.erb -- Add no_access guard for client role at root
  • app/assets/stylesheets/application.css -- Add styles for no-access notice and crew placeholder
  • config/routes.rb -- Add resources :crew, only: [:index]
  • Gemfile -- Add paper_trail gem
  • app/models/property.rb, work_queue_item.rb, upload.rb, service.rb -- Add has_paper_trail
  • db/migrate/20260607011440_create_versions.rb -- PaperTrail versions table
  • spec/support/omniauth.rb -- Add sign_in_as helper for role-based request specs
  • spec/requests/role_access_spec.rb -- 27 specs covering auth redirect, role gating per controller, dev mode bypass, client fallback
  • spec/requests/crew_spec.rb -- 7 specs for crew controller access
  • spec/models/paper_trail_spec.rb -- 5 specs for whodunnit tracking and version creation

Test Plan

  • bundle exec rspec -- 141 examples, 0 failures (101 existing + 40 new)
  • Verify unauthenticated requests redirect to Keycloak when configured
  • Verify member can access /today but not /properties, /weeks, /crew
  • Verify lead can access /today, /properties but not /weeks, /crew
  • Verify admin/super_admin can access all routes
  • Verify client role sees "need crew access" at root
  • Verify dev mode (no KEYCLOAK_URL) skips all auth and shows all tabs
  • Verify PaperTrail records whodunnit on model changes

Review Checklist

  • All 141 specs pass (101 existing + 40 new)
  • No Tailwind -- plain CSS with existing design tokens
  • No local User model -- Keycloak is sole user store
  • Sessions controller unchanged (only added skip_before_action)
  • Dev graceful degradation -- all tabs shown, no auth when KEYCLOAK_URL absent
  • PaperTrail whodunnit tracks username from session

None.

Closes #107

## Summary Enforce authentication globally when Keycloak is configured, gate every controller by role, make the bottom nav show only the tabs each role is allowed to see, and add PaperTrail audit logging on all domain models. ## Changes - **`app/controllers/application_controller.rb`** -- Add global `before_action :authenticate_user!` (gated on `keycloak_configured?`), add `require_role(*roles, **options)` class method with `only`/`except` support, add `visible_tabs` helper with TAB_ROLES mapping, add PaperTrail `user_for_paper_trail` whodunnit - **`app/controllers/sessions_controller.rb`** -- Add `skip_before_action :authenticate_user!` so login/logout routes remain accessible - **`app/controllers/client_errors_controller.rb`** -- Add `skip_before_action :authenticate_user!` for client error reporting - **`app/controllers/properties_controller.rb`** -- Add `require_role :lead, :admin, :super_admin` - **`app/controllers/work_queue_items_controller.rb`** -- Add `require_role :member, :lead, :admin, :super_admin, except: :index`; index action shows "need crew access" message for clients instead of redirecting - **`app/controllers/weeks_controller.rb`** -- Add `require_role :admin, :super_admin` - **`app/controllers/uploads_controller.rb`** -- Add `require_role :lead, :admin, :super_admin` - **`app/controllers/crew_controller.rb`** -- New controller with `require_role :admin, :super_admin` and placeholder index - **`app/views/crew/index.html.erb`** -- New placeholder view - **`app/views/layouts/application.html.erb`** -- Role-based bottom nav: Today (slot 1), Week (slot 2), Person (slot 3, always visible), Properties (slot 4), Crew (slot 5). Removed New and Photos tabs - **`app/views/work_queue_items/index.html.erb`** -- Add no_access guard for client role at root - **`app/assets/stylesheets/application.css`** -- Add styles for no-access notice and crew placeholder - **`config/routes.rb`** -- Add `resources :crew, only: [:index]` - **`Gemfile`** -- Add `paper_trail` gem - **`app/models/property.rb`**, **`work_queue_item.rb`**, **`upload.rb`**, **`service.rb`** -- Add `has_paper_trail` - **`db/migrate/20260607011440_create_versions.rb`** -- PaperTrail versions table - **`spec/support/omniauth.rb`** -- Add `sign_in_as` helper for role-based request specs - **`spec/requests/role_access_spec.rb`** -- 27 specs covering auth redirect, role gating per controller, dev mode bypass, client fallback - **`spec/requests/crew_spec.rb`** -- 7 specs for crew controller access - **`spec/models/paper_trail_spec.rb`** -- 5 specs for whodunnit tracking and version creation ## Test Plan - `bundle exec rspec` -- 141 examples, 0 failures (101 existing + 40 new) - Verify unauthenticated requests redirect to Keycloak when configured - Verify member can access /today but not /properties, /weeks, /crew - Verify lead can access /today, /properties but not /weeks, /crew - Verify admin/super_admin can access all routes - Verify client role sees "need crew access" at root - Verify dev mode (no KEYCLOAK_URL) skips all auth and shows all tabs - Verify PaperTrail records whodunnit on model changes ## Review Checklist - [x] All 141 specs pass (101 existing + 40 new) - [x] No Tailwind -- plain CSS with existing design tokens - [x] No local User model -- Keycloak is sole user store - [x] Sessions controller unchanged (only added skip_before_action) - [x] Dev graceful degradation -- all tabs shown, no auth when KEYCLOAK_URL absent - [x] PaperTrail whodunnit tracks username from session ## Related Notes None. ## Related Closes #107
Add role-based tab visibility, route enforcement, and PaperTrail audit trail
Some checks are pending
CI / scan_ruby (pull_request) Waiting to run
CI / scan_js (pull_request) Waiting to run
CI / lint (pull_request) Waiting to run
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
c367504f67
Enforce authentication globally when Keycloak is configured, with
skip_before_action on sessions and client_errors controllers. Add
require_role class method to ApplicationController for per-controller
role gating with only/except support. Update bottom nav to show tabs
based on user roles (Today, Week, Person, Properties, Crew) with
Person always centered in slot 3. Remove New and Photos tabs. Add
CrewController placeholder. Add paper_trail gem with has_paper_trail
on Property, WorkQueueItem, Upload, and Service models. Client role
gracefully sees "need crew access" message at root instead of redirect.
Dev mode without Keycloak skips all auth and shows all tabs.

Closes #107

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

QA Review -- PR #136

Reviewed the full diff (25 files, +573/-19) against issue #107 requirements.

Findings

1. [bug] Redirect loop risk for client role on mutating work queue actions

WorkQueueItemsController uses require_role :member, :lead, :admin, :super_admin, except: :index. This means create, update, destroy, and reorder are role-gated. When a client fails the role check, require_role redirects to root_path, which is work_queue_items#index. The index then renders the no-access page. This is correct behavior -- no loop occurs because the index action is excluded from role enforcement. Verified this is safe. No action needed.

2. [nit] with_keycloak helper duplicated across three spec files

role_access_spec.rb, crew_spec.rb, and paper_trail_spec.rb each define identical with_keycloak / without_keycloak helpers. Consider extracting to spec/support/keycloak_helpers.rb to DRY up the test suite. Non-blocking.

3. [nit] paper_trail_spec.rb is in spec/models/ but has type: :request

The file lives at spec/models/paper_trail_spec.rb but declares type: :request (it uses patch and sign_in_as). Should live at spec/requests/paper_trail_spec.rb for conventional file placement. Non-blocking -- RSpec uses the explicit type: tag, so it works either way.

4. [ok] Session controller not modified

Issue spec says "don't modify sessions_controller.rb." The diff adds only skip_before_action :authenticate_user! -- this is required for the global auth enforcement to work and does not change auth logic. Acceptable.

5. [ok] Role mapping matches spec

Verified TAB_ROLES and require_role declarations match the issue's role-to-tab matrix exactly:

  • Today: member, lead, admin, super_admin
  • Week: admin, super_admin
  • Person: always visible
  • Properties: lead, admin, super_admin
  • Crew: admin, super_admin

6. [ok] Dev graceful degradation

keycloak_configured? guards both authenticate_user! (via if:) and require_role (via next unless). visible_tabs returns all tabs when not configured. Verified in specs.

7. [ok] PaperTrail integration

has_paper_trail on all four models. user_for_paper_trail returns username or "anonymous". Migration is standard PaperTrail generator output. Whodunnit specs cover both authenticated and anonymous paths.

8. [ok] New/Photos tabs removed, Crew tab added

Layout diff confirms New and Photos tabs are gone. Crew tab added in slot 5 with appropriate SVG icon. Person tab always in slot 3.

Summary

No blocking issues found. Two nits (duplicated test helpers, misplaced spec file) are cleanup items that can be addressed in a follow-up.

VERDICT: APPROVE

## QA Review -- PR #136 Reviewed the full diff (25 files, +573/-19) against issue #107 requirements. ### Findings **1. [bug] Redirect loop risk for client role on mutating work queue actions** `WorkQueueItemsController` uses `require_role :member, :lead, :admin, :super_admin, except: :index`. This means `create`, `update`, `destroy`, and `reorder` are role-gated. When a client fails the role check, `require_role` redirects to `root_path`, which is `work_queue_items#index`. The index then renders the no-access page. This is correct behavior -- no loop occurs because the index action is excluded from role enforcement. Verified this is safe. No action needed. **2. [nit] `with_keycloak` helper duplicated across three spec files** `role_access_spec.rb`, `crew_spec.rb`, and `paper_trail_spec.rb` each define identical `with_keycloak` / `without_keycloak` helpers. Consider extracting to `spec/support/keycloak_helpers.rb` to DRY up the test suite. Non-blocking. **3. [nit] `paper_trail_spec.rb` is in `spec/models/` but has `type: :request`** The file lives at `spec/models/paper_trail_spec.rb` but declares `type: :request` (it uses `patch` and `sign_in_as`). Should live at `spec/requests/paper_trail_spec.rb` for conventional file placement. Non-blocking -- RSpec uses the explicit `type:` tag, so it works either way. **4. [ok] Session controller not modified** Issue spec says "don't modify sessions_controller.rb." The diff adds only `skip_before_action :authenticate_user!` -- this is required for the global auth enforcement to work and does not change auth logic. Acceptable. **5. [ok] Role mapping matches spec** Verified TAB_ROLES and require_role declarations match the issue's role-to-tab matrix exactly: - Today: member, lead, admin, super_admin - Week: admin, super_admin - Person: always visible - Properties: lead, admin, super_admin - Crew: admin, super_admin **6. [ok] Dev graceful degradation** `keycloak_configured?` guards both `authenticate_user!` (via `if:`) and `require_role` (via `next unless`). `visible_tabs` returns all tabs when not configured. Verified in specs. **7. [ok] PaperTrail integration** `has_paper_trail` on all four models. `user_for_paper_trail` returns username or "anonymous". Migration is standard PaperTrail generator output. Whodunnit specs cover both authenticated and anonymous paths. **8. [ok] New/Photos tabs removed, Crew tab added** Layout diff confirms New and Photos tabs are gone. Crew tab added in slot 5 with appropriate SVG icon. Person tab always in slot 3. ### Summary No blocking issues found. Two nits (duplicated test helpers, misplaced spec file) are cleanup items that can be addressed in a follow-up. **VERDICT: APPROVE**
Add object_changes to PaperTrail migration and fix nav centering
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
CI / scan_ruby (pull_request) Has been cancelled
CI / scan_js (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
dfee78c68a
PaperTrail object_changes column stores per-field diffs (e.g.
{"client_name": ["Old", "New"]}) for readable audit trail instead
of requiring snapshot comparison. Nav switches from flex to CSS grid
with fixed 5-column slots so Person icon stays centered in slot 3
regardless of how many role-based tabs are visible.

Closes #137 scope: discovered during PR #136 review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ldraney deleted branch 107-role-based-tabs 2026-06-07 01:33:27 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
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
ldraney/landscaping-assistant!136
No description provided.