Accounts Domain
The Accounts domain handles multi-tenancy, user management, and authentication through Auth0 integration.
Entity Hierarchy
Key Concepts
| Entity | Description |
|---|---|
| Account | Top-level tenant container (like a company). All data is scoped to an account. |
| User | Individual person. Can belong to multiple accounts with different roles. |
| Session | Tracks which account a user currently has selected (stored in Redis). |
| Auth0 Organisation | External SSO tenant in Auth0, linked 1:1 with Account. |
Multi-Tenancy Model
Users can belong to multiple accounts, each with a specific role:
Roles are defined by the AccountRole enum in user.graphql:
| Role | Permissions |
|---|---|
| ADMIN | Full access, can manage members |
| MEMBER | Standard access, can view and edit |
Account Selection Flow
- Session stores
selectedAccountId- all queries scope to this account - Switching accounts updates the session, not the user record
- Sessions are stored in Redis with TTL-based expiration
Authorization
Use case authorization is handled by requireAccountAdmin() in account-authorization.ts:
import { requireAccountAdmin } from './account-authorization';
// Verifies the user is an admin of the account, its parent account, or is FSAdmin
await requireAccountAdmin(accountId, userId, isFSAdmin);Business Rules
- Users must belong to at least one account
- Users with multiple accounts must select one before proceeding
- Admins can manage members; members can view only
- New users must complete registration before accessing features
- Account names should stay in sync with Auth0 org names
- Sessions expire after inactivity (TTL-based)
Common Operations
Getting the Current User and Selected Account
import { requireAccount } from '@/lib/auth';
const { userId, accountId } = await requireAccount();
// Redirects if unauthenticated; throws UnauthorizedError if no account selected.Creating an Account from an Auth0 Organisation
Accounts are provisioned from a selected Auth0 organisation, not created directly from name + email.
import { CreateAccountFromAuth0Org } from '@repo/core';
const useCase = new CreateAccountFromAuth0Org();
const account = await useCase.execute({
auth0OrgId: 'org_abc123',
auth0OrgName: 'Acme Corp',
userId,
});Inviting a User to an Account
import { InviteUserToAccount } from '@repo/core';
const useCase = new InviteUserToAccount();
await useCase.execute({
accountId: 'account-123',
email: 'user@example.com',
inviterName: 'Alice Admin',
userId,
isFSAdmin,
});New members join via the Auth0 invitation flow with the MEMBER role by default. Use UpdateUserRole to promote to ADMIN.
Switching the Selected Account
import { SetSelectedAccount } from '@repo/core';
const useCase = new SetSelectedAccount();
await useCase.execute({
userId,
accountId: 'account-456',
});
// Session now scoped to new accountAuth0 Integration
Account creation and user management sync with Auth0:
| Platform Action | Auth0 Action |
|---|---|
| Create account | Create Auth0 Organisation |
| Add user | Invite to Auth0 Organisation |
| Remove user | Remove from Auth0 Organisation |
| Update account name | Update Auth0 Organisation name |
Important Considerations
- Auth0 API calls cannot be rolled back in transactions
- If Neo4j write succeeds but Auth0 fails, handle orphaned records
- Use background jobs for Auth0 sync when possible
Related Files
| Type | Location |
|---|---|
| GraphQL Schema | packages/core/src/infrastructure/neo4j/schemas/account.graphql |
| Repository | packages/core/src/infrastructure/repositories/account-repository.ts |
| Use Cases | packages/core/src/application/use-cases/account/ |
| Auth0 Integration | packages/core/src/infrastructure/auth0/ |
| Session Management | packages/core/src/infrastructure/redis/session.ts |