Skip to content

Security & RBAC

SchemaStack implements a multi-level Role-Based Access Control (RBAC) system with permissions at the organisation, workspace, view, and column level.

Roles

Organisation Roles

RoleScope
OwnerFull access to everything in the organisation. Bypasses all permission checks.
AdminManage workspaces, members, and settings. Inherits Admin on all workspaces.
MemberBasic membership. No inherited workspace access — must be explicitly added.

Defined in OrganisationMembership.Role enum (organisation-persistence).

Workspace Roles

RoleInherited View RoleDescription
Workspace AdminView AdminFull workspace management + all view access
Workspace EditorView EditorRead/write data in all views
Workspace ViewerView ViewerRead-only data in all views
Workspace MemberNoneWorkspace access but no inherited view access

Defined in WorkspaceMembership.Role enum (metadata-persistence).

The Workspace Member role is for users who should only access specific views via explicit ViewMembership grants.

View Roles

RoleCan DesignCan ReadCan Write
AdminYesYesYes
EditorNoYesYes
ViewerNoYesNo

Defined in ViewMembership.Role enum (metadata-persistence).

Permission Resolution

Permissions are resolved by ViewPermissionService (metadata-service). The evaluation order:

  1. Organisation Owner → full access (always)
  2. Private view check → if view is private, only explicit ViewMembership counts
  3. Explicit ViewMembership → view-level role override
  4. Inherited from WorkspaceMembership → workspace role converted to view role
  5. Inherited from Organisation Admin → Admin access
  6. Default deny

A view-level role overrides the workspace-level role. This allows both promotions (Editor → View Admin) and restrictions (Editor → View Viewer).

View Actions

Actions are defined in the ViewAction enum (metadata-service):

Design Actions (Admin only)

DESIGN_VIEW, ADD_COLUMN, REMOVE_COLUMN, MODIFY_COLUMN, REORDER_COLUMNS, CONFIGURE_VIEW, MANAGE_MEMBERS, CONFIGURE_PERMISSIONS

Data Actions

VIEW_DATA (Viewer+), ADD_ROW (Editor+), EDIT_ROW (Editor+), DELETE_ROW (Editor+), EXPORT_DATA (Viewer+)

Bulk Actions (Editor+)

BULK_DELETE, BULK_UPDATE, BULK_EXPORT

Column-Level Permissions

Each column can have per-role permissions controlling visibility and editability.

Stored in ViewColumnRolePermission entity (metadata-persistence):

FieldTypeDefault
canReadbooleantrue
canWritebooleanfalse
isHiddenbooleanfalse

The ColumnPermission value object provides factory methods: fullAccess(), readOnly(), hidden(), noAccess(), readWrite().

Admins always get full column access regardless of column-level permission settings.

Authentication Filters

Two request filters handle authentication (before any permission check):

  1. TokenAuthorizationFilter (priority 100) — validates token type (SELECTION, TEMP_2FA, REGULAR)
  2. ActiveUserFilter (priority 200) — validates user is active with a valid session

Implementation Pattern

Permission checks use service injection rather than generic interceptors. Resource classes inject ViewPermissionService or WorkspacePermissionService and call permission checks explicitly:

java
// In a REST resource
viewPermissionService.requirePermission(viewUuid, userUuid, ViewAction.EDIT_ROW);

WorkspacePermissionService handles workspace-level actions (CREATE_VIEW, UPDATE_VIEW, DELETE_VIEW, CREATE_COLUMN, UPDATE_COLUMN, DELETE_COLUMN) with requirePermission() (throws ForbiddenException) and checkPermission() (returns boolean).

XSS Protection

Defense-in-depth approach with two implemented layers:

Input validation@Pattern(regexp = "^(?!.*[<>]).*$") on user-generated content fields (bio, names, descriptions) in UserDTO, OrganisationDTO, JoinRequestDTO. Blocks < and > characters.

Output sanitizationsanitizeUrl() and sanitizeErrorMessage() methods in WorkspaceDatabaseConfigService to prevent injection via error messages and URLs.

Not yet implemented

