Feat/multi tenant - #635
Draft
andrechristikan wants to merge 212 commits into
Draft
Conversation
Added `TenantAuthService` for user authentication and tenant membership validation. Introduced new DTOs, controller, and documentation to support login with tenant credential validation. Updated `TenantModule` to include the new service and controller.
Added support for `EnumRoleScope` to distinguish between platform and tenant-level roles across the system. Updated role repository, tenant and role services, and Prisma schema to reflect the changes. Expanded tenant controller endpoints and adjusted existing logic to handle role scope validation and enumeration. Updated related documentation and seed data.
Added support for Just-in-Time (JIT) tenant access with the `tenant-platform-support` role, enabling platform admins to assume temporary access to tenants. Includes role-based time-limited membership creation, expiration handling, manual revocation, and auditing. Updated schemas, controllers, services, and documentation accordingly.
…ervice` Moved tenant member management responsibilities (e.g., add, update, delete, JIT access) from `TenantService` to a dedicated `TenantMemberService`. Updated controllers, guards, and interfaces to reflect the separation of concerns.
Added a new `ProjectModule` enabling tenants to manage projects. Features include project creation, updates, deletion, role-based access control, sharing, and API endpoints to interact with projects.
…ionality Deleted `ProjectShare`, related enums, guards, decorators, and schema references. Simplified `ProjectModule` by focusing on member-based access control. Cleaned up Prisma schema to reflect these changes.
…f `@db.ObjectId` annotations
…eplace `IRequestApp` usage
…nt` interface in tenant module
Contributor
|
Hello @andrechristikan , Thanks :) |
Contributor
|
unbelievable @andrechristikan , while I was writing the previous comment you just committed a lot of work :D |
…a request context service
…reation transactions
…to layered modules
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-Tenant Feature Scope
This document defines the scope for the multi-tenant feature.
Some things are intentionally kept simple for the initial implementation to avoid over-engineering.
Out of Scope
The following are explicitly not included in this initial implementation:
lastTenantIdin the responseGeneral
Module: Tenant
Setup
lastTenantIdon the User model. This is set on sign-up, on invite acceptance, and on tenant switch — allowing the frontend to redirect the user to their last active tenant on login or page load.x-tenant-idheader to determine the current tenant context, so users can switch between tenants.Editable Fields
Only
nameanddescriptionare editable. No status or archive functionality needed.slugis not updated whennamechanges. It can only be changed via a dedicated endpoint, similar to how username updates work.Roles
Tenant roles use an enum directly — no relation to the Role model. Roles are static. One tenant has exactly one owner.
Leave & Delete Rules
ownerwants to leave → must transfer ownership to another existing member first.owneris the last member → tenant is immediately soft deleted.Transfer Ownership Rules
Soft Delete
deletedAtonly. Nostatusfield.pendinginvitations for the tenant and its projects are revoked.Module: Project
Setup
deletedAt). Nostatusfield needed.Context
Current project is determined by
:projectIdroute param — no header needed.The tenant context is already known from
x-tenant-id. The guard validates that the project belongs to the current tenant.Editable Fields
Only
nameanddescriptionare editable.slugis not updated whennamechanges. It can only be changed via a dedicated endpoint, similar to how username updates work.Roles
Project roles are
admin,member,viewer. Leave this as a skeleton — each company will have different requirements.Leave Rules
adminwants to leave → must promote another member toadminfirst.adminis the last member → project is immediately soft deleted.Member Source & Role Assignment Rules
Only existing tenant members can be invited to a project. There is no direct path from outside the tenant into a project — users must be a tenant member first.
owneradmin— hardcoded, cannot be changedadminadmin— hardcoded, cannot be changedmemberviewer,member,adminRevoke Access
Only tenant
ownerandadmincan revoke any project member's access.Soft Delete
deletedAtonly. Nostatusfield.ProjectMemberrecords are soft deleted.pendinginvitations for the project are revoked.Module: Invitation
Invitations are split into two separate contexts: tenant and project. They are not combined into a single source.
Invite Type
Before creating an invite, the frontend checks whether the email is already registered via
POST /check/email. Based on the response, the invite is created with an explicittype:registeredunregisteredInvited users do not get a default tenant and project created — they are joining an existing one.
Tenant Invitation
Who can invite: tenant
ownerandadmin.Role Rules
tenantRoleis required and must beadminormember.ownercannot be assigned via invite — ownership can only be transferred through the Transfer Ownership flow.Model
Must include:
invitedById,invitedEmail,tenantId,tenantRole,type,status,expiresAt,revokedAt,revokedById.Member records are not pre-created before the invite is accepted.
Status Lifecycle
pending→accepted/expired/revokedExpiry
Default 7 days, configurable via config. Inviter can override per request via optional
expiresInfield.Duplicate Handling
Determined by exact match on
invitedEmail+tenantId:pendinginvite → replace: existing invite is revoked, new invite is created.Revoke
Tenant
owneroradmincan revoke apendinginvite at any time. Once revoked, the token is immediately invalidated.Notification
Invitee receives an email with an invitation token regardless of registration status.
registered→ claim with token only. Auto joined to the tenant.unregistered→ directed to sign-up using the token. Upon registration, auto joined to the tenant.On Completed
Set
lastTenantIdfor the user immediately after joining.Project Invitation
Who can invite: tenant
owner, tenantadmin(via guard), and projectadmin.Role Rules
projectRoleis required:admin,member, orviewer.Model
Must include:
invitedById,invitedEmail,projectId,projectRole,status,expiresAt,revokedAt,revokedById.Member records are not pre-created before the invite is accepted.
Status Lifecycle
pending→accepted/expired/revokedExpiry
Default 7 days, configurable via config. Inviter can override per request via optional
expiresInfield.Duplicate Handling
Determined by exact match on
invitedEmail+projectId:pendinginvite → replace: existing invite is revoked, new invite is created.Revoke
Tenant
owner, tenantadmin, or projectadmincan revoke apendinginvite at any time. Once revoked, the token is immediately invalidated.Notification
Invitee receives an email with an invitation token. On claim, they are auto joined to the project with the assigned role.
On Completed
No change to
lastTenantId— user is already a tenant member.Module: Auth (Login Response)
On login, include a
tenantobject in the response so the frontend can immediately set the correct tenant context without an extra request.Config
The following configs are required under
auth.config.ts:tenant.header— header key for resolving tenant context (x-tenant-id)tenant.invite.defaultExpiresIn— default expiry for invite tokens, can be overridden per requestFlows
1. Sign-Up Flow
flowchart TD A[User Sign Up] --> B[Create User] B --> C[Create default Tenant\nrandom name + slug] C --> D[Add user as tenant owner] D --> E[Create default Project\nrandom name + slug] E --> F[Add user as project admin] F --> G[Set lastTenantId on User] G --> H[Return login response\nwith tenant object]2. Login Flow
flowchart TD A[User Login] --> B[Validate credentials] B --> C[Return access token\n+ tenant object from lastTenantId] C --> D[Frontend sets x-tenant-id\nfrom tenant.id] D --> E[User lands on last active tenant]3. Tenant Invitation Flow
flowchart TD A[Owner or Admin\nwants to invite] --> B[Check email via POST /check/email] B --> C{Email registered?} C -- Yes --> D[Create invite\ntype: registered] C -- No --> E[Create invite\ntype: unregistered] D --> F[Send email with token] E --> F F --> G[Invitee clicks email link] G --> H{Invite type?} H -- registered --> I[Claim with token only\nAuto join tenant] H -- unregistered --> J[Sign up with token\n+ password + signUpFrom] J --> K[Create User\nAuto join tenant] I --> L[Set lastTenantId\nMark invite as accepted] K --> L L --> M[Done]4. Project Invitation Flow
flowchart TD A[Owner, Admin, or Project Admin\nwants to invite] --> B[Select existing tenant member] B --> C[Create project invite\nwith projectRole] C --> D[Send email with token] D --> E[Invitee clicks email link] E --> F[Claim with token only\nAuto join project with assigned role] F --> G[Mark invite as accepted] G --> H[Done]5. Revoke Tenant Invite Flow
flowchart TD A[Owner or Admin\ncalls revoke endpoint] --> B{Invite status is pending?} B -- No --> C[Return error\nalready accepted/expired/revoked] B -- Yes --> D[Set status to revoked\nSet revokedAt + revokedById] D --> E[Token immediately invalidated] E --> F[Done]6. Revoke Project Invite Flow
flowchart TD A[Owner, Admin, or Project Admin\ncalls revoke endpoint] --> B{Invite status is pending?} B -- No --> C[Return error\nalready accepted/expired/revoked] B -- Yes --> D[Set status to revoked\nSet revokedAt + revokedById] D --> E[Token immediately invalidated] E --> F[Done]7. Tenant Switch Flow
flowchart TD A[User switches tenant] --> B[Frontend sets new x-tenant-id] B --> C[Request hits tenant guard] C --> D[Resolve tenant from x-tenant-id] D --> E[Validate user is tenant member] E --> F[Update lastTenantId on User] F --> G[Continue to controller]8. Project Access Flow
flowchart TD A[Request with x-tenant-id\n+ projectId param] --> B[Tenant guard\nresolve tenant context] B --> C{Is user tenant owner\nor admin?} C -- Yes --> D[Grant project admin access\nauto, no DB check needed] C -- No --> E[Check ProjectMember record] E --> F{Is user project member?} F -- Yes --> G[Grant access\nbased on project role] F -- No --> H[403 Forbidden] D --> I[Continue to controller] G --> I