Enterprise-grade, multi-tenant backend for the WorkNest platform. Designed for secure platform onboarding, tenant data isolation, HR and project operations, and real-time collaboration.
WorkNest Service is a Java 21 + Spring Boot 3.3.5 backend with strict separation between platform control-plane data and tenant business data.
The implementation provides:
- Master-plane tenant lifecycle and onboarding
- Stateless JWT authentication with rotating refresh tokens
- Tenant isolation enforced at HTTP filter, JWT claim, and Hibernate connection-provider layers
- HR, project execution, communication, analytics, and audit capabilities
- STOMP-over-WebSocket messaging for chat and notification fan-out
Production note: current repository defaults are development-oriented in several areas (secrets, ddl-auto, public onboarding, in-memory broker). See Production Warnings sections below before deployment.
| Layer | Technology | Current Implementation Notes |
|---|---|---|
| Runtime | Java 21 | Configured via Maven compiler and Spring Boot parent |
| Framework | Spring Boot 3.3.5 | REST, scheduling, async, actuator |
| Security | Spring Security 6 + JWT (JJWT) | Stateless bearer auth, role-based authorization, tenant claim checks |
| Data Access | Spring Data JPA + Hibernate 6 | Separate master and tenant persistence units |
| Multi-tenancy | Hibernate DATABASE multi-tenancy | Custom CurrentTenantIdentifierResolver + MultiTenantConnectionProvider |
| Database | MySQL 8.x | Master DB plus per-tenant DB credentials in platform metadata |
| Migrations | Hibernate ddl-auto | No Flyway dependency or migration scripts currently present |
| Real-time | Spring WebSocket + STOMP + SockJS | In-memory simple broker with heartbeat |
| API Docs | springdoc-openapi 2.6.0 | JWT bearer scheme in OpenAPI |
| Containerization | Docker + Docker Compose | Multi-stage image build |
| Data Domain | Persistence Unit | Physical Storage | Responsibilities |
|---|---|---|---|
| Master control plane | masterEntityManagerFactory | platform_master (configured in spring.datasource.*) |
Platform users, refresh tokens, tenant registry, onboarding metadata |
| Tenant business plane | entityManagerFactory (tenant) | Per-tenant MySQL databases (resolved from platform_tenants.db_url/db_username/db_password) |
Employees, teams, attendance, leaves, projects, tasks, chats, notifications, audit logs |
Tenant-scoped endpoints are all under /api/tenant/**.
TenantContextFilterruns first (@Order(1)) and enforcesX-Tenant-IDfor tenant endpoints.- Tenant key is normalized (
trim + lowercase) and validated against master tenant metadata. - Non-existent tenants return
TENANT_NOT_FOUND; inactive tenants returnTENANT_INACTIVE. - Valid tenant is stored in
TenantContext(ThreadLocal) and added to MDC for traceable logs. - Hibernate asks
CurrentTenantIdentifierResolverImplfor the tenant identifier. MultiTenantConnectionProviderImplobtains or creates a tenant datasource fromTenantDataSourceServiceImpl.TenantDataSourceServiceImplcaches Hikari pools per tenant with scheduled idle eviction and max-cache controls.TenantContextis cleared infinallyto avoid cross-request leakage.
Tenant isolation guarantees in this implementation:
- Header gate: tenant endpoints require
X-Tenant-ID. - Metadata gate: tenant must exist and be ACTIVE in master registry.
- Token gate: JWT
tenantKeymust match authenticated user tenant. - Request gate: for
/api/tenant/**,X-Tenant-IDmust match JWTtenantKey. - Connection gate: tenant persistence rejects master/default tenant identifiers for tenant-scoped DB access.
Startup sequence is implemented with ordered CommandLineRunner components:
MasterDatabaseStartupValidator(@Order(HIGHEST_PRECEDENCE)) validates master DB connectivity.StartupSecretsValidator(@Order(5)) enforces stronger checks whenprodprofile is active.BootstrapDataInitializer(@Order(10)) creates a bootstrapPLATFORM_ADMINwhen required.TenantSchemaInitializer(@Order(20)) ensures schema for each ACTIVE tenant.DemoTenantDataSeeder(@Order(30), conditional) seeds demo tenant data when enabled.TenantAdminMirrorRepairInitializer(@Order(40)) repairs tenant-admin employee mirrors.
Onboarding provisioning flow:
POST /api/platform/onboarding/tenantsaccepts registration and emitsTenantProvisioningRequestedEvent.- Async listener (
tenantProvisioningExecutor) creates DB if missing, applies tenant schema, mirrors tenant admin employee, then marks tenant ACTIVE. - Provisioning failure marks tenant SUSPENDED.
Production Warnings:
POST /api/platform/onboarding/tenantsis currently publicly accessible (permitAll) and must be protected by at least one of: API gateway auth, invitation token, mTLS, allowlist, and request throttling.spring.jpa.hibernate.ddl-autoandapp.tenant.jpa.hibernate.ddl-autodefault toupdate; this is not deterministic schema management for production.
| Role | Scope | Typical Access |
|---|---|---|
PLATFORM_ADMIN |
Master/platform | /api/platform/**, platform announcements, tenant governance |
TENANT_ADMIN |
Tenant | Tenant-wide administration and operational controls |
ADMIN |
Tenant | Tenant administration equivalent for business operations |
MANAGER |
Tenant | Team/project management workflows |
HR |
Tenant | HR workflows, leave/attendance governance, HR chat |
EMPLOYEE |
Tenant | Self-service employee, task, communication actions |
Configured public endpoints in security filter chain:
POST /api/auth/loginPOST /api/auth/refreshPOST /api/auth/forgot-passwordPOST /api/auth/reset-passwordPOST /api/platform/onboarding/tenants/ws/**(handshake path is public; STOMP CONNECT is token-validated by interceptor)/error/actuator/healthand/actuator/health/**whenapp.security.public-health-enabled=true/v3/api-docs/**,/swagger-ui/**,/swagger-ui.htmlwhenapp.security.swagger-public-enabled=true
Authenticated route controls:
/api/platform/**requiresROLE_PLATFORM_ADMIN/api/tenant/**requires any ofROLE_TENANT_ADMIN,ROLE_ADMIN,ROLE_MANAGER,ROLE_HR,ROLE_EMPLOYEE/api/auth/logout,/api/auth/me,/api/auth/change-password,/api/auth/admin/**require authentication
- Client calls
POST /api/auth/loginwith email, password, and tenant key for tenant-scoped users. - Server validates user status, tenant scope, and password.
- Existing active refresh tokens for that user are revoked.
- Access token is issued (JWT HS256) with claims:
sub: user emailuid: user idrole: platform roletenantKey: tenant key (null for platform admin)
- Refresh token is generated as random opaque value; only SHA-256 hash is persisted in DB.
- Client uses bearer access token for API calls.
- For refresh, client calls
POST /api/auth/refreshwith refresh token and tenant key (payload/header consistency enforced). - Refresh token is rotated atomically; old token revoked with
rotatedToTokenhash linkage. - On logout, refresh token is validated and revoked; authenticated principal must match token owner.
/wsendpoint uses SockJS and allowed origin patterns fromapp.websocket.allowed-origins.- STOMP
CONNECTmust include:Authorization: Bearer <access-token>X-Tenant-ID(tenant-scoped users)
- Interceptor validates:
- Token signature and expiry
- JWT tenant claim == principal tenant
- STOMP tenant header == token tenant
- Destination tenant path binding (
/topic/tenant/{tenantKey}/...,/app/tenant/{tenantKey}/...) - Chat membership for team/HR conversation destinations
Production Warnings:
- CSRF is disabled globally. This is acceptable for pure stateless token APIs, but do not introduce cookie-based auth without re-evaluating CSRF.
- No built-in rate limiting, brute-force controls, or IP throttling are implemented.
- Onboarding endpoint is public by default and represents high abuse risk.
- Review
swagger-public-enabledandpublic-health-enableddefaults before internet exposure.
| Module | Key Endpoints | Description |
|---|---|---|
| Authentication and Identity | /api/auth/login, /api/auth/refresh, /api/auth/logout, /api/auth/me, /api/auth/forgot-password, /api/auth/reset-password, /api/auth/change-password |
JWT auth, rotating refresh tokens, account password lifecycle |
| Platform Onboarding and Governance | /api/platform/onboarding/tenants, /api/platform/tenants/**, /api/platform/announcements/** |
Tenant registration/provisioning and platform governance |
| Employee and Organization (HR Core) | /api/tenant/employees/**, /api/tenant/teams/**, /api/tenant/attendance/**, /api/tenant/leaves/** |
Employee records, team structures, attendance, leave workflows |
| Project Delivery | /api/tenant/projects/**, /api/tenant/tasks/** |
Project planning, team assignment, task tracking, comments, kanban data |
| Communication | /api/tenant/announcements/**, /api/tenant/notifications/**, /api/tenant/chats/team/**, /api/tenant/chats/hr/**, /api/tenant/chats/read-receipts/** |
Tenant announcements, notifications, team and HR chat |
| Analytics and Insights | /api/tenant/dashboard/**, /api/tenant/analytics/** |
Operational dashboards and analytical summaries |
| Audit and Governance | /api/tenant/audit-logs/** |
Auditable user and domain activity views |
| Attachments and Settings | /api/tenant/attachments/**, /api/tenant/settings/**, /api/files/upload |
Attachment metadata/storage and tenant workspace configuration |
curl -X POST "http://localhost:8080/api/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "platform.admin@worknest.local",
"password": "ChangeMe123!",
"tenantKey": null
}'curl -X GET "http://localhost:8080/api/tenant/dashboard/me" \
-H "Authorization: Bearer <access-token>" \
-H "X-Tenant-ID: <tenant-key>"curl -X POST "http://localhost:8080/api/auth/refresh" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: <tenant-key>" \
-d '{
"refreshToken": "<refresh-token>",
"tenantKey": "<tenant-key>"
}'curl -X POST "http://localhost:8080/api/auth/logout" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: <tenant-key>" \
-d '{
"refreshToken": "<refresh-token>",
"tenantKey": "<tenant-key>"
}'src/main/java/com/worknest
auth/
controller/ # Auth endpoints
dto/ # Auth request/response payloads
service/ # Auth business services
common/
api/ # ApiResponse/ErrorResponse wrappers
enums/ # Shared enums (roles, status)
exception/ # GlobalExceptionHandler and custom exceptions
storage/ # Attachment/file abstractions
util/ # Shared constants/helpers
config/
SecurityConfig.java
CorsConfig.java
WebSocketConfig.java
MasterDataSourceConfig.java
MasterJpaConfig.java
TenantHibernateConfig.java
BootstrapDataInitializer.java
TenantSchemaInitializer.java
DemoTenantDataSeeder.java
StartupSecretsValidator.java
controller/ # Tenant REST controllers (HR, project, communication, analytics)
master/
controller/ # Platform admin/onboarding APIs
dto/
entity/ # PlatformTenant, PlatformUser, RefreshToken
event/ listener/ # Async tenant provisioning flow
repository/
service/
notification/
email/ # Email notification services
security/
authorization/
filter/ # JWT servlet filter + STOMP interceptor
handler/ # Authentication/authorization handlers
jwt/ # JWT service
model/ # Principal model
service/ util/
tenant/
connection/ # MultiTenantConnectionProvider
context/ # TenantContext + MasterTenantContextRunner
datasource/ # Tenant datasource caching and lifecycle
dto/
entity/
enums/
filter/ # TenantContextFilter
repository/
resolver/ # CurrentTenantIdentifierResolver
service/
src/main/resources
application.yml
docker-compose.yml
Dockerfile
pom.xml
- JDK 21
- Maven 3.9+
- MySQL 8+
- Docker and Docker Compose (optional)
- Create a MySQL database for master metadata (for example:
platform_master). - Update configuration values (recommended via environment overrides; see Environment Variables section).
- Start the service:
mvn spring-boot:runor
mvn clean package -DskipTests
java -jar target/worknest-service-0.0.1-SNAPSHOT.jarDefault application port is 8080.
- Default profile: no explicit profile required.
- Production profile:
SPRING_PROFILES_ACTIVE=prod. - In
prod,StartupSecretsValidatorenforces non-empty critical secrets and blocksspring.jpa.hibernate.ddl-auto=update.
docker compose up --buildContainer endpoints:
- Backend:
http://localhost:${BACKEND_PORT:-8080} - MySQL:
localhost:${MYSQL_PORT:-3306}
Production Warnings:
- Current
docker-compose.ymlexportsDB_URL,DB_USERNAME, andDB_PASSWORD, while Spring Boot datasource properties arespring.datasource.*. UseSPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAME, andSPRING_DATASOURCE_PASSWORD(or map values insideapplication.yml) to avoid startup mismatches. - Current repository
application.ymlcontains hardcoded credentials and a JWT secret intended for local development only.
Demo seeding is implemented by DemoTenantDataSeeder and only runs when enabled.
Required configuration:
bootstrap.seed-demo-data=truebootstrap.demo-user-password=<non-empty-password>
Behavior:
- Runs after tenant schema initialization.
- Seeds only ACTIVE tenants.
- Skips a tenant if employees already exist.
- Creates demo employees, teams, projects, tasks, announcements, notifications, and chat conversations.
- Creates/links corresponding platform users for seeded tenant employees.
- Swagger UI:
http://localhost:8080/swagger-ui.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs
Note: public exposure is controlled by app.security.swagger-public-enabled.
- Handshake endpoint:
/ws(SockJS enabled) - Broker prefixes:
- Application destinations:
/app - Topic/queue broker destinations:
/topic,/queue - User destination prefix:
/user
- Application destinations:
- Tenant destination convention:
/topic/tenant/{tenantKey}/...and/app/tenant/{tenantKey}/... - Inbound/outbound channels are backed by task executors; simple broker heartbeat is
10s/10s.
Production Warnings:
- The current broker is Spring in-memory simple broker, suitable for single-instance deployments.
- For horizontal scale and durable messaging, replace with an external broker relay (for example RabbitMQ STOMP relay) and externalize session routing/state as needed.
This project currently keeps many defaults directly in application.yml. For production, externalize all secrets and operational settings.
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| Active profile | spring.profiles.active |
SPRING_PROFILES_ACTIVE |
| Server port | server.port |
SERVER_PORT |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| Master JDBC URL | spring.datasource.url |
SPRING_DATASOURCE_URL |
| Master DB username | spring.datasource.username |
SPRING_DATASOURCE_USERNAME |
| Master DB password | spring.datasource.password |
SPRING_DATASOURCE_PASSWORD |
| Driver class | spring.datasource.driver-class-name |
SPRING_DATASOURCE_DRIVER_CLASS_NAME |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| JWT signing secret (Base64) | app.jwt.secret |
APP_JWT_SECRET |
| Access token TTL (ms) | app.jwt.expiration |
APP_JWT_EXPIRATION |
| Refresh token TTL (ms) | app.jwt.refresh-expiration |
APP_JWT_REFRESH_EXPIRATION |
| Tenant header name | app.tenant.header |
APP_TENANT_HEADER |
| Default tenant alias | app.tenant.default |
APP_TENANT_DEFAULT |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| REST allowed origins | app.cors.allowed-origins |
APP_CORS_ALLOWED_ORIGINS |
| WS allowed origins | app.websocket.allowed-origins |
APP_WEBSOCKET_ALLOWED_ORIGINS |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| Public health endpoint | app.security.public-health-enabled |
APP_SECURITY_PUBLIC_HEALTH_ENABLED |
| Public Swagger endpoints | app.security.swagger-public-enabled |
APP_SECURITY_SWAGGER_PUBLIC_ENABLED |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| SMTP host | spring.mail.host |
SPRING_MAIL_HOST |
| SMTP port | spring.mail.port |
SPRING_MAIL_PORT |
| SMTP username | spring.mail.username |
SPRING_MAIL_USERNAME |
| SMTP password | spring.mail.password |
SPRING_MAIL_PASSWORD |
| Sender email | app.email.from |
APP_EMAIL_FROM |
| Reset token expiry minutes | app.auth.password-reset.token-expiry-minutes |
APP_AUTH_PASSWORD_RESET_TOKEN_EXPIRY_MINUTES |
| Reset link base URL | app.auth.password-reset.link-base-url |
APP_AUTH_PASSWORD_RESET_LINK_BASE_URL |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| Enable bootstrap platform admin | bootstrap.platform-admin.enabled |
BOOTSTRAP_PLATFORM_ADMIN_ENABLED |
| Bootstrap admin name | bootstrap.platform-admin.name |
BOOTSTRAP_PLATFORM_ADMIN_NAME |
| Bootstrap admin email | bootstrap.platform-admin.email |
BOOTSTRAP_PLATFORM_ADMIN_EMAIL |
| Bootstrap admin password | bootstrap.platform-admin.password |
BOOTSTRAP_PLATFORM_ADMIN_PASSWORD |
| Enable demo seeding | bootstrap.seed-demo-data |
BOOTSTRAP_SEED_DEMO_DATA |
| Demo user password | bootstrap.demo-user-password |
BOOTSTRAP_DEMO_USER_PASSWORD |
| Purpose | Spring Property | Recommended Environment Variable |
|---|---|---|
| Frontend upload directory | app.storage.frontend-public-uploads-dir |
APP_STORAGE_FRONTEND_PUBLIC_UPLOADS_DIR |
| Max upload size (bytes) | app.storage.max-file-size-bytes |
APP_STORAGE_MAX_FILE_SIZE_BYTES |
| Allowed MIME types | app.storage.allowed-mime-types |
APP_STORAGE_ALLOWED_MIME_TYPES |
Production Warnings:
- Manage secrets through a centralized secret manager (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, or HashiCorp Vault).
- Never commit real credentials, JWT keys, or SMTP app passwords.
- Rotate all secrets that were previously stored in repository history.
Error response standardization note:
- Most exceptions use
ErrorResponseviaGlobalExceptionHandler, and success responses useApiResponse. - JWT and tenant filters also write error payloads directly. Keep their error-code taxonomy aligned with global handler contracts to preserve client-side consistency.
- Phase 1: Multi-tenant infrastructure (master/tenant separation, resolver/provider/context/filter, onboarding provisioning).
- Phase 2: Authentication and authorization (JWT access tokens, refresh token rotation/revocation, tenant-bound auth checks).
- Phase 3: HR and delivery core (employees, teams, attendance, leaves, projects, tasks).
- Phase 4: Communication and governance (announcements, notifications, team/HR chats, read receipts, audit logs, attachments).
- Phase 5: Operational hardening and delivery readiness (dashboard/analytics APIs, actuator integration, OpenAPI, Dockerization, async provisioning/seeding workflows).
Current production hardening gaps to address before go-live:
- Protect or gate public onboarding endpoint.
- Replace
ddl-autowith versioned schema migrations. - Externalize all secrets and rotate exposed credentials.
- Add rate limiting and abuse controls.
- Move from in-memory STOMP broker to external broker relay for multi-instance scale.