Security
Security Model
Security is implemented via Spring Security in SecurityConfig (src/main/java/com/friendly/ftconfigsservice/utils/config/SecurityConfig.java).
Key properties:
-
Stateless sessions (
SessionCreationPolicy.STATELESS) -
CSRF enabled with cookie-based repository (
CookieCsrfTokenRepository.withHttpOnlyFalse()) -
Authentication: JWT access token read from HTTP-only cookie with
Authorization: Bearer …fallback (JwtAuthenticationFilter)
Roles and Authorities
The service uses three roles (Role enum in src/main/java/com/friendly/ftconfigsservice/auth/model/Role.java):
-
VIEWER→configs.read -
EDITOR→configs.read,configs.write -
ADMIN→configs.read,configs.write,users.manage
Authorization is enforced using authorities (not only ROLE_*).
Password validation rules
All password fields enforce the following constraints:
-
Length: 8—64 characters
-
Complexity: must contain at least one lowercase letter, one uppercase letter, one digit, and one of
@$!%*?&
The 64-character upper limit is enforced on all password inputs to prevent BCrypt denial-of-service (BCrypt processes at most 72 bytes; excessively long inputs waste CPU time before being truncated).
Specific endpoint constraints:
| DTO | Field | Validation |
|---|---|---|
|
|
|
|
|
|
|
|
|
User secret delivery
In offline mail mode (see Configuration — Delivery modes), admin user-management endpoints return secrets directly in the API response instead of emailing them:
-
Create user (
POST /api/admin/users) and reset password (POST /api/admin/users/{id}/reset-password) return the temporary password in the response body withpasswordDelivery=RETURNED. -
User deletion request (
POST /api/admin/users/{id}/deletion/request) issues no OTP/token and returnsotpRequired=false; the account is deleted in the/deletion/confirmstep, which is called without a token/OTP (gated by OTP delivery being unavailable).
This is also the runtime fallback under AUTO when SMTP is configured but the server is unreachable: create/reset degrade to passwordDelivery=RETURNED, and user deletion degrades to the no-OTP path.
To make the tokenless confirm safe, it is gated by MailDeliveryStatus.isOtpConfirmationAvailable(), which is false when configured offline or when a live SMTP connectivity probe fails — so a tokenless confirm is rejected (HTTP 400, "Deletion token is required") whenever OTP delivery is actually working, and accepted only when it is not.
Returning a temporary password (or deleting without OTP) is acceptable because:
-
the endpoints are admin-only (
@PreAuthorize("hasAuthority('users.manage')")); -
traffic is served over HTTPS;
-
the temporary password forces a password change on next login (
passwordChangeRequired=true); and -
the temporary password is never written to logs.
In online mode the deletion OTP is emailed to the initiating admin, so that admin must have an email address; otherwise the request fails with error code USER_DELETION_EMAIL_REQUIRED.
This email check is skipped in offline mode (configured or degraded at runtime).
|
API contract (breaking for the frontend)
Offline mode and the runtime fallback changed the response bodies of the admin user-management endpoints. HTTP status codes are unchanged; only the bodies changed.
| Endpoint | Response body |
|---|---|
|
|
|
|
|
|
|
Body |
The deletion flow is implemented as a router (UserDeletionService) plus an audited bean (UserDeletionOperations).
The deletion always happens in the confirm step.
Audit operation types: an online request emits CREATE / context UserDeletionIntent, while every actual deletion — both the offline no-OTP confirm and the online OTP-confirmed delete — emits DELETE / context UserDeletion.
Dead code removed
The following registration-related code has been removed as dead code:
-
RegisterRequestDTO -
RegisterResponseDTO -
AuthService.register()method -
/auth/registerendpoint removed fromSecurityConfigpublic path list
Registration is now handled exclusively through the admin user-creation flow in UserAdminService.
Endpoint Access Rules (high level)
Configured in SecurityConfig:
-
Public:
-
GET /csrfandGET /configs-service/csrf -
GET /environment/snapshot -
POST /auth/login,POST /auth/refresh-token,POST /auth/first-login -
/actuator/,/v3/api-docs/,/swagger-ui/**
-
-
Admin-only:
-
/api/admin/**,/auth/users/rolerequireusers.manage
-
-
Configuration domains:
-
GETon domain paths requiresconfigs.read -
POST/PUT/PATCH/DELETEon domain paths requiresconfigs.write
-
JWT Details
JwtService (src/main/java/com/friendly/ftconfigsservice/utils/security/JwtService.java) signs tokens using an HMAC key decoded from Base64:
-
jwt.secretis decoded viaio.jsonwebtoken.io.Decoders.BASE64. -
Access token lifetime:
jwt.expiration(ms). -
Refresh token lifetime:
jwt.refresh-token.expiration(ms).
Tokens are issued on login and stored in cookies by CookieUtil (ACCESS_TOKEN, REFRESH_TOKEN).
Bootstrap Admin
AdminBootstrap provisions the first admin user on startup when:
-
no existing
ADMINrole user exists, and -
ft-configs.bootstrap.admin.usernameandft-configs.bootstrap.admin.passwordare set.
The bootstrap account is created with passwordChangeRequired=true; the rotation flow is handled by POST /auth/first-login (AuthService.completeFirstLogin).
Security Boundary: Hazelcast Consumers
Runtime consumers that read Hazelcast directly (ACS, northbound-api, provisionportal, serviceapi, angular backend) bypass REST auth entication and authorization completely.
Operational implication:
-
Treat Hazelcast network access as production-critical security boundary (network policies, firewall rules, cluster auth/TLS if configured).