11 KiB
Tenant User Management Implementation
Overview
This document describes the implementation of tenant user management from the central admin interface. Central administrators can now view and create users for any tenant directly from the tenant detail page.
Features
1. View Tenant Users
- Related list on tenant detail page showing all users for that tenant
- Displays: email, firstName, lastName, createdAt
- Fetches data directly from the tenant's database
2. Create Tenant Users
- Modal dialog for creating new users in a tenant
- Form fields:
- Email (required)
- Password (required)
- First Name (optional)
- Last Name (optional)
- Passwords are automatically hashed with bcrypt
- Creates user directly in the tenant's database
Architecture
Backend Implementation
File: backend/src/tenant/central-admin.controller.ts
Get Tenant Users Endpoint
GET /central/tenants/:id/users
- Connects to the tenant's database using
TenantDatabaseService - Queries the
userstable - Returns array of user records
Create Tenant User Endpoint
POST /central/tenants/:id/users
- Accepts:
{ email, password, firstName?, lastName? } - Hashes password with bcrypt (10 rounds)
- Creates user in tenant database with timestamps
- Returns created user record
Key Implementation Details:
- Uses
tenantDbService.getTenantKnex(tenantId)to get tenant DB connection - Connection pooling ensures efficient database access
- Password hashing is done server-side for security
Frontend Implementation
Components
File: frontend/components/TenantUserDialog.vue
- Reusable modal dialog for creating tenant users
- Form validation (email and password required)
- Loading states and error handling
- Emits 'created' event on success for list refresh
Props:
open: boolean- Dialog visibility statetenantId: string- ID of tenant to create user fortenantName?: string- Display name of tenant
Events:
update:open- Sync dialog visibilitycreated- User successfully created
Page Integration
File: frontend/pages/central/tenants/[[recordId]]/[[view]].vue
Added State:
const showTenantUserDialog = ref(false)
const tenantUserDialogTenantId = ref('')
Handler:
const handleCreateRelated = (objectApiName: string, parentId: string) => {
if (objectApiName.includes('tenants/:parentId/users')) {
tenantUserDialogTenantId.value = parentId
showTenantUserDialog.value = true
return
}
// ... standard navigation for other related lists
}
Refresh Handler:
const handleTenantUserCreated = async () => {
// Refresh current record to update related lists
if (recordId.value && recordId.value !== 'new') {
await fetchRecord(recordId.value)
}
}
Configuration
File: frontend/composables/useCentralEntities.ts
Added to tenantDetailConfig.relatedLists:
{
title: 'Tenant Users',
relationName: 'users',
objectApiName: 'tenants/:parentId/users',
fields: [
{ name: 'email', label: 'Email', type: 'TEXT', required: true },
{ name: 'firstName', label: 'First Name', type: 'TEXT' },
{ name: 'lastName', label: 'Last Name', type: 'TEXT' },
{ name: 'createdAt', label: 'Created', type: 'DATE_TIME' }
],
canCreate: true
}
Key Details:
objectApiName: 'tenants/:parentId/users'- Special format for nested resource:parentIdplaceholder is replaced with actual tenant ID at runtimecanCreate: trueenables the "New" button in the related list
Related List Component
File: frontend/components/RelatedList.vue
Dynamic API Path Resolution:
let apiPath = props.config.objectApiName.replace(':parentId', props.parentId)
const response = await api.get(`/${apiPath}`, {
params: { [parentField]: props.parentId }
})
This allows the component to handle nested resource paths like tenants/:parentId/users.
User Flow
Creating a Tenant User
- Navigate to Central Admin → Tenants
- Click on a tenant to view details
- Scroll to "Tenant Users" related list
- Click "New" button
- Fill in the form:
- Enter email address
- Set password
- Optionally add first and last name
- Click "Create User"
- Dialog closes and related list refreshes with new user
Viewing Tenant Users
- Navigate to Central Admin → Tenants
- Click on a tenant to view details
- Scroll to "Tenant Users" related list
- View table with all users for that tenant
- See email, name, and creation date for each user
Security Considerations
Password Handling
- Passwords are sent over HTTPS
- Backend hashes passwords with bcrypt (10 rounds) before storage
- Passwords never stored in plain text
- Hashing is done server-side, not client-side
Access Control
- Only central admin users can access these endpoints
- Protected by authentication middleware
- Tenant database connections use secure connection pooling
Database Access
- Central admin connects to tenant databases on-demand
- Connections are cached but validated before use
- No direct SQL injection risk (using Knex query builder)
Database Schema
Tenant User Table Structure
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
firstName VARCHAR(255),
lastName VARCHAR(255),
createdAt DATETIME,
updatedAt DATETIME
-- Additional fields may exist in actual schema
)
API Reference
Get Tenant Users
Request:
GET /api/central/tenants/{tenantId}/users
Authorization: Bearer <jwt-token>
Response:
[
{
"id": "uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2025-01-26T12:00:00Z",
"updatedAt": "2025-01-26T12:00:00Z"
}
]
Create Tenant User
Request:
POST /api/central/tenants/{tenantId}/users
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"email": "newuser@example.com",
"password": "SecurePassword123!",
"firstName": "Jane",
"lastName": "Smith"
}
Response:
{
"id": "uuid",
"email": "newuser@example.com",
"firstName": "Jane",
"lastName": "Smith",
"createdAt": "2025-01-26T12:00:00Z",
"updatedAt": "2025-01-26T12:00:00Z"
}
Testing
Manual Testing Steps
-
Setup:
- Ensure Docker containers are running
- Have at least one tenant created
- Be logged in as central admin
-
View Users:
- Navigate to /central/tenants
- Click on a tenant
- Verify "Tenant Users" related list appears
- Verify existing users are displayed
-
Create User:
- Click "New" in Tenant Users section
- Verify dialog opens
- Fill in required fields (email, password)
- Click "Create User"
- Verify success message
- Verify dialog closes
- Verify new user appears in list
-
Error Handling:
- Try creating user without email
- Try creating user without password
- Try creating user with duplicate email
- Verify appropriate error messages
Automated Testing (Future)
describe('Tenant User Management', () => {
it('should fetch tenant users', async () => {
const response = await api.get('/central/tenants/tenant-id/users')
expect(response).toBeInstanceOf(Array)
})
it('should create tenant user', async () => {
const newUser = {
email: 'test@example.com',
password: 'password123',
firstName: 'Test',
lastName: 'User'
}
const response = await api.post('/central/tenants/tenant-id/users', newUser)
expect(response.email).toBe(newUser.email)
expect(response.password).toBeUndefined() // Should not return password
})
})
Future Enhancements
Planned Features
-
Full CRUD Operations:
- Edit tenant user details
- Delete tenant users
- Update passwords
-
Role Management:
- Assign roles to users during creation
- View and edit user roles
- Permission management
-
User Navigation:
- Click on user to view details
- Dedicated user detail page
- Activity history
-
Bulk Operations:
- Create multiple users via CSV import
- Bulk role assignment
- Bulk user activation/deactivation
-
Password Management:
- Password reset functionality
- Force password change on next login
- Password strength indicators
-
Audit Logging:
- Track user creation by central admin
- Log user modifications
- Export audit logs
-
Search and Filter:
- Search users by email/name
- Filter by role/status
- Advanced filtering options
Implementation Notes
Design Decisions
-
Modal vs Navigation:
- Chose modal dialog over page navigation
- Reason: Keeps user in context of tenant detail page
- Better UX for quick user creation
-
Special API Path Format:
- Used
tenants/:parentId/usersformat - Reason: Indicates nested resource structure
- Clear relationship between tenant and users
- Used
-
Separate Dialog Component:
- Created reusable TenantUserDialog component
- Reason: Could be reused in other contexts
- Easier to maintain and test
-
Server-Side Password Hashing:
- Hash passwords in backend, not frontend
- Reason: Security best practice
- Consistent with authentication flow
Known Limitations
-
No Password Validation:
- Currently no minimum password requirements
- Could add password strength validation
-
No Email Validation:
- Basic email format check only
- Could add email verification
-
No User Status:
- Users are created as active by default
- No activation/deactivation workflow
-
No Role Assignment:
- Users created without specific roles
- Role management to be added
Related Documentation
- RELATED_LISTS_IMPLEMENTATION.md - Related lists feature
- CENTRAL_ADMIN_AUTH_GUIDE.md - Central admin authentication
- MULTI_TENANT_IMPLEMENTATION.md - Multi-tenancy architecture
- TENANT_MIGRATION_GUIDE.md - Tenant database setup
Troubleshooting
Common Issues
Issue: "Cannot GET /api/api/central/tenants/:id/users"
- Cause: Double API prefix
- Solution: Check that baseUrl in useApi doesn't include /api prefix
Issue: "Dialog doesn't open"
- Check: showTenantUserDialog state is being set
- Check: Dialog component is imported correctly
- Check: v-model:open binding is correct
Issue: "User not appearing in list after creation"
- Check: handleTenantUserCreated is calling fetchRecord
- Check: API returning correct data
- Check: Related list config matches API response fields
Issue: "Cannot create user - validation error"
- Ensure email and password are filled
- Check network tab for actual error from backend
- Verify tenant database schema matches expected structure
Issue: "Password not hashing"
- Verify bcrypt is installed in backend
- Check backend logs for hashing errors
- Ensure password field is being passed to backend