Skip to main content

User Types and Auth Claims

Last updated: 2026-04-03

This document defines all user types in the Pivot platform, the Firebase Auth custom claims that back them, and how they map to RTDB security rule access patterns.


Custom Claims​

Every authenticated user gets custom claims set on their Firebase Auth token during session initialization (session.service.ts). These claims are checked in RTDB security rules via auth.token.<claim>.

ClaimTypeDescription
currentCompanyIdstring or nullThe company the user is currently connected to
isOwnerbooleanUser is a company owner/admin
isManagerbooleanUser is a manager for at least one position
isHeadOfficebooleanUser has head office (multi-company) access
headOfficeIdstring or nullThe head office company ID (for branch access)
adminbooleanPivot platform super-admin (set manually)

Claims are set once per session by SessionService.setCustomClaims() in functions/systems/auth/services/session.service.ts.


User Types​

There are 6 distinct user types, from lowest to highest privilege:

1. Unauthenticated​

No Firebase Auth session. Can only access paths with .read: true (public paths).

RTDB rule check: auth === null

2. Employee​

A standard company employee with no management responsibilities.

  • UserRole value: 'employee'
  • Claims: currentCompanyId
  • How assigned: Has positions on their employee record, but no entries in Managers/{companyId}/{employeeId}
  • Resolved in: RoleService.resolveRole() in functions/systems/auth/services/role.service.ts

RTDB rule check: auth !== null && auth.token.currentCompanyId === $companyId

3. Manager​

An employee who manages one or more positions (departments).

  • UserRole value: 'manager'
  • Claims: currentCompanyId, isManager: true
  • How assigned: Has entries in Managers/{companyId}/{employeeId}/{positionId} (set by owners via the Roles page)
  • Resolved in: ManagerRepository.getManagerPositions() in functions/repositories/manager/manager.repository.ts
  • Auto-cleanup: When positions are removed from an employee, their manager entries are cleaned up by the on-remove-employee-position database trigger

RTDB rule check: auth !== null && auth.token.isManager === true && auth.token.currentCompanyId === $companyId

4. Owner​

The company owner/admin with full control over company data.

  • UserRole value: 'owner'
  • Claims: currentCompanyId, isOwner: true
  • How assigned: employee.isAdmin === true or employee.role === 'owner' (note: isAdmin is a legacy field name that means company owner, not platform admin)
  • Resolved in: RoleService.resolveRole() — checked before manager resolution

RTDB rule check: auth !== null && auth.token.isOwner === true && auth.token.currentCompanyId === $companyId

5. Head Office​

A multi-company administrator who can access data across multiple branch locations.

  • UserRole value: 'head-office'
  • Claims: isHeadOffice: true, headOfficeId
  • How assigned: Two paths:
    • user.isHeadOffice === true (direct flag on User record)
    • user.headOfficeAccess[companyId] === true AND the user is an admin of a head office company
  • Granted via: HeadOfficeService.grantAccess() in functions/systems/auth/services/head-office.service.ts, or by accepting a head office invitation
  • Special behavior: Bypasses currentCompanyId checks on Companies/$companyId — can read any company

RTDB rule check: auth !== null && auth.token.isHeadOffice === true

The headOfficeId claim is also used in Companies rules to allow reading a branch company when auth.token.headOfficeId === data.child('headOfficeId').val().

6. Pivot Admin​

A platform-level super-admin (internal Pivot team only). This is separate from UserRole — it is checked independently via the admin custom claim.

  • UserRole value: n/a (checked separately from the role system)
  • Claims: admin: true
  • How assigned: Manually via node functions/scripts/set-custom-claim.js — never set automatically
  • Used for: Reading all Companies, reading all Users, writing VersionWeb, OwnerDashboard access

RTDB rule check: auth !== null && auth.token.admin === true


'user' Role​

There is also a 'user' role in the UserRole type (employee.positions is empty). In practice it behaves identically to 'employee' in the RTDB rules since both only require auth !== null with currentCompanyId. It represents a user who has been linked to a company but has no assigned positions yet (e.g. during onboarding).


RTDB Rule Access Patterns​

The security rules use 5 access patterns that map to the user types above:

Public​

Anyone can read, including unauthenticated users. Writes are either fully locked or admin-only.

PathWrite
VersionWebadmin only
VersionMobileNobody (deploy only)
SupportAnyone (unauthenticated write allowed)

Backend-Only​

Both read and write are false. Only accessible via the Firebase Admin SDK (Cloud Functions).

NotificationQueues, NotifiedBefore, NotifiedShifts, NotificationsPushSent, PushHistory, PushNotifications, Migrations, Cache, FilesToDelete, InvitationTokens, givex, migrations

User-Scoped​

Access is restricted to the user's own $uid path.

PathReadWrite
Users/$uidOwn uid (admin can read all)Own uid
Tokens/$uidOwn uidOwn uid
BadgeCount/$uidOwn uidOwn uid
PayrollTutorialSeen/$uidOwn uidOwn uid
PresenceStatus/$uidAny authenticated userOwn uid only

Company-Scoped​

Requires auth.token.currentCompanyId === $companyId for both read and write.

This is the most common pattern, covering the majority of paths: WeeklySchedule, Attendance, Posts, Templates, Managers, Documents, TipsOut, NotificationsFlat, NotificationsFlatV2, and many more (44 paths total — see the test file for the complete list).

Company-Scoped with Role-Gated Writes​

Read access follows the standard company-scoped pattern, but writes require a specific role.

Write RolePaths
Owner onlyVeloceSettings, CloverSettings, CloverApiKeySettings, ClusterSettings, MaitreDSettings, MyrSettings, LightspeedSettings, GivexSettings, LibroSettings, EmployerDSettings, NethrisSettings, PowerpaySettings, NonIntegratedPayrollService, NonIntegratedPosService, PendingMaitreDIntegration, Passwords
Owner or ManagerEmployeeRates, Companies/$companyId/jobs, Companies/$companyId/daysStructure, Companies/$companyId/employeesOrder, Companies/$companyId/includeOnCallShifts
Client read-only (backend writes only)VeloceInvoices, CloverInvoices, CloverEmployees, ClusterInvoices, MaitreDInvoices, MaitreDInvoicesNetSales, MaitreDArchive, MyrInvoices, LightspeedDates, LibroReservations, LibroResevations, LibroWalkIns, LibroExpected, WeatherForecast, HeadOfficeShifts

Known Issues​

CompanySettings Root Read Cascade​

CompanySettings has a root-level .read: "auth !== null" rule alongside $companyId-level rules. Because RTDB rules cascade downward, the root rule grants read access to all company settings for any authenticated user, effectively overriding the $companyId scoping. The write rules are correctly scoped. This should be fixed by removing the root .read.

Session Claim Staleness​

Custom claims are set once per session. If a user's role changes (e.g. promoted to manager) while they have an active session, the old claims remain until the next init-session call. This is a known Firebase Auth limitation.