Skip to main content

Roles And Permissions

Shared access model

Builder Insights uses one shared role model across the mobile app and the admin control plane. If permissions drift between surfaces, user trust and operational safety drift with them.

Core permission principle

  • higher roles inherit lower-role capabilities
  • mobile and admin should tell the same access story
  • role-gated behavior should be tested intentionally, not assumed
Builder Insights roles map illustration

Role ladder

One hierarchy, two product surfaces

The role system scales from read-only visibility to full administrative control. The important product rule is not just who can see a screen, but whether the same role semantics hold everywhere.

  • viewer is read-only across the system
  • advocate is the standard contribution role
  • manager expands into coordination, imports, and executive visibility
  • administrator governs the system and access-sensitive operations

Role hierarchy

RoleLevelSummary
Administrator100Full access, including user management, settings, and admin-only operations
Manager75Team-level access, broader editing, event management, imports, and executive visibility
Advocate50Standard capture and contribution role
Viewer25Read-only access to system data

The hierarchy is defined in src/lib/roles.ts. Permission checks use a >= comparison — a manager automatically inherits advocate and viewer capabilities.

What each role means

Viewer

The system treats this role as read-only in both products.

  • Can view insights, events, advocates, bugs, leaderboard, world map
  • Can view analytics dashboards
  • Can view settings (read-only)
  • Cannot create, edit, or delete any data
  • Cannot access event creation/edit forms
Advocate

The standard field contribution role for mobile capture and admin editing workflows.

  • Can create insights, events, sessions, bugs
  • Can edit and delete their own insights (ownership enforced by advocateId match)
  • Can annotate and upvote any insight
  • Can participate in normal capture workflows
  • Can view all team data
  • Cannot manage advocates, users, imports, or settings mutations
Manager

Expands from contribution into broader coordination, program management, and analysis.

  • Can edit and delete any insight (no ownership restriction)
  • Can manage events (full CRUD including bulk upsert/import)
  • Can create and edit advocates
  • Can edit the Program plan and post program comments
  • Can send Slack digests and manage schema aliases
  • Can access the PMO Import page
  • Cannot access admin-only pages (Operations, Monitoring, User Management)
Administrator

Governs the system itself — not just content inside it.

  • All manager permissions
  • Can access User Management (/admin/users)
  • Can access Operations (/operations) — backup, DB stats
  • Can access Monitoring (/monitoring) — app observability
  • Full access to all settings and API endpoints

Enforcement layers

Access control is enforced at three layers. All three must agree for the system to be secure.

Layer 1: Middleware (src/middleware.ts)

The Next.js middleware intercepts every request and applies path-based rules before the route handler runs.

Fully public (no auth required):

