fix(webauthn): apply PR #256 review fixes#258
Conversation
Apply all 14 review fixes that were missing from the squash merge: - Safe-parse AuthenticatorTransport enums, default label, trim-before-length - Fix TOCTOU race in last-credential protection with in-transaction recount - Add ON DELETE CASCADE to WebAuthn foreign keys in schema - Add WebAuthnPreDeleteEventListener for cleanup on user deletion - Add WebAuthnAuthenticationToken for passkey session distinction - Change transports to List<String> in credential response DTO - Add @notblank on path variable parameters in management API - Add @ConditionalOnProperty to WebAuthnManagementAPIAdvice - Change WebAuthnException to extend RuntimeException - Use SecureRandom for user handle generation - Use @transactional(propagation = MANDATORY) on credential operations - Update all tests for new behavior
PR #258 ReviewOverall this is a solid set of fixes. The changes are well-scoped, the new files follow the project's conventions, and the test additions cover the key new behaviors. A few issues worth addressing below. Issues1. In 2. N+1 delete in credentialRepository.findByUserEntity(userEntity).forEach(credentialRepository::delete);This issues one DELETE per credential. Prefer 3. Label trimmed for validation but not for storage (data integrity)
4. Redundant Seven test methods now read: void shouldRenameSuccessfully() /* no checked exception */ {The comment is noise — the absence of a 5. PostgreSQL schema not updated Only Minor observations
TOCTOU retry in What is working well
|
There was a problem hiding this comment.
Pull request overview
Applies the missing post-review fixes from PR #256 to harden and refine the WebAuthn/passkey feature (safety, data integrity, API quality, and security design adjustments) in the Spring User Framework.
Changes:
- Convert
WebAuthnExceptionto an unchecked exception and update impacted services/controllers/tests accordingly. - Improve WebAuthn persistence and API ergonomics (default label, safer enum parsing, transports as
List<String>, validation on path vars, conditional advice). - Add cleanup behavior and schema integrity improvements (pre-delete listener + test,
ON DELETE CASCADE, TOCTOU locking).
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/digitalsanctuary/spring/user/service/WebAuthnCredentialManagementService.java | Removes checked exception usage; strengthens label validation and deletion locking logic. |
| src/main/java/com/digitalsanctuary/spring/user/persistence/repository/WebAuthnUserEntityBridge.java | Adds transactional lookup and attempts to handle concurrent entity creation; switches user handle to random bytes. |
| src/main/java/com/digitalsanctuary/spring/user/persistence/repository/WebAuthnCredentialQueryRepository.java | Adds mandatory-transaction locking and returns transports as a parsed list. |
| src/main/java/com/digitalsanctuary/spring/user/security/WebAuthnRepositoryConfig.java | Defaults missing labels and safely parses enums/transports with logging. |
| src/main/java/com/digitalsanctuary/spring/user/security/WebAuthnAuthenticationToken.java | Introduces a dedicated token type for WebAuthn-authenticated sessions. |
| src/main/java/com/digitalsanctuary/spring/user/security/WebAuthnAuthenticationSuccessHandler.java | Switches converted authentication to the new WebAuthn token. |
| src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPI.java | Adds @NotBlank path var validation and removes checked exception declaration. |
| src/main/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPIAdvice.java | Makes advice conditional on WebAuthn being enabled. |
| src/main/java/com/digitalsanctuary/spring/user/dto/WebAuthnCredentialInfo.java | Changes transports field from String to List<String>. |
| src/main/java/com/digitalsanctuary/spring/user/listener/WebAuthnPreDeleteEventListener.java | Adds a pre-delete listener to clean up WebAuthn data. |
| src/main/java/com/digitalsanctuary/spring/user/exceptions/WebAuthnException.java | Changes exception type to RuntimeException and updates docs accordingly. |
| src/test/java/com/digitalsanctuary/spring/user/service/WebAuthnCredentialManagementServiceTest.java | Updates tests to match unchecked exception behavior. |
| src/test/java/com/digitalsanctuary/spring/user/api/WebAuthnManagementAPITest.java | Updates tests to match unchecked exception behavior. |
| src/test/java/com/digitalsanctuary/spring/user/security/WebAuthnAuthenticationSuccessHandlerTest.java | Updates expected authentication token type to WebAuthnAuthenticationToken. |
| src/test/java/com/digitalsanctuary/spring/user/persistence/repository/WebAuthnUserEntityBridgeTest.java | Adds coverage for concurrent-creation retry path and random handle sizing. |
| src/test/java/com/digitalsanctuary/spring/user/listener/WebAuthnPreDeleteEventListenerTest.java | Adds tests for user pre-delete cleanup behavior. |
| db-scripts/mariadb-schema.sql | Adds ON DELETE CASCADE to WebAuthn-related foreign keys. |
Comments suppressed due to low confidence (1)
src/main/java/com/digitalsanctuary/spring/user/listener/WebAuthnPreDeleteEventListener.java:46
- Now that the schema adds
ON DELETE CASCADEfromuser_credentials→user_entities, deleting credentials one-by-one before deleting the user entity is redundant and adds extra queries. If the project intends to rely on the DB cascade for cleanup, this method can delete only theWebAuthnUserEntity(or use a bulk delete) to reduce round-trips; otherwise, consider documenting why manual deletes are still needed.
private void deleteUserEntityAndCredentials(WebAuthnUserEntity userEntity) {
credentialRepository.findByUserEntity(userEntity).forEach(credentialRepository::delete);
userEntityRepository.delete(userEntity);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private void validateLabel(String label) { | ||
| if (label == null || label.trim().isEmpty()) { | ||
| throw new WebAuthnException("Credential label cannot be empty"); | ||
| } | ||
| if (label.length() > 64) { | ||
| if (label.trim().length() > 64) { | ||
| throw new WebAuthnException("Credential label too long (max 64 characters)"); | ||
| } |
There was a problem hiding this comment.
validateLabel checks label.trim().length() but renameCredential persists the original newLabel unchanged. This allows inputs like 64 chars plus trailing spaces to pass validation but exceed the DB column length (VARCHAR(64)), causing truncation or a persistence error. Consider normalizing once (e.g., trim) and validating/persisting the normalized value so the stored label can never exceed 64 characters.
| // Create WebAuthn user entity for this application user, handling concurrent creation | ||
| try { | ||
| return createUserEntity(user); | ||
| } catch (DataIntegrityViolationException e) { | ||
| log.debug("Concurrent WebAuthn user entity creation for {}, retrying lookup", username); | ||
| return webAuthnUserEntityRepository.findByName(username).map(this::toSpringSecurityEntity).orElse(null); | ||
| } |
There was a problem hiding this comment.
The concurrent-creation handling is unlikely to work reliably with JPA: webAuthnUserEntityRepository.save(entity) typically does not flush immediately, so a unique-constraint conflict may surface at transaction commit rather than inside this try/catch, bypassing the retry path. To make the retry deterministic, force the insert to flush within the try block (e.g., via saveAndFlush/flush) and consider running the retry lookup in a clean/new persistence context/transaction after a constraint violation.
- Make SecureRandom a static final field in WebAuthnUserEntityBridge instead of creating a new instance per call - Replace N+1 credential deletion with bulk deleteByUserEntity() in WebAuthnPreDeleteEventListener - Trim credential label before storage in renameCredential() - Extract shared WebAuthnTransportUtils utility from duplicate private parsing methods in WebAuthnCredentialQueryRepository and WebAuthnRepositoryConfig - Add MANDATORY propagation note to lockAndCountCredentials Javadoc - Remove redundant /* no checked exception */ comments from test methods
fix(webauthn): address PR #258 review feedback
Summary
PR #256 was squash-merged before the review fixes were committed. This PR applies all 14 review fixes that were missing:
WebAuthnPreDeleteEventListenerfor cleanup on user deletionList<String>,@NotBlankon path vars,@ConditionalOnPropertyon adviceWebAuthnException→RuntimeException,WebAuthnAuthenticationToken,SecureRandomuser handle,@Transactional(MANDATORY)New files:
WebAuthnPreDeleteEventListener+ testWebAuthnAuthenticationTokenTest plan
./gradlew testpasses