Skip to content

Users Groups and Permissions

c0dewhacker edited this page Apr 22, 2026 · 2 revisions

Users, Groups and Permissions

Roomer's permission model has three independent layers. Understanding them makes restricting — or opening up — a deployment straightforward:

  1. Global role on the user record (USER or SUPER_ADMIN).
  2. Resource roles — per-building or per-floor roles (FLOOR_MANAGER, BUILDING_ADMIN, USER, VIEWER) assigned to individual users or to groups.
  3. Access groups — membership-based visibility/booking rules that can hide buildings or floors from users who aren't in the right group.

Users

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
Email 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.

Global roles

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.

Creating and editing users

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

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.

Assigning a resource role

Direct (user → scope):

  1. Admin → Users → pick a user → Roles & Access tab.
  2. Add Resource Role. Pick role, scope type (BUILDING or FLOOR), and the target building or floor.
  3. 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".

  1. Admin → Access Groups → pick a group → Resource Roles tab.
  2. Add role + scope.
  3. Save.

Floor-manager access can come from either source — the check in isFloorManagerForFloor() is direct-role OR group-role.

Floor manager UX

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.

Access groups (visibility & booking gates)

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:

  1. Building access — gate visibility & booking on a building.
  2. Floor access — further gate visibility & booking to specific floors.
  3. Resource roles — grant admin-ish roles to members (covered above).

Building access

  • A building with zero GroupBuildingAccess rows is an open building — every authenticated user sees it and can book in it (subject to per-asset rules).
  • A building with ≥ 1 GroupBuildingAccess row 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/availability endpoints.

SUPER_ADMINs always see every building regardless of group rules.

Floor access

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).

Creating an access group

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_ADMIN to every member.

Deleting a group removes its access rules and memberships but leaves the users untouched.

Group mapping from SSO

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.

SCIM provisioning

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.

Putting it together — common setups

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.

Troubleshooting

  • "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 Forbidden on edit" — the role is scoped to a floor, not a building. Adding FLOOR_MANAGER on 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.

Clone this wiki locally