PathNotes
/api/auth/*Authentication flow (magic-link, verify-code, logout)
/api/healthHealth check

Public GET, authenticated mutations:

These paths allow unauthenticated GET requests (used by the mobile app for read access). POST, PUT, PATCH, and DELETE require a valid JWT.

PathMutation role requirement
/api/insights, /api/events, /api/sessions, /api/bugs, /api/attachments, /api/analytics, /api/statsadvocate or above
/api/advocates, /api/slack, /api/program, /api/schemamanager or above
/api/cronCron routes handle their own auth via CRON_SECRET

Admin-only paths (all methods):

PathRequired role
/admin/*, /api/admin/*admin
/operations, /api/operations/*admin
/monitoringadmin

Manager-only paths (all methods):

PathRequired role
/importmanager
/api/events/upsertmanager

Viewer-blocked page paths:

PathRequired roleNotes
/events/newadvocateViewers redirected to dashboard
/events/[id]/editadvocateViewers redirected to dashboard

Layer 2: API route handlers (defense-in-depth)

Some routes add their own role checks inside the handler. This is defense-in-depth — if middleware is bypassed or misconfigured, the route still enforces access.

RouteInternal checkRequired role
api/admin/users (GET, POST, PUT)requireAdmin()admin
api/admin/users/[id] (GET, PUT, DELETE)requireAdmin()admin
api/operations/backup (GET)sessionUser?.isAdminadmin
api/operations/stats (GET)sessionUser?.isAdminadmin
api/advocates (POST)session.role checkmanager+
api/program (POST, DELETE)canUpdateProgram(role)manager+
api/program/comments (POST)canUpdateProgram(role)manager+
api/insights/[id] (PUT)ownership check via advocateIdown insights (advocate), any (manager+)
api/insights/[id] (DELETE)ownership check via advocateIdown insights (advocate), any (manager+)

Layer 3: UI-level gating

The admin app's navigation and page components conditionally show or disable features based on the user's role (fetched from /api/auth/me).

Navigation sidebar (src/components/AdminLayout.tsx):

ItemVisibility rule
Dashboard, Search, Events, Insights, Advocates, Leaderboard, World Map, Bug Reports, SettingsAll authenticated users
ProgramminRole: 'manager' — hidden for advocate and viewer
PMO ImportminRole: 'manager' — hidden for advocate and viewer
MonitoringadminOnly: true — hidden for non-admins
OperationsadminOnly: true — hidden for non-admins
User ManagementadminOnly: true — hidden for non-admins

Page-level behavior:

PageRole checkBehavior for insufficient role
/insightscanModify (advocate+)Edit, delete, and create buttons hidden for viewers
/advocatescanModify (admin or manager)Edit/delete hidden for advocate and viewer
/programcanEdit (admin or manager)Read-only alert shown; all form inputs disabled
/settingsisManager (manager+)Slack digest button disabled; schema delete buttons hidden
/operationsisAdmin checkBlank page rendered for non-admins
/monitoringisAdmin check"Admin access required" alert for non-admins

Full access matrix

The definitive reference for what each role can do across the entire system.

Data operations

CapabilityViewerAdvocateManagerAdmin
View insights, events, advocates, sessions, bugsYesYesYesYes
View dashboards and analyticsYesYesYesYes
View search, leaderboard, world mapYesYesYesYes
Create insightsYesYesYes
Edit own insightsYesYesYes
Edit any insightYesYes
Delete own insightsYesYesYes
Delete any insightYesYes
Create/edit eventsYesYesYes
Create/edit sessionsYesYesYes
Create/edit bugsYesYesYes
Annotate or upvote insightsYesYesYes

Management operations

CapabilityViewerAdvocateManagerAdmin
Create/edit advocatesYesYes
Edit Program planYesYes
Post program commentsYesYes
Import data (PMO Import)YesYes
Bulk upsert eventsYesYes
Send Slack digestYesYes
Share insight to SlackYesYes
Modify schema aliasesYesYes

Administrative operations

CapabilityViewerAdvocateManagerAdmin
User managementYes
Operations (backup, DB stats)Yes
Monitoring dashboardYes

Authentication flow

Builder Insights uses passwordless magic-link authentication.

  1. User enters email at /login
  2. Server sends a 6-digit verification code (and a magic-link URL) to the email
  3. User enters the code or clicks the link
  4. Server looks up the email in the advocates collection to determine role
  5. A JWT is created with { email, name, role, isAdmin, advocateId } and stored as an httpOnly cookie (di-session)
  6. The JWT expires after 7 days

Token sources: The middleware accepts JWTs from either:

  • Cookie (di-session) — used by the web admin app
  • Authorization: Bearer header — used by the mobile app

Auto-provisioning: If a user signs in with an email that does not match any advocate record, the system auto-creates a viewer-level advocate record.

The isAdmin flag

The JWT contains both a role field and a legacy isAdmin boolean. These can theoretically diverge (e.g., a record with role: 'manager' but isAdmin: true). The system normalizes this:

  • The /api/auth/me endpoint promotes role to 'admin' if isAdmin is true
  • The middleware checks both: user.role !== 'admin' && !user.isAdmin
  • UI code reads from /api/auth/me and always sees a consistent role + isAdmin pair

Decision

isAdmin normalization

The canonical source of truth for admin status is: role === 'admin' || isAdmin === true. The /api/auth/me endpoint normalizes these into a single consistent response so all UI components can rely on the role field alone.


Surface implications

Mobile experience

Role visibility on mobile reinforces a simple and trustworthy field workflow.

  • Executive tab visible only for manager and admin
  • Standard field capture intended for advocate-and-above roles
  • Profile reflects the active user role clearly
Admin control plane

The admin side enforces higher-risk operations with stronger gating at all three layers.

  • /operations, /admin, and /monitoring require admin access (middleware + page-level)
  • Manager-level paths such as import require at least manager (middleware)
  • Viewers see the settings page but cannot mutate schema or send Slack digests
  • Advocates can edit/delete only their own insights (API-level ownership check)

Risk

Where permission drift becomes dangerous

  • a screen is visible but the action should not be
  • an API allows mutation that the UI appears to forbid
  • a role sees executive or admin-only views in one surface but not the other
  • testers assume a gate is correct because the demo account does not expose the edge case

Test accounts

The system provides built-in test accounts for verifying role-specific behavior:

EmailRolePurpose
demo@builderinsights.appadvocateGeneral behavior testing
admin@builderinsights.appadminAdmin feature testing
manager@builderinsights.appmanagerManager workflow testing
advocate@builderinsights.appadvocateAdvocate permission testing
viewer@builderinsights.appviewerRead-only access testing

These accounts bypass the email verification step — any 6-digit code is accepted.

Practical testing guidance

If role-specific accounts are not available:

  • test with the demo account for general behavior
  • note role-gated paths as not verified
  • do not assume visibility or access rules are correct without the proper role

QA check

Best way to validate permissions

Test both view access and mutation access. A page loading correctly is not enough if the wrong role can still modify data, reach restricted routes, or see executive-only signals. For each role, verify: (1) correct navigation items visible, (2) correct pages accessible via direct URL, (3) correct API responses for mutations.