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 changetenantIdin 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
tidmatches the tenant's registeredentraDirectoryId; 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 (
RegistrationAttemptledger) and gated behind email-OTP verification.
Data Protection
- Tenant isolation: shared database, logically isolated per tenant by
tenantIdand 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
RESTRICTforeign keys so a tenant's data can't be orphaned.
Known Gaps
See Known Issues & Limitations for the current list.