RentOne Tenant Service — Technical & Business Documentation
Version: 0.0.1-SNAPSHOT
Runtime: Java 21 · Spring Boot 3.5.6 · Spring Cloud 2025.0.0
Database: MongoDB (dynamic multi-tenant)
Service Registry: Netflix Eureka
Inter-service Communication: OpenFeign + Unirest
Table of Contents
- Service Overview
- Tenant Onboarding Flow
- Tenant Administrator Creation
- Tenant Authentication
- Access Control Group (ACG)
- Tenant Management
- Tenant User Management
- Security
- MongoDB Collections
- External Dependencies
- Entity Reference
- Cross-Cutting Concerns
1. Service Overview
The RentOne Tenant Service is the central identity and configuration authority within the RentOne microservices platform. Every request that flows through the system is scoped to a tenant — a named, isolated business entity (e.g. a property management company). This service owns the entire lifecycle of tenants and their administrator users.
Responsibilities
- Register new tenants and assign them a dedicated MongoDB database connection
- Authenticate tenant administrators and issue JWT tokens
- Manage tenant administrator profiles, roles, and access permissions
- Resolve tenant database connections on every inbound request via a per-request filter
- Propagate real-time token refresh events to active browser sessions over SSE
- Report unhandled exceptions to the Notifications Service
Role in the RentOne Architecture
All other RentOne microservices operate within a tenant context. They rely on this service to:
- Confirm that a tenant exists and is enabled
- Obtain the correct MongoDB connection string for that tenant
- Validate the JWT token in the
Authorizationheader (token is issued here)
The Tenant Service is therefore a critical path dependency — no other service can process a request without a valid, enabled tenant registered here.
Infrastructure Topology
flowchart TD
Client[Client / Browser]
Gateway[API Gateway]
TenantSvc[Tenant Service\nPort: registered via Eureka]
Eureka[Eureka Service Registry]
CloudApp[Cloud App Service\nlocalhost:6004]
Notifications[Notifications Service]
SharedDB[(Tenant Registry DB\nrentone_tenants collection)]
TenantDB[(Tenant-Specific DB\nper-tenant MongoDB)]
SSE[SSE Channel\n/tenants/sse/connect]
Client -->|HTTP Requests| Gateway
Gateway -->|Route| TenantSvc
TenantSvc -->|Register & Discover| Eureka
TenantSvc -->|Resolve Mongo URL| SharedDB
TenantSvc -->|User Data per Tenant| TenantDB
TenantSvc -->|Get Default ACG| CloudApp
TenantSvc -->|Report Errors| Notifications
TenantSvc -->|Push token-refresh events| SSE
SSE -->|Real-time Events| Client
2. Tenant Onboarding Flow
Overview
Onboarding registers a new named tenant in the system. The tenant ID becomes the routing key for all future requests from that business entity. Once registered, the tenant can create administrator users and begin operating within the platform.
Business Flow
flowchart TD
A[Receive Create Tenant Request] --> B{Tenant ID Already Exists?}
B -->|Yes| C[Return Error: Tenant Already Exists]
B -->|No| D[Normalise Tenant ID to UPPERCASE]
D --> E[Assign Default MongoDB URL]
E --> F[Set Creation Date]
F --> G[Save Tenant Record]
G --> H[Return Success with Tenant Object]
Data Flow
flowchart LR
API[POST /rentone/tenants/create] --> SVC[Tenant Registration Logic]
SVC --> DUP{Duplicate Check\nby Tenant ID}
DUP -->|Exists| ERR[400 — Tenant Already Exists]
DUP -->|New| NORM[Uppercase Tenant ID]
NORM --> URL[Assign RENTONE_DEFAULT_DB_URI]
URL --> SAVE[(rentone_tenants collection)]
SAVE --> RESP[Success Response + Saved Tenant]
REST API
| Method | Path | Auth Required | Description |
|---|---|---|---|
POST |
/rentone/tenants/create |
No | Register a new tenant |
Request Body:
{
"tenantId": "ACME_CORP",
"enabled": true
}
Success Response:
{
"success": true,
"message": "Tenant ACME_CORP has been created successfully",
"data": {
"tenantId": "ACME_CORP",
"mongoURL": "<default-db-uri>",
"creationDate": "2026-06-25T10:00:00.000Z",
"enabled": true
}
}
Error Response:
{
"success": false,
"message": "Tenant Already exist with this name"
}
Business Rules
- The tenant ID is always stored and matched in UPPERCASE
- The MongoDB URL assigned at creation is the platform default (
RENTONE_DEFAULT_DB_URIenvironment variable); it can be updated separately - The
enabledflag controls whether the tenant can be used by the routing filter — a disabled tenant will cause all downstream requests to be rejected with401 Unauthorized - No authentication is required to create a tenant (this endpoint is a bootstrap/admin operation)
Validation Rules
tenantIdmust not already exist in therentone_tenantscollectiontenantIdis normalised (uppercased) before persistence — duplicate detection is case-insensitive
MongoDB Collection
| Collection | Field | Notes |
|---|---|---|
rentone_tenants |
tenantId |
Primary key (String, uppercase) |
mongoURL |
MongoDB connection string for this tenant | |
creationDate |
Timestamp of registration | |
enabled |
Whether tenant is active |
Error Scenarios
| Scenario | HTTP Status | Message |
|---|---|---|
| Tenant ID already registered | 200 (success: false) | "Tenant Already exist with this name" |
| Unhandled exception | 500 | Generic error; error reported to Notifications Service |
3. Tenant Administrator Creation
Overview
After a tenant is registered, one or more administrator users must be created. These users are stored in the tenant's own MongoDB database (not the shared registry). Each user is assigned a role (userTypes), and the default permission set for that role is fetched from the Cloud App Service at the point of creation.
Business Flow
flowchart TD
A[Receive Create User Request] --> B{Valid Email Format?}
B -->|No| C[Return Error: Invalid Email]
B -->|Yes| D{User Already Exists?}
D -->|Yes| E[Return Error: User Already Exists]
D -->|No| F{Required Fields Present?}
F -->|No| G[Return Error: Missing Fields]
F -->|Yes| H[Fetch Default ACG from Cloud App]
H --> I{ACG Retrieved Successfully?}
I -->|No| J[Return Error: Failed to Fetch ACG]
I -->|Yes| K[Generate 128-bit Random Salt]
K --> L[Hash Password with Salt + Pepper]
L --> M[Save User to Tenant DB]
M --> N[Return Success]
Data Flow
flowchart LR
API[POST /tenants/tenants-user/v1/create] --> VAL[Validate Email + Fields]
VAL --> DUP{Duplicate Email Check}
DUP -->|Exists| ERR1[Error: User Exists]
DUP -->|New| ACG[GET Cloud App\n/cloud-app/system-info/get/default-access-level]
ACG -->|ACG Map| SALT[Generate Salt\nSecureRandom 16 bytes]
SALT --> HASH[BCrypt Hash\npassword + salt + pepper]
HASH --> SAVE[(tenants_users collection\ntenant-specific DB)]
SAVE --> RESP[Success Response]
REST API
| Method | Path | Auth Required | Description |
|---|---|---|---|
POST |
/tenants/tenants-user/v1/create |
No | Create a new tenant administrator user |
Request Body:
{
"email": "admin@acme.com",
"tenantId": "ACME_CORP",
"tenantUserName": "John Doe",
"password": "SecurePassword123",
"userTypes": "SUPER_ADMIN",
"profileImage": "https://..."
}
Success Response:
{
"success": true,
"message": "Tenants User created successfully"
}
Business Rules
- The
emailaddress is the unique identifier (primary key) for every tenant user - The
userTypesfield determines the default permission map fetched from Cloud App - Passwords are never stored in plaintext — they go through salt + pepper + BCrypt hashing
- The
accessLevelmap is not accepted from the client at creation time; it is always sourced from Cloud App based onuserTypes - A
SUPER_ADMINuser cannot be deleted once created
Validation Rules
emailmust pass RFC-compliant email format validation (ValidationUtils.isValidEmail)tenantId,password,tenantUserName, anduserTypesare all required — any missing field returns an error- Duplicate detection is by exact email match (case-sensitive, as stored)
External Dependencies
| Service | Endpoint | Purpose |
|---|---|---|
| Cloud App Service | GET /cloud-app/system-info/get/default-access-level?userType={type} |
Retrieve the default UniqueAccessControlGroup → ACGAccessLevel map for the given user type |
Error Scenarios
| Scenario | Message |
|---|---|
| Invalid email format | "Invalid Email Id" |
| Email already registered | "This User Name Already Exist" |
| Missing required fields | "Some Fields are mission" |
| Cloud App returns failure | "Failed to fetch default access levels" |
| Cloud App returns empty body | "Empty response from Cloud App" |
| Cloud App unreachable | "Error fetching default access levels: {exception message}" |
4. Tenant Authentication
Overview
Authentication is credential-based (email + password). On success, a signed JWT is issued containing the user's identity, tenant ID, user type, ACG permissions, and login platform. The token is valid for 24 hours. A separate refresh token endpoint allows a new token to be issued without re-authentication.
Business Flow
flowchart TD
A[Receive Login Request] --> B{User Exists?}
B -->|No| C[Return Error: Incorrect Username]
B -->|Yes| D{Password Correct?\nBCrypt verify with stored salt + pepper}
D -->|No| E[Return Error: Incorrect Password]
D -->|Yes| F[Set Platform to ADMIN_WEB if not provided]
F --> G[Load Spring UserDetails]
G --> H[Generate JWT\n24-hour expiry]
H --> I[Record Last Login Timestamp]
I --> J[Return Auth Response with JWT + User Profile]
Data Flow
flowchart TD
API[POST /tenants/tenants-user/v1/auth/login] --> LOOKUP[(tenants_users\nlookup by email)]
LOOKUP -->|Not Found| ERR1[Error: Incorrect Username]
LOOKUP -->|Found| PWD[Verify: rawPwd + salt + pepper\nBCrypt match]
PWD -->|Mismatch| ERR2[Error: Incorrect Password]
PWD -->|Match| JWT[Generate JWT\nemail + ACG + userType + tenantId + platform]
JWT --> UPD[(Update lastLogin\nin tenants_users)]
UPD --> RESP[Return TenantUserDTO + authToken]
REST API
| Method | Path | Auth Required | Description |
|---|---|---|---|
POST |
/tenants/tenants-user/v1/auth/login |
No | Authenticate and receive JWT |
POST |
/tenants/tenants-user/refresh-token |
No | Issue a new JWT for an existing user |
PUT |
/tenants/tenants-user/reset-password |
No | Reset a user's password |
Login Request Body:
{
"username": "admin@acme.com",
"password": "SecurePassword123",
"platform": "ADMIN_WEB"
}
Login Success Response:
{
"success": true,
"message": "Authentication successful",
"data": {
"email": "admin@acme.com",
"tenantId": "ACME_CORP",
"tenantUserName": "John Doe",
"userTypes": "SUPER_ADMIN",
"accessLevel": { "...": "..." },
"authToken": "<JWT>",
"password": null
}
}
Refresh Token Request: POST /tenants/tenants-user/refresh-token?email=admin@acme.com
Reset Password Request: PUT /tenants/tenants-user/reset-password?email=admin@acme.com&newPassword=NewPass123
Authentication Rules
- Platform defaults to
ADMIN_WEBif not supplied in the request - JWT expiry is fixed at 24 hours from issue time
passwordis always set to null in the response — it is never returned to the client- Last login timestamp is updated in the database on every successful login
- Password reset re-hashes the new password using the existing stored salt (salt is never rotated on reset)
- The refresh token endpoint issues a new 24-hour JWT with
ADMIN_WEBplatform without credential verification
Security Considerations
- Passwords are stored as:
BCrypt(rawPassword + salt + PASSWORD_PEPPER)— three-layer protection - Salt is a 128-bit (16-byte) cryptographically random value unique per user
- Pepper is a server-side environment variable (
PASSWORD_PEPPER), not stored in the database - JWT contains the full ACG permission map, enabling stateless authorization in downstream services
Failure Scenarios
| Scenario | Message |
|---|---|
| Email not found | "Incorrect user name" |
| Password mismatch | "Incorrect password" |
| Refresh token — user not found | "User does not exist" |
| Reset password — user not found | "User does not exist" |
5. Access Control Group (ACG)
Overview
The Access Control Group (ACG) system defines what actions a user is permitted to perform within the platform. Permissions are stored as a map of UniqueAccessControlGroup keys to ACGAccessLevel values, embedded directly on the TenantsUser document. The default permission set is defined externally in the Cloud App Service, keyed by user type.
How Permissions Are Assigned
At user creation, the Tenant Service calls the Cloud App Service to retrieve the default ACG map for the specified userTypes (e.g. SUPER_ADMIN). This map is stored on the user record. When a user is updated, a new ACG map can be supplied directly in the update payload — the service replaces the stored map and immediately issues a refreshed JWT via SSE so the active browser session picks up the new permissions without requiring a re-login.
Business Flow
flowchart TD
A[User Creation / Update] --> B{Operation Type?}
B -->|Create| C[Fetch Default ACG\nfrom Cloud App by UserType]
B -->|Update| D[Accept ACG Map from Request Payload]
C --> E[Store ACG Map on User Record]
D --> E
E --> F[Embed ACG in JWT at Login / Refresh]
F --> G[JWT Consumed by Downstream Services\nfor Authorization Decisions]
Data Flow
flowchart LR
CloudApp[Cloud App Service\nGET default-access-level] -->|ACG Map| UserRecord[(tenants_users)]
UserRecord -->|ACG embedded in JWT| JWT[JWT Token]
JWT -->|Authorization Header| DownstreamSvc[Other RentOne Services]
UpdateAPI[PUT /tenants/tenants-user/update] -->|New ACG Map| UserRecord
UserRecord -->|Token Refresh Event| SSE[SSE Channel\ntoken-refresh event]
SSE -->|New JWT| Browser[Active Browser Session]
REST API
| Method | Path | Description |
|---|---|---|
POST |
/tenants/tenants-user/v1/create |
ACG set automatically from Cloud App at creation |
PUT |
/tenants/tenants-user/update |
ACG map can be updated directly |
POST |
/tenants/tenants-user/refresh-token |
Re-issues JWT with current stored ACG |
Business Rules
- ACG is set only from Cloud App at creation — clients cannot inject custom ACG maps during user creation
- At update time, the client-supplied ACG map replaces the stored one entirely
- After any update that changes ACG, a
token-refreshevent is pushed over SSE so the active session does not need to log out and back in - The ACG map is embedded in the JWT payload — downstream services read permissions from the token without calling this service
- A
SUPER_ADMINuser type receives the highest default permissions as defined in Cloud App
User Types
| Type | Description |
|---|---|
SUPER_ADMIN |
Full platform access; cannot be deleted |
| Other types | Configured in Cloud App; permission set varies |
Validation Rules
- At creation, if Cloud App returns a failure or empty response, user creation is aborted
- At update, the supplied
accessLevelmap is accepted as-is from the request — no schema validation is performed server-side beyond what Cloud App originally defines
6. Tenant Management
Overview
This section covers the lifecycle operations on the tenant record itself: retrieving a single tenant, listing all tenants with their database URLs, and resolving the MongoDB connection string used by the routing filter on every inbound request.
Business Flow
flowchart TD
A[Inbound Request to Any Service] --> B[TenantFilter checks xTenant Header]
B --> C{Connection Cached?}
C -->|Yes| D[Use Cached DB Connection]
C -->|No| E[GET /rentone/tenants/get/tenant/mongo-url]
E --> F{Tenant Exists?}
F -->|No| G[Return 401 Unauthorized]
F -->|Yes| H{Tenant Enabled?}
H -->|No| I[Return 401 Unauthorized]
H -->|Yes| J[Return Mongo URL]
J --> K[Replace Placeholder with Tenant Name]
K --> L[Cache Connection for Tenant]
L --> D
D --> M[Process Request in Tenant DB Context]
Data Flow
flowchart LR
Filter[TenantFilter] -->|tenantId| MongoURLAPI[GET /rentone/tenants/get/tenant/mongo-url]
MongoURLAPI --> DB[(rentone_tenants)]
DB -->|mongoURL template| Filter
Filter -->|resolved URL| MongoStorage[ThreadLocal MongoConnectionStorage]
MongoStorage --> MongoTemplate[Dynamic MongoTemplate\nDatabaseConfiguration]
REST API
| Method | Path | Auth Required | Description |
|---|---|---|---|
GET |
/rentone/tenants/get/tenant/mongo-url |
No | Resolve MongoDB URL for a tenant (used by routing filter) |
GET |
/rentone/tenants/get/by-id |
Yes | Retrieve full tenant record by ID |
GET |
/rentone/tenants/get-all-tenants-with-db-url |
No | Return map of all tenant IDs to MongoDB URLs |
Get Mongo URL: GET /rentone/tenants/get/tenant/mongo-url?tenantId=ACME_CORP
Get Tenant by ID: GET /rentone/tenants/get/by-id?tenantId=ACME_CORP
Get All Tenants with DB URLs: GET /rentone/tenants/get-all-tenants-with-db-url
Returns:
{
"success": true,
"message": "Tenant Retrieved successfully",
"data": {
"ACME_CORP": "mongodb://...",
"BETA_TENANT": "mongodb://..."
}
}
Business Rules
- The routing filter maintains an in-memory cache (
CLIENT_DB_CONNECTIONSmap) of resolved tenant connections — the lookup API is only called on the first request per tenant per service instance - If a tenant's
enabledflag isfalse, the Mongo URL endpoint returnssuccess: false, causing the filter to reject the request with401 - The
mongoURLfield stores a template URL containing a placeholder (TENANTS_REPLACEMENT) that is substituted with the actual tenant ID at runtime - The
get-all-tenants-with-db-urlendpoint is used during service startup or cache warm-up by other services
Error Scenarios
| Scenario | Message |
|---|---|
| Tenant ID not found | "Tenant Not Exist With This Name" |
| Tenant is disabled | "Client Not Enabled" |
| MongoDB URL not set on tenant | "Mongo URL Not Exist for this Tenant" |
| Null tenant ID in request | "Tenant Does Not Exist" |
| Tenant not found by ID | "Tenant Does Not Exist" |
7. Tenant User Management
Overview
After initial setup, tenant administrator users can be retrieved, updated, listed, and deleted through this set of APIs. All user data is scoped to the tenant's own database — users from different tenants are never mixed.
Business Flow
flowchart TD
A[User Management Operation] --> B{Operation Type?}
B -->|Get User| C[Lookup by Email]
B -->|Get Profile| D[Lookup by Email\nReturn safe profile fields only]
B -->|List All| E[Paginated query\nscoped by xTenant header]
B -->|Get Name Map| F[Return email→name map\nfor tenant]
B -->|Update| G[Lookup by Email\nUpdate fields\nRefresh JWT via SSE]
B -->|Delete| H{Is SUPER_ADMIN?}
H -->|Yes| I[Return Error: Cannot Delete Super Admin]
H -->|No| J[Delete User Record]
Data Flow
flowchart LR
UpdateAPI[PUT /tenants/tenants-user/update] --> Lookup[(tenants_users lookup)]
Lookup --> Update[Update: name, image, userTypes, ACG]
Update --> Save[(Save Updated User)]
Save --> Refresh[Refresh JWT]
Refresh --> SSE[Push token-refresh\nover SSE to active session]
DeleteAPI[DELETE /tenants/tenants-user/delete] --> CheckSA{SUPER_ADMIN?}
CheckSA -->|No| DelRecord[(Delete from tenants_users)]
CheckSA -->|Yes| ErrSA[Error: Cannot Delete Super Admin]
REST API
| Method | Path | Auth Required | Description |
|---|---|---|---|
GET |
/tenants/tenants-user/get |
Yes | Get full user record by email |
GET |
/tenants/tenants-user/get/profile |
No | Get safe public profile by email |
GET |
/tenants/tenants-user/get-all |
No | Paginated list of all users for a tenant |
GET |
/tenants/tenants-user/get-names |
No | Map of email → display name for a tenant |
PUT |
/tenants/tenants-user/update |
No | Update user profile, role, and ACG |
DELETE |
/tenants/tenants-user/delete |
Yes | Delete a user (SUPER_ADMIN protected) |
Get All Users (Paginated):
GET /tenants/tenants-user/get-all?page=1&size=20
Header: xTenant: ACME_CORP
Get Name Map:
GET /tenants/tenants-user/get-names
Header: xTenant: ACME_CORP
Returns:
{
"success": true,
"message": "Tenant User name retrieved successfully",
"data": {
"admin@acme.com": "John Doe",
"manager@acme.com": "Jane Smith"
}
}
Update User Request Body:
{
"email": "admin@acme.com",
"tenantUserName": "John Updated",
"profileImage": "https://...",
"userTypes": "SUPER_ADMIN",
"accessLevel": { "...": "..." }
}
Get Profile Response:
{
"success": true,
"message": "User Profile Retrieved Successfully",
"data": {
"email": "admin@acme.com",
"tenantUserName": "John Doe",
"profileImage": "https://...",
"userTypes": "SUPER_ADMIN",
"accessLevel": { "...": "..." },
"lastLogin": "2026-06-25T09:00:00.000Z"
}
}
Business Rules
- All user queries are scoped to the tenant established by the
xTenantrequest header — cross-tenant data access is structurally impossible - Pagination is 1-indexed: page=1 returns the first page
- When
pageandsizeare omitted from the list endpoint, all users are returned without pagination - Deleting a
SUPER_ADMINis explicitly blocked to prevent loss of system access - After any update, the service automatically generates a new JWT and pushes it to the user's active SSE channel — no manual re-login is required
- The profile endpoint (
get/profile) omits thepasswordandrandomSaltfields — it is safe to call from a front-end context
Validation Rules
- Update requires the user to exist by email — unknown emails return an error
- Delete requires the user to exist by email — unknown emails return "User Not Found"
Error Scenarios
| Scenario | Message |
|---|---|
| User not found (get/update/delete) | "User Not Found" / "Admin User Not exist this id" |
| Attempt to delete SUPER_ADMIN | "Super Admin Cannot be deleted..." |
| No users found for tenant | "Admin User Not found" / "Tenant User Not found" |
8. Security
Overview
The service implements a layered security model: all passwords are protected with salt + pepper + BCrypt hashing; JWT tokens carry the full authorization context; and tenant isolation is enforced at the database routing layer on every single request.
Security Architecture
flowchart TD
Request[Inbound Request] --> Filter[TenantFilter\nper-request filter]
Filter --> TenantCheck{xTenant Header\nPresent?}
TenantCheck -->|No| Reject401[Reject: 401 Unauthorized]
TenantCheck -->|Yes| Resolve[Resolve Tenant MongoDB URL]
Resolve --> EnabledCheck{Tenant Enabled?}
EnabledCheck -->|No| Reject401
EnabledCheck -->|Yes| SetContext[Set ThreadLocal DB Connection]
SetContext --> Handler[Route to Controller]
Handler --> Auth{Endpoint Requires Auth?}
Auth -->|Yes| JWT[Validate JWT via Spring Security]
JWT -->|Invalid| Reject401
JWT -->|Valid| Business[Execute Business Logic]
Auth -->|No| Business
Business --> ClearContext[Clear ThreadLocal on Response]
JWT
- Tokens are generated by
JwtUtilsfrom the sharedrentone-shared-utilslibrary - Each token embeds:
email,tenantId,userTypes,accessLevel(full ACG map),platform, and expiry - Expiry: 24 hours from issue
- The token is designed to be self-contained — downstream services can authorize requests without calling back to this service
Password Hashing
The three-layer hashing strategy:
| Layer | Value | Storage |
|---|---|---|
| Raw password | Supplied by user | Never stored |
| Salt | 128-bit cryptographically random (Base64) | Stored in randomSalt field |
| Pepper | Server environment variable PASSWORD_PEPPER |
Never stored in DB |
| Final hash | BCrypt(rawPassword + salt + pepper) |
Stored in password field |
Password resets re-use the existing salt. The salt is generated once at user creation and never rotated.
Tenant Isolation
- The
TenantFilterruns on every request (/*) - It reads the
xTenantheader (orxTenantquery param for SSE connections which cannot send custom headers) - It resolves and sets a ThreadLocal MongoDB connection string before the request reaches any controller
- The
DatabaseConfigurationclass overridesdoGetMongoDatabaseto read from this ThreadLocal at every MongoDB operation - After the response is sent, the ThreadLocal is cleared
- This guarantees that data from Tenant A can never be read or written in the context of Tenant B
Public Endpoints
The following endpoints are explicitly excluded from authentication requirements:
| Endpoint | Reason |
|---|---|
POST /rentone/tenants/create |
Bootstrap operation |
GET /rentone/tenants/get/tenant/mongo-url |
Called by TenantFilter itself |
POST /tenants/tenants-user/v1/create |
Initial admin setup |
POST /tenants/tenants-user/v1/auth/login |
Authentication entry point |
GET /tenants/tenants-user/get-names |
Used by other services |
PUT /tenants/tenants-user/update |
Profile management |
GET /rentone/tenants/get-all-tenants-with-db-url |
Cache warm-up |
PUT /tenants/tenants-user/reset-password |
Password recovery |
GET /tenants/tenants-user/get-all |
Admin listing |
GET /tenants/sse/connect |
SSE long-poll connection |
GET /tenants/tenants-user/get/profile |
Profile display |
SSE Real-Time Token Delivery
When a user's profile or ACG is updated, the service pushes a token-refresh event directly to the user's active SSE connection (tenantId:email keyed emitter). The browser receives the new JWT without requiring a page reload or re-login. SSE connections have a 30-minute timeout and a 30-second heartbeat.
9. MongoDB Collections
rentone_tenants (Shared Registry Database)
This collection lives in the platform-wide central database and is accessed directly by the Tenant Service without a tenant context header.
| Field | Type | Description |
|---|---|---|
tenantId |
String (PK) | Unique tenant identifier, always uppercase |
mongoURL |
String | MongoDB connection string template for this tenant |
creationDate |
Date | When the tenant was registered |
enabled |
Boolean | Whether the tenant is currently active |
Relationships: tenantId is the foreign key referenced by tenants_users.tenantId in every tenant-specific database.
tenants_users (Per-Tenant Database)
One instance of this collection exists per tenant, inside that tenant's dedicated MongoDB database.
| Field | Type | Description |
|---|---|---|
email |
String (PK) | User's email address; serves as the login username |
tenantId |
String | The tenant this user belongs to |
tenantUserName |
String | Display name of the user |
profileImage |
String | URL to the user's profile image |
password |
String | BCrypt-hashed password (includes salt + pepper) |
randomSalt |
String | Base64-encoded 128-bit random salt, unique per user |
userTypes |
Enum | User role (e.g. SUPER_ADMIN) |
accessLevel |
Map | UniqueAccessControlGroup → ACGAccessLevel permission map |
lastLogin |
Date | Timestamp of the most recent successful login |
createdDate |
Date | When the user account was created |
Relationships: tenantId references rentone_tenants.tenantId in the central registry.
db_sequence (Shared Registry Database)
An auto-increment counter store used by the SequenceGeneratorService.
| Field | Type | Description |
|---|---|---|
id |
String (PK) | Sequence name identifier |
seq |
Long | Current sequence value, atomically incremented |
Note: While the sequence generator is present in this service, it is not actively used in any current tenant or user creation flow — IDs are generated by email (users) or tenant name (tenants). It is available for future use.
10. External Dependencies
Dependency Diagram
flowchart LR
TenantSvc[Tenant Service]
CloudApp[Cloud App Service\nrentone-cloud-app]
Notifications[Notifications Service\nnotifications]
Eureka[Eureka Service Registry]
SharedUtils[rentone-shared-utils\nlocal library]
MongoDB[(MongoDB)]
TenantSvc -->|Feign: get default ACG| CloudApp
TenantSvc -->|Feign: report errors| Notifications
TenantSvc -->|Register + discover services| Eureka
TenantSvc -->|JwtUtils, validators, models| SharedUtils
TenantSvc -->|Read/Write tenant + user data| MongoDB
Cloud App Service
| Attribute | Detail |
|---|---|
| Service Name | rentone-cloud-app |
| Discovery | Eureka (load-balanced via Feign) |
| Hard-coded fallback | http://localhost:6004 (used in TenantsUserService) |
| Endpoint used | GET /cloud-app/system-info/get/default-access-level?userType={type} |
| Purpose | Returns the default UniqueAccessControlGroup → ACGAccessLevel permission map for a given user type |
| Why required | Without this, a user cannot be created — the ACG map is mandatory on every user record |
| Called at | User creation time only |
Notifications Service
| Attribute | Detail |
|---|---|
| Service Name | notifications |
| Discovery | Eureka (Feign client) |
| Endpoint used | POST /notifications/error-reporting/report |
| Purpose | Receives structured ErrorContext objects (service name, file, line, HTTP method, URI, error message, severity, timestamp) whenever an unhandled exception occurs |
| Why required | Centralised error alerting and monitoring for all RentOne microservices |
| Called at | Every unhandled exception caught by MainExceptionHandler |
Eureka Service Registry
| Attribute | Detail |
|---|---|
| Role | Service discovery for Feign clients (rentone-cloud-app, notifications) |
| Configuration | @EnableDiscoveryClient on the application class |
| Why required | Enables service-name-based routing without hardcoded URLs for inter-service calls |
rentone-shared-utils (Local Library)
| Attribute | Detail |
|---|---|
| Artifact | com.microservices.shriccointernational.rentone:rentone-shared-utils:0.0.1-SNAPSHOT |
| Contents used | JwtUtils (token generation/validation), ValidationUtils (email validation), MongoConnectionStorage (ThreadLocal DB context), AuthTokenStorage (ThreadLocal auth token), GlobalConstants (shared constant values), standard response models (StatusResponse, StringMapResponse), ErrorContext, SeverityResolver, EnvironmentResolver, ACG enums (UniqueAccessControlGroup, ACGAccessLevel, TenantUserTypes, LoginPlatforms) |
| Why required | Shared contract between all RentOne services; ensures consistent JWT structure, response shapes, and MongoDB routing behaviour |
11. Entity Reference
Tenant
Represents a registered business entity within the RentOne platform.
| Field | Type | Description |
|---|---|---|
tenantId |
String | Primary key. Unique name identifying the tenant (uppercase). |
mongoURL |
String | The MongoDB connection string assigned to this tenant. |
creationDate |
Date | Registration timestamp. |
enabled |
Boolean | Controls whether this tenant can process requests. |
Relationships: One tenant has many TenantsUser records stored in its dedicated database.
TenantsUser
Represents an administrator user within a specific tenant.
| Field | Type | Description |
|---|---|---|
email |
String | Primary key. Used as the login username. |
tenantId |
String | The tenant this user belongs to. |
tenantUserName |
String | Human-readable display name. |
profileImage |
String | URL to profile image. |
password |
String | BCrypt hash of (password + salt + pepper). Never returned in responses. |
randomSalt |
String | Base64 128-bit salt. Used during password hashing and verification. |
userTypes |
TenantUserTypes | Role of the user (e.g. SUPER_ADMIN). |
accessLevel |
Map | Permission map: UniqueAccessControlGroup → ACGAccessLevel. |
lastLogin |
Date | Timestamp of last successful authentication. |
createdDate |
Date | Account creation timestamp. |
Relationships: Belongs to one Tenant. Has one accessLevel map sourced from the ACG system.
TenantUserDTO (Request/Response Transfer Object)
Used for user creation and update requests, and as the login response payload.
| Field | Type | Notes |
|---|---|---|
email |
String | Identifies the user |
tenantId |
String | Tenant scope |
tenantUserName |
String | Display name |
password |
String | Provided at creation; always null in responses |
profileImage |
String | Optional |
userTypes |
TenantUserTypes | Role |
accessLevel |
Map | ACG permission map |
createdDate |
Date | Set on creation |
authToken |
String | Populated only in login/refresh responses; @Transient (not persisted) |
TenantUserProfileDTO (Response Only)
A safe, read-only projection of the user's profile — excludes password and randomSalt.
| Field | Type |
|---|---|
email |
String |
tenantId |
String |
tenantUserName |
String |
profileImage |
String |
userTypes |
TenantUserTypes |
accessLevel |
Map |
lastLogin |
Date |
UniqueAccessControlGroup (ACG Key)
Enum values defined in rentone-shared-utils. Each value represents a functional module or capability group within the platform (e.g. property management, billing, reporting). The exact enum values are defined in the shared library and are the source of truth for all RentOne services.
ACGAccessLevel (ACG Value)
Enum values defined in rentone-shared-utils. Represents the access tier granted for a given ACG (e.g. READ, WRITE, NONE, FULL). The exact values are defined in the shared library.
TenantUserTypes (Role)
Enum defined in rentone-shared-utils. At minimum includes SUPER_ADMIN. Additional roles are defined in the shared library and determine the default ACG map fetched from Cloud App.
LoginPlatforms
Enum defined in rentone-shared-utils. Indicates which client platform initiated the login. Defaults to ADMIN_WEB. Embedded in the JWT.
12. Cross-Cutting Concerns
Multi-Tenancy
Every request (except bootstrap endpoints) must carry an xTenant header identifying the tenant. The TenantFilter intercepts this header, resolves the tenant's MongoDB connection string (with in-memory caching for performance), and stores it in a ThreadLocal (MongoConnectionStorage). The DatabaseConfiguration class reads from this ThreadLocal at every MongoDB operation, dynamically switching the database on a per-request basis. After the response is sent, the ThreadLocal is cleared to prevent data leakage between requests on the same thread.
For SSE connections — where the browser's EventSource API cannot send custom headers — the xTenant value is read from the xTenant query parameter via the CustomRequestWrapper.
flowchart TD
Request[Request with xTenant header] --> Filter[TenantFilter]
Filter --> Cache{In-Memory Cache?}
Cache -->|Hit| TL[Set ThreadLocal Connection]
Cache -->|Miss| API[Resolve from Tenant Registry]
API --> TL
TL --> DB[Dynamic MongoDB Operation]
DB --> Clear[Clear ThreadLocal after response]
JWT Authentication
JWT tokens are generated and verified using JwtUtils from rentone-shared-utils. The token payload includes the user's full identity and permission context. Downstream services verify the token independently — they do not need to call this service for authorization. The TenantFilter stores the raw Authorization header in AuthTokenStorage (ThreadLocal), which is automatically forwarded on all outbound Feign calls via CustomFeignInterceptor.
MongoDB Connection Resolution
The service uses a custom DatabaseConfiguration that overrides Spring's SimpleMongoClientDatabaseFactory. On every MongoDB operation it reads the current MongoConnectionStorage ThreadLocal value to determine which database to connect to. This makes MongoDB routing transparent to all repositories and services — they simply use the injected MongoTemplate or MongoRepository as normal.
Exception Handling
All unhandled exceptions are caught by MainExceptionHandler (@ControllerAdvice). It:
- Extracts the first stack frame within the
com.microservices.shriccointernational.rentonepackage to identify the source file and line number - Constructs an
ErrorContextwith full request metadata (method, URI, handler class, handler method, error message, stack trace, severity, timestamp) - Sends the
ErrorContextto the Notifications Service via Feign - Returns a generic
500 Internal Server Errorwith{ "success": false, "message": "An error occurred" }to the client
SSE-specific exceptions (AsyncRequestNotUsableException, AsyncRequestTimeoutException) are handled silently with a debug log — they represent normal client disconnection behaviour.
Logging
The service uses SLF4J with Lombok @Slf4j. Logging is present in:
SseService: debug-level logs for SSE lifecycle events (connect, disconnect, timeout, heartbeat, token push)MainExceptionHandler:ex.printStackTrace()for unhandled exceptions (in addition to Notifications Service reporting)- No business-level info/warn logging is present in the current codebase
Validation Strategy
Validation is applied at the service layer before any persistence or external calls:
| Check | Location | Mechanism |
|---|---|---|
| Email format | User creation | ValidationUtils.isValidEmail from shared-utils |
| Duplicate user | User creation | repository.existsById |
| Required fields presence | User creation | Null checks on tenantId, password, tenantUserName, userTypes |
| Duplicate tenant | Tenant creation | repository.findById + empty check |
| User existence | Update, delete, reset, get | repository.findById + empty check |
| SUPER_ADMIN protection | Delete | userTypes == TenantUserTypes.SUPER_ADMIN guard |
There is no Bean Validation (@Valid / @NotNull) in the current implementation — all validation is imperative in the service layer.
Scheduling
The service uses @EnableScheduling. The only scheduled task is in SseService:
sendHeartbeat()— runs every 30 seconds (fixedDelay = 30000)- Sends a
heartbeat/pingevent to all active SSE emitters - Stale or disconnected emitters are detected and removed from the registry on send failure