Skip to main content

Security & RBAC

Ticku layers two enforcement systems: tenant isolation (which tenant's data a request can touch at all) and role-based access control (what the user may do within it).

Access Control Model

Tier 1 — Tenant isolation (structural)

Before any RBAC check, a request is bound to exactly one tenant. Every query on a tenant model runs through a tenant-scoped Prisma client that injects tenantId into where (reads/updates/deletes) and data (creates). Cross-tenant access is impossible from ordinary application code — see Multi-Tenancy. A CI guard (scripts/check-tenant-scoping.ts) fails the build if a tenant-model query bypasses the scoped client.

Tier 2 — Tenant admin

A membership's isAdmin unlocks the admin panel. Admin procedures build on the tenant-scoped procedure, so admins are still confined to their own tenant.

Tier 3 — Per-project permissions

ProjectMember.roleId points at a ProjectRole holding permission codes. Every mutation asserts the required code (e.g. workspace:tickets:create) via assertProjectPermission. The permission catalog is the single source of truth, imported by both server and client.

Platform admin

isPlatformAdmin procedures are tenant-less by design and deliberately use the raw (unscoped) client, whitelisted in the scoping guard — their whole job is cross-tenant tenant management.

Enforcement

  • Server-side, always. UI gating (hidden/disabled buttons) mirrors permissions but is never the boundary.
  • Shared permission definitions. One catalog for assertion (server) and gating (client) — they cannot drift.
  • Ownership checks. Comment edits/deletes verify the author.
  • Immutable tenantId. Any attempt to change tenantId in an update payload is stripped by the scoped client.

Authentication Security

  • Three sign-in paths (Teams JWT, Azure SSO, session cookie) all resolve to the same server-side user context — see Authentication.
  • Teams JWTs are validated against Entra ID signing keys, audience, and timestamps.
  • Azure-SSO logins are auto-approved only when the token's tid matches the tenant's registered entraDirectoryId; otherwise they fall back to email-domain trust.
  • Passwords (custom login) are hashed; email verification via OTP precedes activation; self-registrations require admin approval.
  • Self-serve tenant registration is rate-limited per IP (RegistrationAttempt ledger) and gated behind email-OTP verification.

Data Protection

  • Tenant isolation: shared database, logically isolated per tenant by tenantId and the scoped client (not a database-per-client model). Attachment download URLs re-check the requester's tenant before issuing a SAS URL.
  • Secrets at rest: per-tenant email credentials are encrypted with AES-256-GCM (EMAIL_ENCRYPTION_KEY).
  • Transport: HTTPS-only; Graph webhook notifications are verified against a shared secret.
  • Attachments: stored in Azure Blob, never on the app server.
  • Suspension, not deletion: tenants and memberships are suspended (reversible); child rows use RESTRICT foreign keys so a tenant's data can't be orphaned.

Known Gaps

See Known Issues & Limitations for the current list.