Skip to content

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

  1. Service Overview
  2. Tenant Onboarding Flow
  3. Tenant Administrator Creation
  4. Tenant Authentication
  5. Access Control Group (ACG)
  6. Tenant Management
  7. Tenant User Management
  8. Security
  9. MongoDB Collections
  10. External Dependencies
  11. Entity Reference
  12. 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:

  1. Confirm that a tenant exists and is enabled
  2. Obtain the correct MongoDB connection string for that tenant
  3. Validate the JWT token in the Authorization header (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_URI environment variable); it can be updated separately
  • The enabled flag controls whether the tenant can be used by the routing filter — a disabled tenant will cause all downstream requests to be rejected with 401 Unauthorized
  • No authentication is required to create a tenant (this endpoint is a bootstrap/admin operation)

Validation Rules

  • tenantId must not already exist in the rentone_tenants collection
  • tenantId is 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 email address is the unique identifier (primary key) for every tenant user
  • The userTypes field determines the default permission map fetched from Cloud App
  • Passwords are never stored in plaintext — they go through salt + pepper + BCrypt hashing
  • The accessLevel map is not accepted from the client at creation time; it is always sourced from Cloud App based on userTypes
  • A SUPER_ADMIN user cannot be deleted once created

Validation Rules

  • email must pass RFC-compliant email format validation (ValidationUtils.isValidEmail)
  • tenantId, password, tenantUserName, and userTypes are 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_WEB if not supplied in the request
  • JWT expiry is fixed at 24 hours from issue time
  • password is 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_WEB platform 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-refresh event 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_ADMIN user 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 accessLevel map 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_CONNECTIONS map) of resolved tenant connections — the lookup API is only called on the first request per tenant per service instance
  • If a tenant's enabled flag is false, the Mongo URL endpoint returns success: false, causing the filter to reject the request with 401
  • The mongoURL field 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-url endpoint 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 xTenant request header — cross-tenant data access is structurally impossible
  • Pagination is 1-indexed: page=1 returns the first page
  • When page and size are omitted from the list endpoint, all users are returned without pagination
  • Deleting a SUPER_ADMIN is 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 the password and randomSalt fields — 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 JwtUtils from the shared rentone-shared-utils library
  • 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 TenantFilter runs on every request (/*)
  • It reads the xTenant header (or xTenant query 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 DatabaseConfiguration class overrides doGetMongoDatabase to 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 UniqueAccessControlGroupACGAccessLevel 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:

  1. Extracts the first stack frame within the com.microservices.shriccointernational.rentone package to identify the source file and line number
  2. Constructs an ErrorContext with full request metadata (method, URI, handler class, handler method, error message, stack trace, severity, timestamp)
  3. Sends the ErrorContext to the Notifications Service via Feign
  4. Returns a generic 500 Internal Server Error with { "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 / ping event to all active SSE emitters
  • Stale or disconnected emitters are detected and removed from the registry on send failure