Role Assignment and Authorization
Overview
User roles are now managed exclusively through the Better Auth API on the Bun backend. The Go backend is read-only with respect to roles — it only queries and enforces them, never assigns or modifies them. There are two levels of roles:-
Platform Roles (managed by Better Auth admin plugin)
- Applied globally to every user
- Values:
user,admin,superadmin - Stored in
users.rolecolumn
-
Organization Roles (managed by Better Auth organization plugin)
- Applied per-organization
- Values:
owner,admin,staff,viewer,member - Stored in
member.rolecolumn per organization
How Role Assignment Works
Platform Roles (Better Auth Admin Plugin)
When a user signs up via the Bun backend (POST /api/auth/sign-up):
- Better Auth creates an entry in the
userstable - Platform role is initially set to
"user"(default) - Superadmin can promote users to
"admin"or"superadmin"via the admin plugin API
POST /api/auth/admin/create-user— Create user with custom platform rolePATCH /api/auth/admin/update-user/:userId— Update user roleGET /api/auth/admin/users— List all users and their roles
Organization Roles (Better Auth Organization Plugin)
When a user is invited to or joins an organization:- Better Auth creates an entry in the
membertable - The inviter specifies the
role(owner, admin, staff, viewer, member) - Organization owners can update member roles via the organization plugin API
POST /api/auth/organization/create— Create organization (caller becomes owner)POST /api/auth/organization/:id/members/invite— Invite member with specific rolePATCH /api/auth/organization/:id/members/:userId/update-role— Change member’s roleDELETE /api/auth/organization/:id/members/:userId— Remove member from org
How Go Backend Queries Roles
The Go backend never writes roles. Instead, it reads them on-demand or caches them.User Repository (Platform Role)
Organization Repository (Org Role)
Authorization with Permission Service
Instead of checking roles directly, Go modules use the PermissionService for authorization. This service:- Queries the
membertable for a user’s org role - Maps the role to a permission set (owner → full perms, staff → limited perms, etc.)
- Caches the result for 5 minutes
Example: Checking Leave Approval Permission
- Both approver and employee are in the org
- Approver has
"leave:approve"permission - If approver is not an admin, they must be the employee’s direct manager
Permission Mappings
Org roles map to permissions as follows:| Role | Permissions | Use Case |
|---|---|---|
owner | All permissions | Organization founder/admin |
admin | org:manage, member:, data:, leave:* | Team lead, HR |
staff | data:read, data:write, leave:request | Regular employee |
viewer | data:read | Read-only access |
data:read,data:write— Data accessleave:approve,leave:request— Leave managementmember:invite,member:manage,member:remove— Team managementorg:manage— Organization settings
Caching and Consistency
Permission Cache
The PermissionService caches role-to-permission lookups with a 5-minute TTL:Real-time Consistency (NATS)
To achieve real-time consistency despite caching, the Bun backend publishes role-change events to NATS. The Go backend’sMemberRoleChangeConsumer subscribes to these and invalidates the cache immediately:
- Member Role Changed:
InvalidateMemberCache(userID, orgID) - Member Removed:
InvalidateMemberCache(userID, orgID) - Member Added:
InvalidateMemberCache(userID, orgID)
Performance Optimization (Postgres Indexes)
Specialized indexes are added to the shared PostgreSQL database to ensure permission checks remain fast (sub-millisecond):idx_users_role: ONusers(role)— Optimizes platform permission checkidx_members_role: ONmembers(role)— Optimizes fetching members by roleidx_members_org_role: ONmembers(organization_id, role)— Primary index for role-to-permission lookups
How to Assign Roles
As a Developer
You cannot assign roles from the Go backend. All role changes must be done via the Better Auth API on the Bun backend:-
Add user to organization with role:
-
Update user’s organization role:
As a User
In the frontend, go to Settings → Members (org admin only) and:- Invite new members with a specific role
- Change existing members’ roles
- Remove members from the organization
Testing
Test: Query User Role from Go
Test: Query Organization Members from Go
Test: Authorization Check in Go
Key Differences from Previous Architecture
| Aspect | Old | New |
|---|---|---|
| Role Source | Local users table (synced via NATS) | Better Auth tables (auth, member) |
| Role Assignment | Go backend NATS handler | Better Auth API (Bun backend) |
| Authorization | Direct role name checks in Go code | Permission Service with role-to-permission mapping |
| Consistency | Event-driven sync (eventual) | Direct queries (immediate reads) |
| Permission Mapping | Hardcoded in each use case | Centralized in PermissionService |
Related Documentation
- Permission Service Integration — How to use PermissionService in your code
- Authentication Architecture — How JWT validation and Better Auth integration works
- Leave Module Integration — Example of role/permission usage in leave approval