Roles and access control
Chive uses a role-based access control (RBAC) system backed by Redis. Roles determine what features a user can access, including the admin dashboard.
Role system overview
Roles are stored in Redis as sets keyed by DID:
chive:authz:roles:{did} -> SET { "admin", "alpha-tester", ... }
Each role assignment also stores metadata:
chive:authz:assignments:{did}:{role} -> JSON { role, assignedAt, assignedBy }
The auth middleware reads a user's roles from Redis on every authenticated request and sets boolean flags (isAdmin, isAlphaTester) on the user context object.
Available roles
| Role | Description |
|---|---|
admin | Full access to the admin dashboard and all admin XRPC endpoints |
moderator | Knowledge graph governance and proposal review |
graph-editor | Knowledge graph editing (node and edge proposals) |
author | Eprint submission permissions |
reader | Standard read access |
alpha-tester | Access to alpha features during the testing phase |
Admin users implicitly have alpha-tester access regardless of explicit role assignment. This is enforced in the pub.chive.actor.getMyRoles handler.
Bootstrapping admin access
ADMIN_DIDS environment variable
On server startup, Chive reads the ADMIN_DIDS environment variable (a comma-separated list of DIDs) and assigns the admin role to each DID via the authorization service. This is idempotent; Redis SADD is a no-op for existing set members.
ADMIN_DIDS="did:plc:abc123,did:plc:def456"
If ADMIN_DIDS is not set, the server uses a default DID (the project maintainer's DID).
seed-admin script
For manual bootstrapping outside the server process (e.g., during initial deployment), run the seed script:
REDIS_URL="redis://localhost:6379" \
ADMIN_DIDS="did:plc:abc123,did:plc:def456" \
pnpm tsx scripts/seed-admin.ts
This script:
- Connects to Redis at the specified URL
- For each DID in
ADMIN_DIDS, addsadminto the role set and stores assignment metadata - Logs whether each assignment was new or already existed
- Exits after processing all DIDs
How admin status is determined
The admin check happens at two levels:
Backend (auth middleware)
- The auth middleware extracts the DID from the service auth JWT
- It reads roles from Redis:
SMEMBERS chive:authz:roles:{did} - It checks if
adminis in the role set - It sets
user.isAdmin = trueon the Hono context
Frontend (role context)
- After OAuth login, the frontend calls
pub.chive.actor.getMyRoles - The response includes
isAdmin,isAlphaTesterbooleans - These are stored in the auth context (React context provider)
- Components access them via
useAuth()hook AdminGuardcomponent checksuser.isAdminand redirects non-admins
getMyRoles endpoint
The pub.chive.actor.getMyRoles endpoint returns the authenticated user's roles and computed flags:
{
"roles": ["admin", "alpha-tester"],
"isAdmin": true,
"isAlphaTester": true
}
This endpoint:
- Is available to any authenticated user (not restricted to admins)
- Reads fresh roles from Redis (not relying on cached middleware state)
- Computes
isAlphaTesterasroles.includes('alpha-tester') || isAdmin
AdminGuard component
The AdminGuard React component (web/components/auth/admin-guard.tsx) restricts frontend access to admin pages:
- Loading state: Renders a skeleton loader while
isAdminis being determined - Non-admin redirect: If the user is authenticated but not an admin, redirects to
/dashboardviarouter.replace() - Admin access: If
user.isAdminis true, renders children
The component must be nested inside AuthGuard, which ensures the user is authenticated before the admin check runs:
<AuthGuard>
<AdminGuard>{/* Admin-only content */}</AdminGuard>
</AuthGuard>
Granting and revoking access
Via the admin dashboard
- Navigate to
/admin/users - Search for the user by handle or DID
- Click on the user to view their detail page
- Use the role assignment controls to add or remove roles
Via XRPC endpoints
Assign a role:
POST /xrpc/pub.chive.admin.assignRole
{
"did": "did:plc:target-user",
"role": "moderator"
}
Revoke a role:
POST /xrpc/pub.chive.admin.revokeRole
{
"did": "did:plc:target-user",
"role": "moderator"
}
Both endpoints require admin authentication and validate the role name against the allowed list.
Via Redis directly
For emergency access or when the admin dashboard is unavailable:
redis-cli SADD "chive:authz:roles:did:plc:target-user" "admin"
To revoke:
redis-cli SREM "chive:authz:roles:did:plc:target-user" "admin"
Audit trail
Role changes made through the admin dashboard are tracked in multiple ways:
- Structured logging: Every
assignRoleandrevokeRolecall logs the target DID, role, and acting admin DID - Redis metadata: Assignment records store
assignedAtandassignedByfields - Prometheus metrics:
chive_admin_actions_total{action="assign_role"}andchive_admin_actions_total{action="revoke_role"}counters - Redis pub/sub: Content deletions publish events to
chive:admin:content-deleted - Audit log: The
pub.chive.admin.getAuditLogendpoint returns a paginated list of all admin actions
Next steps
- Admin dashboard: Accessing and navigating the admin UI
- Admin API reference:
assignRole,revokeRole, andgetMyRolesendpoints - Admin architecture: Implementation details