-
Notifications
You must be signed in to change notification settings - Fork 1
Users Groups and Permissions
Roomer's permission model has three independent layers. Understanding them makes restricting — or opening up — a deployment straightforward:
-
Global role on the user record (
USERorSUPER_ADMIN). -
Resource roles — per-building or per-floor roles (
FLOOR_MANAGER,BUILDING_ADMIN,USER,VIEWER) assigned to individual users or to groups. - Access groups — membership-based visibility/booking rules that can hide buildings or floors from users who aren't in the right group.
User accounts are either created manually (Admin → Users), pulled in from SSO (Enterprise Auth), or created on first LDAP login. Every user has:
| Field | Notes |
|---|---|
| Login identity. Must be unique. Required for local and SSO. | |
| Display Name | Shown everywhere (bookings, emails, admin lists). |
| Global Role |
USER (default) or SUPER_ADMIN. |
| Provider |
LOCAL, LDAP, OIDC, or SAML. Set when the user is created — identifies which auth flow applies. |
| Account Status |
ACTIVE or BLOCKED. Blocked users can't sign in and their tokens are effectively dead on next request. |
There are exactly two global roles. The hierarchy is SUPER_ADMIN > USER, and
any endpoint that requires USER also admits SUPER_ADMIN.
| Role | Can do |
|---|---|
USER |
Sign in, view buildings/floors they have access to, book assets, manage their own bookings and queue entries. |
SUPER_ADMIN |
Everything. Bypasses all access groups, can manage users, groups, settings, buildings, leases, reports. There's no higher role. |
The seed creates one SUPER_ADMIN at admin@roomer.local (or the value of
SEED_ADMIN_EMAIL). The password is taken from SEED_ADMIN_PASSWORD — if that
variable is not set, a random password is generated and printed to the API
logs at seed time. Change it at first login.
From Admin → Users:
- New User — email, display name, password (min 8 chars), global role. Triggers a welcome email via the notification queue.
-
Edit — change display name, global role, or account status
(
ACTIVE/BLOCKED). Users can edit their own display name; only admins can change role or status. - Search — case-insensitive match against email or display name.
A user's bookings and resource roles are preserved through status changes — blocking a user does not delete their history.
Resource roles bind a role to a scope (a specific building or floor) for
either a single user or a whole group. They exist so you can carve up
administrative work without giving someone SUPER_ADMIN.
| Role | Scope | What it lets you do |
|---|---|---|
VIEWER |
Building or Floor | Reserved for future use; currently read-only. |
USER |
Building or Floor | Grants booking access even if the scope is restricted (see Access Groups below). |
FLOOR_MANAGER |
Floor | Create/edit/delete zones and zone groups on that floor; create/edit assets on that floor; bulk-import CSVs into that floor; place and position assets; manage availability windows as an admin. Cannot delete buildings, manage users, or alter access groups. |
BUILDING_ADMIN |
Building | Superset of FLOOR_MANAGER for every floor in the building. Reserved for future use — most operations today still require SUPER_ADMIN. |
Roles compare numerically (BUILDING_ADMIN > FLOOR_MANAGER > USER > VIEWER),
so granting a higher role implicitly covers everything the lower ones do.
Direct (user → scope):
- Admin → Users → pick a user → Roles & Access tab.
-
Add Resource Role. Pick role, scope type (
BUILDINGorFLOOR), and the target building or floor. - Save.
Via a group (group → scope):
Groups can carry resource roles too — every member inherits them. This is the right way to grant "all members of Facilities are floor managers of every floor in Building HQ".
- Admin → Access Groups → pick a group → Resource Roles tab.
- Add role + scope.
- Save.
Floor-manager access can come from either source — the check in
isFloorManagerForFloor() is direct-role OR group-role.
Floor managers get a "Floor Manager" section in the sidebar listing the floors they manage, plus the Assets admin view. Practical things they can do on their own floors:
- Upload/replace floor plans; adjust
displayScale. - Create, rename, recolour, delete zones and zone groups.
- Create, edit, place, and reposition assets; run per-floor bulk imports.
- Manage asset amenities and assigned/allow-listed users.
- Cancel any booking on their floor (via the booking detail page).
They cannot: create buildings, add users, change global roles, edit global settings, or manage access groups.
A UserGroup is just a list of users with a name. Groups do three separate
things — any given group can do none, some, or all:
- Building access — gate visibility & booking on a building.
- Floor access — further gate visibility & booking to specific floors.
- Resource roles — grant admin-ish roles to members (covered above).
- A building with zero
GroupBuildingAccessrows is an open building — every authenticated user sees it and can book in it (subject to per-asset rules). - A building with ≥ 1
GroupBuildingAccessrow is a restricted building — only users who are members of at least one linked group see it in the sidebar, in building listings, and on the/api/v1/availabilityendpoints.
SUPER_ADMINs always see every building regardless of group rules.
When one of a user's groups has ≥ 1 GroupFloorAccess row, that user's view
is narrowed for that group's intent: for every group the user is in that
carries floor rules, the target floor must be listed.
Worked examples (assume Building HQ contains floors L1, L2, L3):
- User is in no groups → sees all open buildings, all floors.
- User is only in group Finance-Team → no floor rules, building open → sees every floor.
- User is in group Finance-Team which is linked to Building HQ only, no floor rules → sees Building HQ and all its floors.
- User is in group Finance-Team which has floor-access rules restricting it to floor L2 → the user can only see/book on L2, even if some other building is open.
The exact algorithm is checkGroupAccess
and is applied at both the visibility layer (GET /buildings,
GET /floors/:id/availability) and the booking layer (POST /bookings).
From Admin → Access Groups → New Group:
| Field | Notes |
|---|---|
| Name | Required, unique per organisation. |
| Description | Free text. |
| Global Role | Optional default for members. Rarely useful — prefer setting the role on the user. Defaults to USER. |
Once created, open the group's detail page to:
- Members tab — add/remove users one at a time.
- Building Access tab — add a building to make it visible to members (and restrict it for non-members).
- Floor Access tab — pin the group to specific floors within a building.
-
Resource Roles tab — grant
FLOOR_MANAGER/BUILDING_ADMINto every member.
Deleting a group removes its access rules and memberships but leaves the users untouched.
OIDC, SAML and LDAP configs all support groupMappings — a list of
{ idpGroup: "<upstream group>", roomerGroupId: "<Roomer group id>" } pairs.
At every login the user's memberships in mapped groups are fully reconciled:
- Users are added to mapped groups whose upstream equivalent is present in the assertion.
- Users are removed from mapped groups whose upstream equivalent is no longer present — this prevents privilege accumulation if someone's upstream group membership changes.
- Group memberships you set manually in Roomer for non-mapped groups are left untouched.
The reconciliation also re-derives the user's global role from their current group memberships, so a role granted by a group mapping can be downgraded if the user is removed from that group upstream.
See Enterprise Auth for per-provider configuration details.
For automated, real-time user and group sync from an IdP (Okta, Azure AD, OneLogin, etc.), use SCIM 2.0 provisioning instead of (or alongside) group mappings. SCIM operates independently of the login flow — it can create, update and deactivate users without requiring a login event.
See Enterprise Auth#scim-20-provisioning for setup instructions.
| Goal | Configuration |
|---|---|
| "Everyone signs in and can book anywhere." | One open building. No access groups. |
| "Finance team only — the Finance floor." | Create group Finance, add users, link it to Building HQ building-access and Floor L3 floor-access. |
| "Give Alice admin on just Floor L5." | Keep Alice as a regular USER. Admin → Users → Alice → Resource Roles → FLOOR_MANAGER on L5. |
| "Entire facilities team admins every floor in HQ." | Create group Facilities-HQ. Add members. Group → Resource Roles → FLOOR_MANAGER on each floor (or BUILDING_ADMIN on the building once that role is fully implemented). |
| "Contractors shouldn't see buildings A or B, only C." | Create group Contractors, link it to Building C (restricted). Buildings A and B stay open — do NOT link the contractor group to them, or they'd gain access. Make A and B restricted via a separate Staff group. |
- "User can't see a building they should have access to" — check Admin → Access Groups for any group linked to that building and confirm the user is a member. Also confirm the user isn't stuck in a floor-restricted group whose rules exclude the floor they're trying to reach.
-
"Floor manager gets
Forbiddenon edit" — the role is scoped to a floor, not a building. AddingFLOOR_MANAGERon Building HQ does not grant manager rights on Floor L2 under it — you need a floor-scoped row. -
"SSO group mapping not taking effect" — group membership is re-computed
on every SSO login, so check Admin → Users → user detail → Groups right
after a login. If empty, verify the
groupsClaimName(OIDC) / group attribute (SAML/LDAP) is actually in the assertion — enable debug logging on the API and inspect the parsed claims.
Install & Run
- Getting Started
- Development Setup
- Production Deployment
- Kubernetes Deployment
- Backup and Recovery
- TLS Configuration
- Configuration Reference
Using Roomer
- Buildings and Floors
- Zones and Assets
- Users, Groups and Permissions
- Booking and Queue
- Bulk CSV Import
- Enterprise Auth
- Email Notifications
- Webhooks
- Reports and Leases
Developer