|
| 1 | +## [4.3.0] - 2026-03-12 |
| 2 | +### Features |
| 3 | +- RegistrationGuard SPI to gate all registration paths |
| 4 | + - Introduces a single pre-registration hook invoked before account creation across: |
| 5 | + - Form registration |
| 6 | + - Passwordless registration |
| 7 | + - OAuth2 social login auto-registration |
| 8 | + - OIDC (e.g., Keycloak) auto-registration |
| 9 | + - Core types: |
| 10 | + - RegistrationGuard (functional interface): evaluate(RegistrationContext) -> RegistrationDecision |
| 11 | + - RegistrationContext (record): email (nullable), source (FORM/PASSWORDLESS/OAUTH2/OIDC, non-null), providerName (nullable) |
| 12 | + - RegistrationDecision (record): allowed + reason, with factories allow() and deny(String) |
| 13 | + - RegistrationSource enum |
| 14 | + - DefaultRegistrationGuard (permit-all fallback) |
| 15 | + - Auto-configuration with bean-presence activation (@ConditionalOnMissingBean). If no custom guard is defined, a default permit-all guard is registered; startup logs at INFO: “No custom RegistrationGuard bean found — using DefaultRegistrationGuard (permit-all)”. |
| 16 | + - Framework integration points: |
| 17 | + - UserAPI: Guard is evaluated before form/passwordless registration. Denials return HTTP 403 with JSON error code 6 and the guard’s reason; denials logged at INFO. |
| 18 | + - DSOAuth2UserService and DSOidcUserService: Guard is evaluated before creating a new user via OAuth2/OIDC. Denials throw OAuth2AuthenticationException with error code "registration_denied" and the denial reason in the OAuth2Error description; denials logged at INFO. |
| 19 | + - Documentation: New REGISTRATION-GUARD.md with overview, usage examples, denial behavior (including code 6), constraints, and troubleshooting. |
| 20 | + |
| 21 | +### Fixes |
| 22 | +- OIDC parity and robustness with OAuth2 |
| 23 | + - Email normalization: |
| 24 | + - Trim + toLowerCase(Locale.ROOT) applied to both extraction and lookup in OIDC flow to avoid case/whitespace issues. |
| 25 | + - Transactional boundaries: |
| 26 | + - DSOidcUserService now annotated @Transactional at class level to ensure DB operations are atomic (e.g., user save + role assignment). |
| 27 | + - Registration auditing: |
| 28 | + - Publish “OIDC Registration Success” AuditEvent after user save; same change mirrored for OAuth2 (“OAuth2 Registration Success”) to avoid audit records on failed saves. |
| 29 | + - Post-login updates and immutable tokens: |
| 30 | + - DSOidcUserService now uses LoginHelperService.userLoginHelper(User, OidcUserInfo, OidcIdToken) to: |
| 31 | + - Update lastActivityDate, process unlock checks, compute authorities. |
| 32 | + - Return a DSUserDetails with OIDC tokens set via constructor (preserving immutability; no setters). |
| 33 | + - Error detail for UI handlers: |
| 34 | + - On guard denial, both OAuth2 and OIDC now include the denial reason in OAuth2Error’s description, enabling AuthenticationFailureHandlers to retrieve it via getError().getDescription(). |
| 35 | + - Consistency and cleanup: |
| 36 | + - Extracted USER_ROLE_NAME ("ROLE_USER") in services. |
| 37 | + - Fixed stale log text: “OIDC provider”. |
| 38 | +- RegistrationGuard SPI polish |
| 39 | + - RegistrationContext enforces non-null source (NPE with message “source must not be null”); email/providerName can be null. |
| 40 | + - RegistrationDecision.deny(String) defaults to “Registration denied.” when null/blank (uses isBlank and a shared constant). |
| 41 | + - RegistrationGuard marked @FunctionalInterface. |
| 42 | + - UserAPI replaces magic number with ERROR_CODE_REGISTRATION_DENIED constant (6). |
| 43 | + - INFO-level denial logs added across form/passwordless/OAuth2/OIDC paths. |
| 44 | + |
| 45 | +### Breaking Changes |
| 46 | +- None. The default permit-all guard preserves existing behavior unless a custom RegistrationGuard bean is provided. New denial flows only apply when a custom guard denies registration. |
| 47 | + |
| 48 | +### Refactoring |
| 49 | +- Extracted constants: |
| 50 | + - UserAPI: ERROR_CODE_REGISTRATION_DENIED = 6. |
| 51 | + - Services: USER_ROLE_NAME = "ROLE_USER". |
| 52 | +- Reordered imports per project guidelines; minor log message corrections. |
| 53 | +- Moved audit publish to after save() in both OAuth2 and OIDC registration paths. |
| 54 | + |
| 55 | +### Documentation |
| 56 | +- Added REGISTRATION-GUARD.md and linked it from CLAUDE.md. |
| 57 | +- README updated to reference version 4.3.0 in dependency snippets. |
| 58 | +- REGISTRATION-GUARD.md troubleshooting enhanced: |
| 59 | + - Notes INFO startup log when default guard is active. |
| 60 | + - Documents JSON error code 6 for registration denials. |
| 61 | + - Advises on reading denial reason from OAuth2Error description and customizing failure handlers. |
| 62 | + |
| 63 | +### Testing |
| 64 | +- New and updated tests to cover the SPI and OIDC/OAuth2 flows: |
| 65 | + - UserAPIRegistrationGuardTest: form/passwordless denials and success paths (includes code 6 assertions). |
| 66 | + - DSOAuth2UserServiceRegistrationGuardTest and DSOidcUserServiceRegistrationGuardTest: guard denial handling (exception type/code). |
| 67 | + - DefaultRegistrationGuardTest: permit-all baseline. |
| 68 | + - RegistrationDecisionTest: default denial reason when null/blank; allow/deny factories. |
| 69 | + - RegistrationContextTest: null source validation and field handling. |
| 70 | + - DSOidcUserServiceTest: |
| 71 | + - Uses mocked LoginHelperService and ApplicationEventPublisher. |
| 72 | + - Asserts authorities present, audit event published after save, and tokens preserved via new constructor path. |
| 73 | + |
| 74 | +### Other Changes |
| 75 | +- Gradle: version bumped to 4.2.3-SNAPSHOT (release plugin). |
| 76 | +- Merge commit for RegistrationGuard PR integration. |
| 77 | + |
1 | 78 | ## [4.2.2] - 2026-03-11 |
2 | 79 | ### Features |
3 | 80 | - Opt-in Multi-Factor Authentication (MFA) via simple user.mfa.* properties |
|
0 commit comments