Role-based tab visibility, route enforcement, and audit trail #107

Closed
opened 2026-06-05 03:58:56 +00:00 by ldraney · 0 comments
Owner

Type

Feature

Lineage

Parent of #115 (Keycloak login — DONE). Scoped during multi-user planning session (2026-06-04), rewritten 2026-06-07 after #115 shipped.

Repo

ldraney/landscaping-assistant

User Story

As a business owner
I want each user to see only the tabs and actions their role allows
So that crew members can't accidentally edit property data and I can see who changed what

Context

Keycloak login is live (#115, PR #134). Users can log in via OmniAuth Authorization Code + PKCE and their session stores username, email, and roles (from realm_access.roles in the ID token). Five realm roles exist: admin, lead, member, client, super_admin.

What's missing: the app doesn't use those roles yet. Every route is open, every tab is visible, and changes aren't tracked by user. This ticket wires up role-based access control.

Five roles, escalating access:

  • client — Person icon only (My Property / My Profile)
  • member — + Today tab
  • lead — Today + Properties tabs
  • admin — Today + Week + Properties + Crew tabs
  • super_admin — same as admin + Platform view under Person icon

Nav layout (5 slots, Person always centered in slot 3):

Slot:     1        2        3        4        5

Client:   _        _      Person     _        _
Member:   _      Today    Person     _        _
Lead:   Today      _      Person  Properties   _
Admin:  Today    Week     Person  Properties  Crew

Audit trail: PaperTrail gem tracks every create/update/destroy on all models with whodunnit set from current_user[:username].

Auth enforcement: authenticate_user! (already in ApplicationController) applied as a before_action on controllers that require login. Role checks via current_user_has_role? (already in ApplicationController).

File Targets

Files to create:

  • config/initializers/paper_trail.rb — whodunnit config from session
  • db/migrate/*_add_paper_trail.rb — versions table migration
  • app/controllers/crew_controller.rb — Crew tab (admin only), stub for now
  • app/views/crew/index.html.erb — Crew tab placeholder

Files to modify:

  • Gemfile — add paper_trail
  • app/views/layouts/application.html.erb — role-based bottom nav (5 slots, conditional tabs)
  • app/controllers/application_controller.rb — add before_action :authenticate_user! globally, add require_role helper, set PaperTrail whodunnit
  • app/controllers/properties_controller.rbrequire_role(:lead, :admin, :super_admin)
  • app/controllers/work_queue_items_controller.rbrequire_role(:member, :lead, :admin, :super_admin)
  • app/controllers/weeks_controller.rbrequire_role(:admin, :super_admin)
  • app/controllers/uploads_controller.rb — role check for upload actions
  • app/models/property.rbhas_paper_trail
  • app/models/work_queue_item.rbhas_paper_trail
  • app/models/upload.rbhas_paper_trail
  • app/models/service.rbhas_paper_trail
  • app/assets/stylesheets/application.css — nav adjustments for role-based tab visibility
  • config/routes.rb — add crew routes

Files NOT to touch:

  • app/models/user.rb — does not exist, should not be created. Keycloak is the user store.
  • config/initializers/omniauth.rb — already configured, don't change
  • app/controllers/sessions_controller.rb — already working, don't change

Acceptance Criteria

  • authenticate_user! enforced globally — unauthenticated requests redirect to Keycloak login
  • Bottom nav shows only tabs the user's role permits (per nav layout above)
  • Person icon always in slot 3
  • Client role sees only Person icon, no other tabs
  • Member role sees Today + Person
  • Lead role sees Today + Person + Properties
  • Admin role sees Today + Week + Person + Properties + Crew
  • super_admin role sees same as admin (Platform view is a separate ticket)
  • require_role helper rejects users without the required role (403 or redirect)
  • Crew tab exists (admin only) — can be a placeholder listing "Crew management coming soon"
  • PaperTrail tracks changes to Property, WorkQueueItem, Upload, Service
  • PaperTrail whodunnit records the Keycloak username
  • Dev environment still works without Keycloak (graceful degradation — skip auth enforcement when keycloak_configured? is false)

Test Expectations

  • Request spec: unauthenticated GET /properties redirects to Keycloak
  • Request spec: authenticated member cannot access /properties (lead+ only)
  • Request spec: authenticated lead can access /properties
  • Request spec: authenticated member can access /today
  • Request spec: client cannot access /today
  • Request spec: admin can access /crew
  • Request spec: non-admin cannot access /crew
  • Model spec: Property changes create PaperTrail versions with correct whodunnit
  • Run command: bundle exec rspec

Constraints

  • No local User model — Keycloak is the sole user store
  • Auth already works via OmniAuth (#115) — don't reinvent it
  • current_user returns {username:, email:, roles:} hash from session — use it
  • Match existing controller patterns (Turbo-compatible, mobile-first views)
  • No Tailwind — plain CSS only
  • Dev without Keycloak must still work (guard all auth enforcement behind keycloak_configured?)

Checklist

  • PR opened
  • Tests pass
  • Nav renders correctly per role
  • PaperTrail whodunnit verified
  • No unrelated changes
  • #115 — Keycloak login (DONE, PR #134)
  • #117 — Crew tab: admin oversight (separate, deeper implementation)
  • #130 — FeatureFlag implementation (super_admin Platform view)
  • docs/user-stories-auth.md — role definitions and nav layout
  • docs/app-architecture.md — architecture and data model
  • landscaping-assistant — project
### Type Feature ### Lineage Parent of #115 (Keycloak login — DONE). Scoped during multi-user planning session (2026-06-04), rewritten 2026-06-07 after #115 shipped. ### Repo `ldraney/landscaping-assistant` ### User Story As a business owner I want each user to see only the tabs and actions their role allows So that crew members can't accidentally edit property data and I can see who changed what ### Context Keycloak login is live (#115, PR #134). Users can log in via OmniAuth Authorization Code + PKCE and their session stores `username`, `email`, and `roles` (from `realm_access.roles` in the ID token). Five realm roles exist: `admin`, `lead`, `member`, `client`, `super_admin`. What's missing: the app doesn't **use** those roles yet. Every route is open, every tab is visible, and changes aren't tracked by user. This ticket wires up role-based access control. **Five roles, escalating access:** - `client` — Person icon only (My Property / My Profile) - `member` — + Today tab - `lead` — Today + Properties tabs - `admin` — Today + Week + Properties + Crew tabs - `super_admin` — same as admin + Platform view under Person icon **Nav layout** (5 slots, Person always centered in slot 3): ``` Slot: 1 2 3 4 5 Client: _ _ Person _ _ Member: _ Today Person _ _ Lead: Today _ Person Properties _ Admin: Today Week Person Properties Crew ``` **Audit trail**: PaperTrail gem tracks every create/update/destroy on all models with `whodunnit` set from `current_user[:username]`. **Auth enforcement**: `authenticate_user!` (already in ApplicationController) applied as a `before_action` on controllers that require login. Role checks via `current_user_has_role?` (already in ApplicationController). ### File Targets Files to create: - `config/initializers/paper_trail.rb` — whodunnit config from session - `db/migrate/*_add_paper_trail.rb` — versions table migration - `app/controllers/crew_controller.rb` — Crew tab (admin only), stub for now - `app/views/crew/index.html.erb` — Crew tab placeholder Files to modify: - `Gemfile` — add `paper_trail` - `app/views/layouts/application.html.erb` — role-based bottom nav (5 slots, conditional tabs) - `app/controllers/application_controller.rb` — add `before_action :authenticate_user!` globally, add `require_role` helper, set PaperTrail whodunnit - `app/controllers/properties_controller.rb` — `require_role(:lead, :admin, :super_admin)` - `app/controllers/work_queue_items_controller.rb` — `require_role(:member, :lead, :admin, :super_admin)` - `app/controllers/weeks_controller.rb` — `require_role(:admin, :super_admin)` - `app/controllers/uploads_controller.rb` — role check for upload actions - `app/models/property.rb` — `has_paper_trail` - `app/models/work_queue_item.rb` — `has_paper_trail` - `app/models/upload.rb` — `has_paper_trail` - `app/models/service.rb` — `has_paper_trail` - `app/assets/stylesheets/application.css` — nav adjustments for role-based tab visibility - `config/routes.rb` — add crew routes Files NOT to touch: - `app/models/user.rb` — does not exist, should not be created. Keycloak is the user store. - `config/initializers/omniauth.rb` — already configured, don't change - `app/controllers/sessions_controller.rb` — already working, don't change ### Acceptance Criteria - [ ] `authenticate_user!` enforced globally — unauthenticated requests redirect to Keycloak login - [ ] Bottom nav shows only tabs the user's role permits (per nav layout above) - [ ] Person icon always in slot 3 - [ ] Client role sees only Person icon, no other tabs - [ ] Member role sees Today + Person - [ ] Lead role sees Today + Person + Properties - [ ] Admin role sees Today + Week + Person + Properties + Crew - [ ] `super_admin` role sees same as admin (Platform view is a separate ticket) - [ ] `require_role` helper rejects users without the required role (403 or redirect) - [ ] Crew tab exists (admin only) — can be a placeholder listing "Crew management coming soon" - [ ] PaperTrail tracks changes to Property, WorkQueueItem, Upload, Service - [ ] PaperTrail `whodunnit` records the Keycloak username - [ ] Dev environment still works without Keycloak (graceful degradation — skip auth enforcement when `keycloak_configured?` is false) ### Test Expectations - [ ] Request spec: unauthenticated GET /properties redirects to Keycloak - [ ] Request spec: authenticated member cannot access /properties (lead+ only) - [ ] Request spec: authenticated lead can access /properties - [ ] Request spec: authenticated member can access /today - [ ] Request spec: client cannot access /today - [ ] Request spec: admin can access /crew - [ ] Request spec: non-admin cannot access /crew - [ ] Model spec: Property changes create PaperTrail versions with correct whodunnit - Run command: `bundle exec rspec` ### Constraints - No local User model — Keycloak is the sole user store - Auth already works via OmniAuth (#115) — don't reinvent it - `current_user` returns `{username:, email:, roles:}` hash from session — use it - Match existing controller patterns (Turbo-compatible, mobile-first views) - No Tailwind — plain CSS only - Dev without Keycloak must still work (guard all auth enforcement behind `keycloak_configured?`) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] Nav renders correctly per role - [ ] PaperTrail whodunnit verified - [ ] No unrelated changes ### Related - #115 — Keycloak login (DONE, PR #134) - #117 — Crew tab: admin oversight (separate, deeper implementation) - #130 — FeatureFlag implementation (super_admin Platform view) - `docs/user-stories-auth.md` — role definitions and nav layout - `docs/app-architecture.md` — architecture and data model - `landscaping-assistant` — project
ldraney changed title from Keycloak auth with direct grant, roles, and audit trail to Role-based tab visibility, route enforcement, and audit trail 2026-06-07 01:07:39 +00:00
Sign in to join this conversation.
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#107
No description provided.