DocsGuidesRBAC

Role-Based Access Control (RBAC)

Current Implementation Status
  • Works today: JWT roles claim is extracted and stored on the connection principal.
  • Works today: Basic per-connection, per-map read/write booleans are enforced by WriteValidator.
  • Works today: Default-allow — MapPermissions::default() is { read: true, write: true } and require_auth defaults to false. All maps are accessible without authentication by default.
  • To enable deny-by-default: Configure SecurityConfig with require_auth: true and default_permissions: MapPermissions { read: false, write: false }.
  • Planned (TODO-171): Role-based policy evaluation, map pattern matching (users:*), and field-level security (allowedFields) are not yet implemented.

TopGun provides a flexible security model based on Permission Policies. You can define fine-grained access control rules for your data maps, controlling who can read (READ), write (PUT), or delete (REMOVE) data based on user roles.

Development-Friendly Defaults

TopGun Server allows all access by default (read: true, write: true, require_auth: false). To enable deny-by-default, configure SecurityConfig with require_auth: true and default_permissions: { read: false, write: false }.

Core Concepts

Principal

Represents the authenticated user. Contains the sub claim from the JWT (RFC 7519 user identifier) and a list of roles (e.g., ‘USER’, ‘ADMIN’).

Permission Policy

A rule that grants specific actions (READ, PUT, REMOVE) on a set of maps to a specific role.

What Works Today

The following RBAC capabilities are implemented and enforced in the current server:

  • JWT role extraction: The server reads the roles array from the JWT payload and stores it on the connection’s principal object. Roles are available for use in permission checks.
  • Per-connection map permissions: Each connection can be granted read: true or write: true (or both) on specific maps. These booleans are the primary access control mechanism today.
  • Default-allow: MapPermissions::default() is { read: true, write: true } and require_auth defaults to false. All maps are accessible without authentication unless you explicitly configure otherwise. To enable deny-by-default, set require_auth: true and default_permissions: MapPermissions { read: false, write: false } in SecurityConfig. Per-connection map_permissions can then grant access to specific maps.
  • WriteValidator enforcement: All write operations pass through WriteValidator, which checks map-level permissions before allowing the CRDT merge.

Configuration (Planned — TODO-171)

Planned — TODO-171

Map name pattern matching (e.g., users:*) and role-based policy evaluation are not yet implemented. The configuration example below shows the planned design.

RBAC policies will be configured on the Rust server via security settings and environment variables.

RBAC Configuration (planned)
# PLANNED — map pattern matching (users:*, public:*, etc.) is not yet implemented.
# Role-based policy evaluation against map name patterns is planned (TODO-171).
#
# Planned policy structure (future):
#   ANON  -> public:*          -> READ only
#   USER  -> users:{userId}:*  -> ALL (read, write, delete)
#   ADMIN -> *                 -> ALL
#
# What works today: basic per-connection read/write booleans applied by WriteValidator.
# Roles are extracted from the JWT "roles" claim and stored on the connection principal.
#
# Start the server (development):
PORT=8765 \
DATABASE_URL=postgres://user:pass@localhost/myapp \
JWT_SECRET=your-secret-key \
cargo run --bin test-server
#
# Note: a standalone topgun-server production binary is planned but does not yet exist.
# For the full RBAC configuration API (when implemented), see: /docs/reference/server

Field-Level Security (Planned — TODO-171)

Planned — TODO-171

allowedFields field-level security is not yet implemented. The example below shows the planned API.

You will be able to restrict which fields are returned to the client using allowedFields. This is useful for hiding sensitive data like password hashes or internal metadata.

Example: User Profile (planned)
{
  role: 'USER',
  mapNamePattern: 'users:*',
  actions: ['READ'],
  // Only allow reading public profile fields
  allowedFields: ['username', 'displayName', 'avatarUrl']
}