Security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options) are not configured. No SecurityHeaderFilter exists. This is the third planned layer.

Endpoint Security Audit (2026-01-11)

A security audit identified 13 critical/high-priority vulnerabilities — all have been fixed and tested:

  • View endpoints allowing cross-organisation data access → fixed with requireWorkspaceMembership() checks
  • Column endpoints with no workspace membership checks → fixed with ViewAccessService.requireViewPermission()
  • Workspace list information disclosure → fixed with membership-filtered queries
  • Database connection testing without admin checks → fixed with requireOrganisationAdmin()
  • Drift detection and view column sync without permission checks → fixed

Test coverage: 35 security tests across ViewAndColumnWorkspaceIsolationTest (19 tests) and WorkspaceSecurityTest (16 tests), all passing.

Encryption

EncryptionUtil (common module) handles all encryption:

  • Algorithm: AES-256-GCM (12-byte random IV, 128-bit auth tag)
  • Key: From TOTP_ENCRYPTION_KEY env var (must be exactly 32 bytes). Falls back to a default key with a warning in dev.
  • Legacy: Decryption falls back to AES/ECB for backwards compatibility with older encrypted data
  • Backup codes: BCrypt with cost factor 12 (one-way hash)

Used for: TOTP secrets, database connection passwords. Note: EncryptionUtil is duplicated in 3 modules (common, processor-core, workspace-api-core) — this is a known tech debt item.

Token Lifecycle

TokenService manages authentication tokens with type-based expiration:

Token TypeExpiryLinked To
PASSWORD_RESET1 hourUser
EMAIL_VERIFICATION24 hoursUser
INVITATION2 daysOrganisationMembership

On generation, all existing tokens of the same type for the user are invalidated first. Tokens are validated by checking: exists, not expired, not already used. Validation marks the token as used=true.

Invitation tokens have special handling — they link to an OrganisationMembership rather than a user, and are cleaned up (membership reference nulled) when memberships are deleted.

User Session Management

UserSessionService tracks server-side sessions:

  • Each login creates a UserSession record (sessionId UUID, device, deviceName, ipAddress, location, userAgent, organisationSlug)
  • Only one session is marked as current per user
  • User-Agent is parsed to extract browser and OS (e.g. "Chrome on macOS")
  • Sessions can be revoked (sets revokedAt), but users cannot revoke their own current session
  • Device names can be renamed by the user

The ActiveUserFilter (priority 200) validates the session on every request.

Guest Access Tokens

GuestTokenService provides view-level guest access without organisation membership:

  • Token format: 32 bytes from SecureRandom, URL-safe Base64 (43 characters)
  • Storage: SHA-256 hash stored in DB (64 hex characters). Plaintext returned only once on creation.
  • Validation: checks existence, not revoked, not expired, not over usage limit (currentUses >= maxUses)
  • Roles: GUEST_VIEWER (read-only) or GUEST_EDITOR (read-write)
  • Usage tracking: recordAccess() increments currentUses and updates lastAccessedAt

Workspace API Keys

WorkspaceApiKeyService manages API keys for external service access (Jotform, Zapier, Make):

  • Key format: sk_ prefix + 32 bytes SecureRandom URL-safe Base64 (total: sk_ + 43 chars)
  • Storage: SHA-256 hash (64 hex characters) + first 8 chars as keyPrefix for identification
  • Permissions: READ_ONLY or READ_WRITE (enforced by ApiKeyAuthInterceptor in workspace-api)
  • Features: enabled/disabled flag, optional expiration date, lastUsedAt tracking
  • Administration: Requires WORKSPACE_ADMIN permission. Key creation returns plaintext once; revocation soft-deletes by setting enabled = false

The ApiKeyAuthInterceptor (Spring Boot) also supports public access — entities with publicAccess set to READ_ONLY or READ_WRITE are accessible without an API key.

Not Yet Implemented

  • Entity-level permissions for the generated workspace API (design docs exist but not coded)
  • Permission caching (@CacheResult / @CacheInvalidate pattern proposed but not active)
  • Security headers (CSP, X-Frame-Options — see XSS section above)

SchemaStack Internal Developer Documentation