Security model
HridaOne holds a company's entire payroll and personnel record. The controls that matter are tenant isolation, what a stolen session can and can't do, and an auditable trail of access to sensitive data.
Tenant isolation
Every record carries a tenant_code. Every query is scoped to the caller's tenant. Groups are tenant-scoped so a permission grant can never cross a company boundary, and the LDAP OU is resolved through a stored, migration-safe name rather than a mutable code.
Authentication
- JWT signed with
JWT_SECRET(64+ chars). The client may readexpto pre-empt expiry, but the server validates every request. - Login OTP as an optional second step on native sign-in.
- Keycloak SSO with an
azpallow-list (KEYCLOAK_ALLOWED_CLIENT_IDS) — a token from an untrusted client is rejected even if the realm is right. - Password reset is email-round-trip only; a reset never reveals whether an address exists.
The security keyword
A per-company secret, AES-encrypted at rest with a key (SECURITY_KEYWORD_ENC_KEY) distinct from the JWT and Global Admin secrets. It is never stored or returned in plain text. Revealing or changing it requires an OTP to the Super Admin, so it's a genuine second factor for sensitive operations rather than another thing a session token unlocks.
Global Admin cannot read a tenant silently
Platform staff get into a company's operational data only through a company access request the Super Admin approves. The grant is scoped and logged; a denied or un-approved request is an explicit access-denied state.
Encryption of sensitive fields
CryptoService encrypts the fields that would be damaging in a database dump — the security keyword, and other secret-bearing configuration — rather than relying on database-at-rest encryption alone.
Audit trail
The audit log records sign-ins, permission changes, access grants and sensitive-field reveals, append-only and visible to the Global Admin. The per-tenant activity log gives a company its own record of who did what.
Maintenance mode
A platform-wide or per-tenant switch that shows a maintenance page and holds writes during a migration or incident — served from the API so every client honours it on the next request.
Operational guidance
- Run behind a TLS-terminating reverse proxy; the container binds
127.0.0.1by default. SPRING_JPA_HIBERNATE_DDL_AUTO=validatein production — neverupdateon a live database.- Generate every secret fresh per environment (
openssl rand); never copy one from.env.exampleor another deployment. - Keep
CORS_ALLOWED_ORIGINStight. - Set
KEYCLOAK_SSO_COOKIE_SECURE=trueanywhere that isn't local HTTP.