Feature/password reset - #202
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
docker-compose.yml (1)
143-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePin the Mailpit image tag.
axllent/mailpit:latestis unpinned, so rebuilds can silently pick up a different image. Low risk since this is dev-only, but worth pinning for reproducibility.Pin to a specific tag
- image: axllent/mailpit:latest + image: axllent/mailpit:v1.20.5🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docker-compose.yml` around lines 143 - 150, The Mailpit service image is using an unpinned tag, so update the mailpit service definition to use a specific axllent/mailpit version instead of latest. Make the change in the mailpit block so the docker-compose setup remains reproducible across rebuilds and references the same service name and image field.backend/user-service/src/test/java/com/verita/userservice/service/PasswordResetServiceTests.java (1)
179-198: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing coverage for a null/blank
tokeninresetPassword.Given the null-token matching risk flagged in
PasswordResetService.resetPassword(Spring Data JPA's derived-query null →IS NULLsemantics), add a regression test assertingresetPassword(resetRequest(null, ...))throwsInvalidPasswordResetExceptiononce the guard is added.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/user-service/src/test/java/com/verita/userservice/service/PasswordResetServiceTests.java` around lines 179 - 198, Add regression coverage for the null/blank token case in PasswordResetServiceTests by extending the resetPassword scenarios around service.resetPassword and resetRequest. The issue is that a null token can fall through to tokenRepository.findByResetToken with Spring Data JPA null-to-IS NULL behavior, so add a test asserting resetPassword(resetRequest(null, ...)) throws InvalidPasswordResetException once the input guard is in place. Keep the existing unknown-token and expired-token tests unchanged, and mirror their assertion/verification style to confirm userRepository.save is not called.backend/user-service/src/main/resources/application.properties (1)
32-44: 🧹 Nitpick | 🔵 TrivialLGTM! Env-driven SMTP config with sane local defaults (Mailpit, no auth/TLS) and fail-open (rather than fail-closed) style for mail, which is acceptable here since mail failures are already swallowed and non-blocking for the flow. Consider adding a metric/alert on
MailServicesend failures so a prod SMTP misconfiguration (e.g. blankMAIL_USERNAME/MAIL_PASSWORD) doesn't go unnoticed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/user-service/src/main/resources/application.properties` around lines 32 - 44, Add observability for SMTP send failures in the password-reset mail flow so production misconfigurations don’t go unnoticed. Update the mail sending path in MailService (and any wrapper used by the reset flow) to emit a metric and/or alert when a send attempt fails, including the failure reason and relevant SMTP context. Keep the existing non-blocking behavior, but make sure failures are counted and visible for ops monitoring.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ansible-deploy.yml:
- Around line 39-42: The ansible-playbook invocation in the deploy workflow is
building --extra-vars with inline GitHub Actions expressions, which is
vulnerable to template-injection. Move all secrets and expressions currently
embedded in the shell command into env: values for the job/step, then reference
those environment variables in the ansible-playbook command so deploy.yml
receives safely expanded values without ${{
}} interpolation inside the script.
In `@backend/user-service/api/openapi.yaml`:
- Around line 542-547: Align the password policy between RegisterRequest and
ResetPasswordRequest in openapi.yaml by adding the same complexity requirement
to ResetPasswordRequest.newPassword that RegisterRequest.password uses. Update
the ResetPasswordRequest schema so newPassword keeps minLength: 8 and also
enforces the letter-plus-digit pattern, matching the existing register
validation and preventing weaker passwords during reset.
In
`@backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java`:
- Around line 14-16: Add a database-level unique constraint for the user
identifier so the one-active-reset-token-per-user rule is enforced even under
concurrent forgot-password requests. Update the PasswordResetTokenEntity userId
mapping to declare it unique, and add the matching UNIQUE(user_id) constraint in
the migration so deleteByUserId/save cannot create duplicate rows that would
break findByUserId. Reference PasswordResetTokenEntity and its userId field when
making the change.
In
`@backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java`:
- Line 15: The reset-token lookup path in PasswordResetService.resetPassword
should guard against null or blank tokens before calling
PasswordResetTokenRepository.findByResetToken, since Spring Data will otherwise
treat a null parameter as IS NULL. Add an early validation check using the
request token in resetPassword so the repository is only queried with a real
token value, and keep the lookup behavior in findByResetToken unchanged.
In
`@backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java`:
- Around line 87-111: The verifyResetCode flow in PasswordResetService updates
PasswordResetTokenEntity.attempts without protecting against concurrent
requests, so parallel wrong-code attempts can overwrite each other. Change the
token lookup in verifyResetCode/tokenRepository access to acquire a row lock
before checking isExpired, maxAttempts, and incrementing attempts, then keep the
save in the same transactional flow so each failed attempt is serialized.
- Around line 120-137: The reset flow in PasswordResetService.resetPassword
currently relies on tokenRepository.findByResetToken(...) even when
ResetPasswordRequest.getToken() is empty or null, which can lead to an
unintended repository lookup. Add a local guard at the start of resetPassword to
reject missing/blank tokens and throw InvalidPasswordResetException before
touching tokenRepository, while keeping the existing expiration and user lookup
logic unchanged.
In `@frontend/src/components/modals/AuthModal/index.tsx`:
- Around line 694-711: The OTP resend UI in AuthModal stays stuck on the notice
because resendNotice is never cleared after handleResend replaces the Resend
button. Update the AuthModal flow so resendNotice is reset when the user starts
a new verification attempt or edits the OTP, likely in handleVerify or
handleOtpInput, so the otpResend section can show the Resend button again after
another attempt.
---
Nitpick comments:
In `@backend/user-service/src/main/resources/application.properties`:
- Around line 32-44: Add observability for SMTP send failures in the
password-reset mail flow so production misconfigurations don’t go unnoticed.
Update the mail sending path in MailService (and any wrapper used by the reset
flow) to emit a metric and/or alert when a send attempt fails, including the
failure reason and relevant SMTP context. Keep the existing non-blocking
behavior, but make sure failures are counted and visible for ops monitoring.
In
`@backend/user-service/src/test/java/com/verita/userservice/service/PasswordResetServiceTests.java`:
- Around line 179-198: Add regression coverage for the null/blank token case in
PasswordResetServiceTests by extending the resetPassword scenarios around
service.resetPassword and resetRequest. The issue is that a null token can fall
through to tokenRepository.findByResetToken with Spring Data JPA null-to-IS NULL
behavior, so add a test asserting resetPassword(resetRequest(null, ...)) throws
InvalidPasswordResetException once the input guard is in place. Keep the
existing unknown-token and expired-token tests unchanged, and mirror their
assertion/verification style to confirm userRepository.save is not called.
In `@docker-compose.yml`:
- Around line 143-150: The Mailpit service image is using an unpinned tag, so
update the mailpit service definition to use a specific axllent/mailpit version
instead of latest. Make the change in the mailpit block so the docker-compose
setup remains reproducible across rebuilds and references the same service name
and image field.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b4ae6338-a878-4ba6-9960-7c2b6aecde5d
📒 Files selected for processing (29)
.github/workflows/ansible-deploy.yml.github/workflows/helm-deploy.yml.gitignorebackend/user-service/api/openapi.yamlbackend/user-service/build.gradlebackend/user-service/src/main/java/com/verita/userservice/controller/AuthController.javabackend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.javabackend/user-service/src/main/java/com/verita/userservice/exception/GlobalExceptionHandler.javabackend/user-service/src/main/java/com/verita/userservice/exception/InvalidPasswordResetException.javabackend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.javabackend/user-service/src/main/java/com/verita/userservice/service/AuthService.javabackend/user-service/src/main/java/com/verita/userservice/service/MailService.javabackend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.javabackend/user-service/src/main/resources/application.propertiesbackend/user-service/src/main/resources/db/migration/V3__password_reset_tokens.sqlbackend/user-service/src/test/java/com/verita/userservice/controller/AuthControllerTests.javabackend/user-service/src/test/java/com/verita/userservice/exception/GlobalExceptionHandlerTests.javabackend/user-service/src/test/java/com/verita/userservice/service/PasswordResetServiceTests.javadocker-compose.prod.ymldocker-compose.ymldocs/Infrastructure_Design.mdfrontend/src/components/modals/AuthModal/index.tsxfrontend/src/errors/AuthError.tsfrontend/src/services/auth.service.tsinfra/ansible/deploy.ymlinfra/helm/verita/templates/deployment.yamlinfra/helm/verita/templates/secret-mail.yamlinfra/helm/verita/values-prod.yamlinfra/helm/verita/values.yaml
💤 Files with no reviewable changes (1)
- backend/user-service/src/main/java/com/verita/userservice/service/AuthService.java
| ansible-playbook \ | ||
| -i infra/ansible/inventory.ini \ | ||
| infra/ansible/deploy.yml \ | ||
| --extra-vars "github_actor=${{ github.actor }} github_token=${{ secrets.GITHUB_TOKEN }} user_db_password=${{ secrets.USER_DB_PASSWORD }} content_db_password=${{ secrets.CONTENT_DB_PASSWORD }} recommendation_db_password=${{ secrets.RECOMMENDATION_DB_PASSWORD }} jwt_secret=${{ secrets.JWT_SECRET }} minio_root_password=${{ secrets.MINIO_ROOT_PASSWORD }} user_service_s3_access_key=${{ secrets.USER_SERVICE_S3_ACCESS_KEY }} user_service_s3_secret_key=${{ secrets.USER_SERVICE_S3_SECRET_KEY }} content_service_s3_access_key=${{ secrets.CONTENT_SERVICE_S3_ACCESS_KEY }} content_service_s3_secret_key=${{ secrets.CONTENT_SERVICE_S3_SECRET_KEY }} internal_service_token=${{ secrets.INTERNAL_SERVICE_TOKEN }}" | ||
| --extra-vars "github_actor=${{ github.actor }} github_token=${{ secrets.GITHUB_TOKEN }} user_db_password=${{ secrets.USER_DB_PASSWORD }} content_db_password=${{ secrets.CONTENT_DB_PASSWORD }} recommendation_db_password=${{ secrets.RECOMMENDATION_DB_PASSWORD }} jwt_secret=${{ secrets.JWT_SECRET }} minio_root_password=${{ secrets.MINIO_ROOT_PASSWORD }} user_service_s3_access_key=${{ secrets.USER_SERVICE_S3_ACCESS_KEY }} user_service_s3_secret_key=${{ secrets.USER_SERVICE_S3_SECRET_KEY }} content_service_s3_access_key=${{ secrets.CONTENT_SERVICE_S3_ACCESS_KEY }} content_service_s3_secret_key=${{ secrets.CONTENT_SERVICE_S3_SECRET_KEY }} internal_service_token=${{ secrets.INTERNAL_SERVICE_TOKEN }} mail_username=${{ secrets.MAIL_USERNAME }} mail_password=${{ secrets.MAIL_PASSWORD }} mail_from=${{ secrets.MAIL_FROM }}" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Pass secrets/expressions via env: instead of inline ${{ }} interpolation in the shell script.
zizmor flags this line for template-injection: GitHub Actions expands ${{ }} before the shell parses the script, so any interpolated value (e.g. github.actor) becomes part of the shell command text rather than a safely-quoted argument. This PR extends an already-flagged line with three more values.
🔒 Proposed fix using env vars
- name: Run Ansible playbook
+ env:
+ GH_ACTOR: ${{ github.actor }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }}
+ MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
+ MAIL_FROM: ${{ secrets.MAIL_FROM }}
run: |
ansible-playbook \
-i infra/ansible/inventory.ini \
infra/ansible/deploy.yml \
- --extra-vars "github_actor=${{ github.actor }} github_token=${{ secrets.GITHUB_TOKEN }} ... mail_username=${{ secrets.MAIL_USERNAME }} mail_password=${{ secrets.MAIL_PASSWORD }} mail_from=${{ secrets.MAIL_FROM }}"
+ --extra-vars "github_actor=$GH_ACTOR github_token=$GH_TOKEN ... mail_username=$MAIL_USERNAME mail_password=$MAIL_PASSWORD mail_from=$MAIL_FROM"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ansible-playbook \ | |
| -i infra/ansible/inventory.ini \ | |
| infra/ansible/deploy.yml \ | |
| --extra-vars "github_actor=${{ github.actor }} github_token=${{ secrets.GITHUB_TOKEN }} user_db_password=${{ secrets.USER_DB_PASSWORD }} content_db_password=${{ secrets.CONTENT_DB_PASSWORD }} recommendation_db_password=${{ secrets.RECOMMENDATION_DB_PASSWORD }} jwt_secret=${{ secrets.JWT_SECRET }} minio_root_password=${{ secrets.MINIO_ROOT_PASSWORD }} user_service_s3_access_key=${{ secrets.USER_SERVICE_S3_ACCESS_KEY }} user_service_s3_secret_key=${{ secrets.USER_SERVICE_S3_SECRET_KEY }} content_service_s3_access_key=${{ secrets.CONTENT_SERVICE_S3_ACCESS_KEY }} content_service_s3_secret_key=${{ secrets.CONTENT_SERVICE_S3_SECRET_KEY }} internal_service_token=${{ secrets.INTERNAL_SERVICE_TOKEN }}" | |
| --extra-vars "github_actor=${{ github.actor }} github_token=${{ secrets.GITHUB_TOKEN }} user_db_password=${{ secrets.USER_DB_PASSWORD }} content_db_password=${{ secrets.CONTENT_DB_PASSWORD }} recommendation_db_password=${{ secrets.RECOMMENDATION_DB_PASSWORD }} jwt_secret=${{ secrets.JWT_SECRET }} minio_root_password=${{ secrets.MINIO_ROOT_PASSWORD }} user_service_s3_access_key=${{ secrets.USER_SERVICE_S3_ACCESS_KEY }} user_service_s3_secret_key=${{ secrets.USER_SERVICE_S3_SECRET_KEY }} content_service_s3_access_key=${{ secrets.CONTENT_SERVICE_S3_ACCESS_KEY }} content_service_s3_secret_key=${{ secrets.CONTENT_SERVICE_S3_SECRET_KEY }} internal_service_token=${{ secrets.INTERNAL_SERVICE_TOKEN }} mail_username=${{ secrets.MAIL_USERNAME }} mail_password=${{ secrets.MAIL_PASSWORD }} mail_from=${{ secrets.MAIL_FROM }}" | |
| env: | |
| GH_ACTOR: ${{ github.actor }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} | |
| MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} | |
| MAIL_FROM: ${{ secrets.MAIL_FROM }} | |
| run: | | |
| ansible-playbook \ | |
| -i infra/ansible/inventory.ini \ | |
| infra/ansible/deploy.yml \ | |
| --extra-vars "github_actor=$GH_ACTOR github_token=$GH_TOKEN user_db_password=${{ secrets.USER_DB_PASSWORD }} content_db_password=${{ secrets.CONTENT_DB_PASSWORD }} recommendation_db_password=${{ secrets.RECOMMENDATION_DB_PASSWORD }} jwt_secret=${{ secrets.JWT_SECRET }} minio_root_password=${{ secrets.MINIO_ROOT_PASSWORD }} user_service_s3_access_key=${{ secrets.USER_SERVICE_S3_ACCESS_KEY }} user_service_s3_secret_key=${{ secrets.USER_SERVICE_S3_SECRET_KEY }} content_service_s3_access_key=${{ secrets.CONTENT_SERVICE_S3_ACCESS_KEY }} content_service_s3_secret_key=${{ secrets.CONTENT_SERVICE_S3_SECRET_KEY }} internal_service_token=${{ secrets.INTERNAL_SERVICE_TOKEN }} mail_username=$MAIL_USERNAME mail_password=$MAIL_PASSWORD mail_from=$MAIL_FROM" |
🧰 Tools
🪛 zizmor (1.26.1)
[error] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ansible-deploy.yml around lines 39 - 42, The
ansible-playbook invocation in the deploy workflow is building --extra-vars with
inline GitHub Actions expressions, which is vulnerable to template-injection.
Move all secrets and expressions currently embedded in the shell command into
env: values for the job/step, then reference those environment variables in the
ansible-playbook command so deploy.yml receives safely expanded values without
${{
}} interpolation inside the script.
Source: Linters/SAST tools
| ResetPasswordRequest: | ||
| type: object | ||
| required: [token, newPassword] | ||
| properties: | ||
| token: { type: string } | ||
| token: { type: string, description: The reset token returned by /verify-reset-code. } | ||
| newPassword: { type: string, minLength: 8 } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Password-reset flow allows weaker passwords than registration.
RegisterRequest.password enforces pattern: '^(?=.*[a-zA-Z])(?=.*\d).+$' (letter + digit) at line 496, but ResetPasswordRequest.newPassword only enforces minLength: 8 with no complexity pattern. Users could set a purely numeric or purely alphabetic 8-character password via reset, bypassing the complexity rule enforced at registration.
🔒 Proposed fix to align password policy
ResetPasswordRequest:
type: object
required: [token, newPassword]
properties:
token: { type: string, description: The reset token returned by /verify-reset-code. }
- newPassword: { type: string, minLength: 8 }
+ newPassword: { type: string, minLength: 8, pattern: '^(?=.*[a-zA-Z])(?=.*\d).+$' }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ResetPasswordRequest: | |
| type: object | |
| required: [token, newPassword] | |
| properties: | |
| token: { type: string } | |
| token: { type: string, description: The reset token returned by /verify-reset-code. } | |
| newPassword: { type: string, minLength: 8 } | |
| ResetPasswordRequest: | |
| type: object | |
| required: [token, newPassword] | |
| properties: | |
| token: { type: string, description: The reset token returned by /verify-reset-code. } | |
| newPassword: { type: string, minLength: 8, pattern: '^(?=.*[a-zA-Z])(?=.*\d).+$' } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/user-service/api/openapi.yaml` around lines 542 - 547, Align the
password policy between RegisterRequest and ResetPasswordRequest in openapi.yaml
by adding the same complexity requirement to ResetPasswordRequest.newPassword
that RegisterRequest.password uses. Update the ResetPasswordRequest schema so
newPassword keeps minLength: 8 and also enforces the letter-plus-digit pattern,
matching the existing register validation and preventing weaker passwords during
reset.
| * <p>{@code forgot-password} creates one row per request holding a hashed 6-digit code; a matching | ||
| * {@code verify-reset-code} call stamps {@link #resetToken}, which {@code reset-password} then | ||
| * consumes. At most one active row exists per user — a new request deletes the prior rows. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether V3 migration enforces uniqueness/index on user_id
fd 'V3__password_reset_tokens.sql' | xargs catRepository: AET-DevOps26/team-colleague-md
Length of output: 1179
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the entity, repository, and service usage around password reset tokens
for f in \
backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java \
backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java \
backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java
do
echo "===== $f ====="
sed -n '1,220p' "$f"
echo
doneRepository: AET-DevOps26/team-colleague-md
Length of output: 8654
Add a DB-level unique constraint on user_iddeleteByUserId + save only enforces “one active row per user” in application code. A concurrent forgot-password request can insert a second row, and findByUserId(...) will then fail with multiple matches instead of the expected reset flow. Add UNIQUE(user_id) in the migration and mirror it on PasswordResetTokenEntity.userId.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java`
around lines 14 - 16, Add a database-level unique constraint for the user
identifier so the one-active-reset-token-per-user rule is enforced even under
concurrent forgot-password requests. Update the PasswordResetTokenEntity userId
mapping to declare it unique, and add the matching UNIQUE(user_id) constraint in
the migration so deleteByUserId/save cannot create duplicate rows that would
break findByUserId. Reference PasswordResetTokenEntity and its userId field when
making the change.
|
|
||
| Optional<PasswordResetTokenEntity> findByUserId(UUID userId); | ||
|
|
||
| Optional<PasswordResetTokenEntity> findByResetToken(String resetToken); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files
git ls-files | rg 'backend/user-service/src/main/java/com/verita/userservice/(repository|service|entity)/.*PasswordReset.*|backend/user-service/src/main/java/com/verita/userservice/.*PasswordReset.*'
# Show the repository interface, entity, and service with line numbers
for f in \
backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java \
backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java \
backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java
do
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
doneRepository: AET-DevOps26/team-colleague-md
Length of output: 10856
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the request DTO and reset-password endpoint
git ls-files | rg 'ResetPasswordRequest|PasswordReset.*Controller|AuthController|.*Controller.java|.*Controller.kt'
# Inspect the request model and any controller entrypoint that calls PasswordResetService.resetPassword
for f in \
backend/model/src/main/java/com/verita/model/ResetPasswordRequest.java \
backend/user-service/src/main/java/com/verita/userservice/controller/PasswordResetController.java \
backend/user-service/src/main/java/com/verita/userservice/controller/AuthController.java
do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
fi
done
# Search for validation annotations or null guards around the reset token field and resetPassword usage
rg -n -S 'resetPassword\(|getToken\(\)|`@NotNull`|`@NotBlank`|`@Valid`|ResetPasswordRequest' backend -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 14014
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the OpenAPI schema and generated request model for reset token validation/nullability
for f in \
backend/user-service/api/openapi.yaml \
backend/model/src/main/java/com/verita/model/ResetPasswordRequest.java
do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '520,590p;1,220p'
fi
done
# Also inspect any Bean Validation annotations on request fields in the generated model
rg -n -S 'class ResetPasswordRequest|token|`@NotNull`|`@NotBlank`|`@Nullable`|`@Schema`' backend/model/src/main/java/com/verita/model/ResetPasswordRequest.java backend/user-service/api/openapi.yamlRepository: AET-DevOps26/team-colleague-md
Length of output: 12948
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the generated ResetPasswordRequest model
fd -a 'ResetPasswordRequest.java|ResetPasswordRequest.kt|ResetPasswordRequest.*' backend
# Read the generated model if present
for f in $(fd -a 'ResetPasswordRequest.java|ResetPasswordRequest.kt|ResetPasswordRequest.*' backend); do
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,220p'
done
# Inspect any tests or validation setup around auth request bodies
rg -n -S '`@Validated`|`@Valid`|`@NotNull`|`@NotBlank`|ResetPasswordRequest' backend/user-service/src/main/java backend/user-service/src/test/java backend/model/src/main/java -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 2054
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect user-service build/generator config and generated API interface
for f in \
backend/user-service/pom.xml \
backend/user-service/api/.openapi-generator-ignore \
backend/user-service/api/openapi-generator-config.yaml \
backend/user-service/src/main/java/com/verita/api/AuthenticationApi.java
do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
fi
done
# Search for bean validation / generator settings relevant to request nullability
rg -n -S 'useBeanValidation|performBeanValidation|spring-boot|openapi-generator|validation|`@Valid`|`@Validated`|`@NotNull`|`@NotBlank`|`@Schema`' backend/user-service -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 1539
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the generated API interface and request DTO source
git ls-files | rg 'AuthenticationApi\.java|ResetPasswordRequest\.java|ResetPasswordRequest\.kt|com/verita/api/AuthenticationApi\.java|com/verita/model/ResetPasswordRequest\.java'
# Read any matching files with line numbers
for f in $(git ls-files | rg 'AuthenticationApi\.java|ResetPasswordRequest\.java|ResetPasswordRequest\.kt|com/verita/api/AuthenticationApi\.java|com/verita/model/ResetPasswordRequest\.java'); do
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
done
# Inspect validation/annotation usage around the auth API and reset request types
rg -n -S '`@Valid`|`@Validated`|`@NotNull`|`@NotBlank`|`@Schema`|ResetPasswordRequest|AuthenticationApi' backend/user-service/src/main/java backend/user-service/src/test/java backend/user-service/api -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 168
🌐 Web query:
Spring Data JPA derived query method null parameter IS NULL Optional multiple matches IncorrectResultSizeDataAccessException documentation
💡 Result:
In Spring Data JPA, derived query methods handle null parameters and multiple results according to specific rules: 1. Null Parameter Handling: When you pass a null value to a derived query method for an equality predicate, Spring Data JPA automatically interprets this as an IS NULL condition in the generated SQL [1][2][3][4]. For example, a method findByFirstname(String firstname) will execute "... where x.firstname =?1" if a value is provided, but will be converted to "... where x.firstname IS NULL" if the argument passed is null [1][2]. 2. Multiple Results and IncorrectResultSizeDataAccessException: When a repository query method is defined to return a single entity (or an Optional), it expects the query to return at most one result [5]. If the query finds more than one record, Spring Data JPA throws an IncorrectResultSizeDataAccessException [6][5][7]. This exception is intended to enforce the constraint that the operation should only return a unique result; if you expect or intend to handle multiple results, the method should be defined to return a collection type (e.g., List or Set) instead [5][7]. 3. Optional Return Types: Using Optional as a return type for a query method changes how the absence of a result is handled—returning Optional.empty instead of null—but it does not change the behavior regarding multiple results [5]. It still expects a single result and will throw an IncorrectResultSizeDataAccessException if multiple records are found [5][7]. 4. Important Nuance: Occasionally, a query might appear to return multiple rows but still not trigger this exception. This can occur if the underlying provider (like Hibernate) identifies the multiple results as functionally equivalent based on the object's equals implementation, in which case it may consolidate them into a single result [8].
Citations:
- 1: https://docs.spring.io/spring-data/jpa/reference/4.0-SNAPSHOT/jpa/query-methods.html
- 2: https://docs.spring.io/spring-data/jpa/reference/3.5/jpa/query-methods.html
- 3: https://www.baeldung.com/spring-data-derived-queries
- 4: https://www.baeldung.com/spring-data-jpa-null-parameters
- 5: https://docs.spring.io/spring-data/jpa/reference/repositories/query-return-types-reference.html
- 6: https://stackoverflow.com/questions/57140122/spring-data-jpa-findone-throwing-incorrectresultsizedataaccessexception-exceptio
- 7: @Query annotated repository method with an Optional<> return type throws IncorrectResultSizeDataAccessException [DATAJPA-756] spring-projects/spring-data-jpa#1127
- 8: No IncorrectResultSizeDataAccessException is thrown as expected [DATAJPA-1038] spring-projects/spring-data-jpa#1382
Guard the reset token before the repository lookup
Spring Data turns a null equality parameter into IS NULL, so findByResetToken(request.getToken()) can resolve against pending reset rows — or fail if more than one exists. Add a null/blank check in PasswordResetService.resetPassword before the lookup.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java`
at line 15, The reset-token lookup path in PasswordResetService.resetPassword
should guard against null or blank tokens before calling
PasswordResetTokenRepository.findByResetToken, since Spring Data will otherwise
treat a null parameter as IS NULL. Add an early validation check using the
request token in resetPassword so the repository is only queried with a real
token value, and keep the lookup behavior in findByResetToken unchanged.
| @Transactional | ||
| public VerifyResetCodeResponse verifyResetCode(VerifyResetCodeRequest request) { | ||
| UserEntity user = userRepository.findByEmail(request.getEmail()) | ||
| .orElseThrow(InvalidPasswordResetException::new); | ||
|
|
||
| PasswordResetTokenEntity entity = tokenRepository.findByUserId(user.getId()) | ||
| .orElseThrow(InvalidPasswordResetException::new); | ||
|
|
||
| if (isExpired(entity) || entity.getAttempts() >= maxAttempts) { | ||
| throw new InvalidPasswordResetException(); | ||
| } | ||
|
|
||
| if (!encoder.matches(request.getCode(), entity.getCodeHash())) { | ||
| entity.setAttempts(entity.getAttempts() + 1); | ||
| tokenRepository.save(entity); | ||
| throw new InvalidPasswordResetException(); | ||
| } | ||
|
|
||
| entity.setResetToken(UUID.randomUUID().toString()); | ||
| tokenRepository.save(entity); | ||
|
|
||
| VerifyResetCodeResponse response = new VerifyResetCodeResponse(); | ||
| response.setResetToken(entity.getResetToken()); | ||
| return response; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the service, repository, and entity involved in password reset.
git ls-files | rg 'PasswordReset(Service|TokenEntity|TokenRepository|.*Request|.*Response)\.java$|UserEntity\.java$'
# Show relevant outlines for structure and annotations.
for f in \
backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java \
backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java \
backend/user-service/src/main/java/com/verita/userservice/model/PasswordResetTokenEntity.java
do
echo "===== $f ====="
ast-grep outline "$f" --view expanded || true
done
# Print the relevant slices with line numbers.
for f in \
backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java \
backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java \
backend/user-service/src/main/java/com/verita/userservice/model/PasswordResetTokenEntity.java
do
echo "===== $f (excerpt) ====="
wc -l "$f"
done
sed -n '1,220p' backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java | cat -n
echo "===== repository ====="
sed -n '1,220p' backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java | cat -n
echo "===== entity ====="
sed -n '1,260p' backend/user-service/src/main/java/com/verita/userservice/model/PasswordResetTokenEntity.java | cat -n
# Search for locking/versioning/query hints on the token entity/repository.
rg -n "`@Version`|`@Lock`|PESSIMISTIC|OPTIMISTIC|LockModeType|SELECT .*FOR UPDATE|attempts" backend/user-service/src/main/java/com/verita/userservice -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 2312
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the entity and any persistence/transaction settings that could affect concurrency.
sed -n '1,260p' backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java | cat -n
echo "===== search for version/locking/isolation ====="
rg -n "`@Version`|`@Lock`|PESSIMISTIC|OPTIMISTIC|Isolation|isolation|hibernate\.jdbc|spring\.jpa|transaction-isolation|READ_COMMITTED|READ COMMITTED" \
backend/user-service/src/main/resources backend/user-service/src/main/java -g '!**/target/**'
echo "===== related repository/service references ====="
rg -n "findByUserId\(|save\(entity\)|setAttempts\(|getAttempts\(" backend/user-service/src/main/java/com/verita/userservice -g '!**/target/**'Repository: AET-DevOps26/team-colleague-md
Length of output: 4329
Lock the reset-token row before incrementing attempts. Concurrent wrong-code requests can read the same attempts value and overwrite each other’s increments under READ COMMITTED, which lets an attacker bypass maxAttempts with enough parallel guesses.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java`
around lines 87 - 111, The verifyResetCode flow in PasswordResetService updates
PasswordResetTokenEntity.attempts without protecting against concurrent
requests, so parallel wrong-code attempts can overwrite each other. Change the
token lookup in verifyResetCode/tokenRepository access to acquire a row lock
before checking isExpired, maxAttempts, and incrementing attempts, then keep the
save in the same transactional flow so each failed attempt is serialized.
| public void resetPassword(ResetPasswordRequest request) { | ||
| PasswordResetTokenEntity entity = tokenRepository.findByResetToken(request.getToken()) | ||
| .orElseThrow(InvalidPasswordResetException::new); | ||
|
|
||
| if (isExpired(entity)) { | ||
| throw new InvalidPasswordResetException(); | ||
| } | ||
|
|
||
| UserEntity user = userRepository.findById(entity.getUserId()) | ||
| .orElseThrow(InvalidPasswordResetException::new); | ||
|
|
||
| user.setPassword(encoder.encode(request.getNewPassword())); | ||
| user.setRefreshToken(null); | ||
| user.setRefreshTokenExpiry(null); | ||
| userRepository.save(user); | ||
|
|
||
| tokenRepository.deleteByUserId(user.getId()); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## Files matching ResetPasswordRequest / PasswordResetController / findByResetToken\n'
git ls-files | rg 'ResetPasswordRequest|PasswordResetController|PasswordResetService|PasswordResetTokenRepository|openapi|swagger|yaml|yml'
printf '\n## Search for token validation annotations and reset password endpoints\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' \
'ResetPasswordRequest|PasswordResetController|findByResetToken|`@NotNull`|`@NotBlank`|`@Valid`|resetPassword' \
backend/user-service/src/main/java backend/user-service/src/main/resources . 2>/dev/null || trueRepository: AET-DevOps26/team-colleague-md
Length of output: 11736
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## AuthController excerpt\n'
sed -n '1,130p' backend/user-service/src/main/java/com/verita/userservice/controller/AuthController.java | cat -n
printf '\n## PasswordResetTokenRepository excerpt\n'
sed -n '1,80p' backend/user-service/src/main/java/com/verita/userservice/repository/PasswordResetTokenRepository.java | cat -n
printf '\n## OpenAPI ResetPasswordRequest schema excerpt\n'
sed -n '520,590p' backend/user-service/api/openapi.yaml | cat -n
printf '\n## Generated ResetPasswordRequest model excerpt\n'
fd -a 'ResetPasswordRequest.java' backend/user-service | xargs -r sed -n '1,220p' | cat -nRepository: AET-DevOps26/team-colleague-md
Length of output: 8725
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## Generated API interface and model locations\n'
git ls-files | rg 'AuthenticationApi\.java|ResetPasswordRequest\.java|VerifyResetCodeRequest\.java|ForgotPasswordRequest\.java|OpenApi|ApiController|model/ResetPasswordRequest'
printf '\n## Search for validation annotations on reset-password types\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' \
'`@NotNull`|`@NotBlank`|`@Valid`|class ResetPasswordRequest|interface AuthenticationApi|resetPassword\(' \
backend/user-service/src/main/java backend/user-service/src/generated backend/user-service/target 2>/dev/null || trueRepository: AET-DevOps26/team-colleague-md
Length of output: 216
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## user-service tree around potential generated sources\n'
fd -a -t d 'src|generated|openapi|api' backend/user-service | sed -n '1,200p'
printf '\n## Search for OpenAPI generator and validation config\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' \
'openapi-generator|springdoc|swagger|`@Validated`|`@Valid`|`@NotNull`|`@NotBlank`|bean validation|validation' \
backend/user-service pom.xml .github/workflows backend/user-service/api backend/user-service/src/main 2>/dev/null || true
printf '\n## Find any AuthenticationApi or ResetPasswordRequest sources anywhere\n'
fd -a 'AuthenticationApi.java|ResetPasswordRequest.java' . | sed -n '1,200p'Repository: AET-DevOps26/team-colleague-md
Length of output: 1529
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## backend/user-service/build.gradle excerpt\n'
sed -n '1,220p' backend/user-service/build.gradle | cat -nRepository: AET-DevOps26/team-colleague-md
Length of output: 4319
🌐 Web query:
OpenAPI Generator spring generator interfaceOnly requestBody @Validrequired property@NotNull useBeanValidation default
💡 Result:
In the OpenAPI Generator Spring generator, the inclusion of @NotNull on request bodies and path variables is controlled primarily by the additionalNotNullAnnotations configuration option, rather than relying solely on useBeanValidation [1][2]. Key Configuration Details: 1. additionalNotNullAnnotations: Introduced to provide explicit control, this option defaults to false [2]. When set to true, the generator includes @NotNull annotations on request bodies and required path variables [1][2]. If you require these annotations, you must explicitly enable this property in your generator configuration (e.g., via configOptions or --additional-properties) [2]. 2. useBeanValidation: This property controls the generation of general Jakarta/JSR-303 Bean Validation API annotations (e.g., @Size, @Pattern, @Min, @Max) [1]. While historically linked to @NotNull, recent updates have decoupled the specific injection of @NotNull for request bodies and path variables to allow for more granular control [2]. Setting useBeanValidation to true will generate validation annotations on your models and parameters, provided the library supports it [3][4]. 3. interfaceOnly: When interfaceOnly is set to true, the generator produces API interface stubs [1]. In this mode, the generation of @Valid and @NotNull still adheres to the configuration of useBeanValidation and additionalNotNullAnnotations [3][4]. Note that for specific libraries like spring-http-interface, recent updates have improved support for honoring these bean validation flags, allowing you to use bean validation even when generating interfaces [3][4]. If you find that @NotNull or @Valid annotations are being generated when they should not be (or vice versa), ensure that both useBeanValidation and additionalNotNullAnnotations are explicitly set in your configuration, as behavior can sometimes depend on the specific library (e.g., spring-boot, spring-http-interface) and the version of the generator you are using [2][5][6].
Citations:
- 1: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/spring.md
- 2: [spring] Add an option "additionalNotNullAnnotations" to include additional NotNull annotations OpenAPITools/openapi-generator#23096
- 3: [Java-Spring] Enable bean validation for
spring-http-interfacewhen configured OpenAPITools/openapi-generator#23609 - 4: [JAVA-SPRING] - 22859 - spring http interface library should support 'useBeanValidation' OpenAPITools/openapi-generator#23803
- 5: [BUG][Java][Spring] useBeanValidation=false still generates jakarta.validation.constraints.NotNull OpenAPITools/openapi-generator#23751
- 6: [BUG][JAVA] Bug generating @NotNull when useBeanValidation is set to false OpenAPITools/openapi-generator#13885
🌐 Web query:
OpenAPI Generator spring generator performBeanValidation @Valid @NotNull required schema properties
💡 Result:
To enable Bean Validation (such as @Valid and @NotNull) for your Spring Boot application using the OpenAPI Generator, you must configure the following options in your generator configuration (e.g., in your Maven or Gradle plugin configuration) [1][2]: 1. useBeanValidation: Set this to true to enable the generation of Bean Validation API annotations (like @NotNull, @Size, @Email) on your model objects based on your OpenAPI schema definitions [3][1][2]. 2. performBeanValidation: Set this to true to enable the generation of code that triggers bean validation logic within the Spring controller layer [3][1][2]. These settings ensure that when an API request is received, Spring automatically validates the incoming data against the constraints defined in your OpenAPI specification [2]. Important configuration details: - If you are using a modern version of Spring Boot (Spring Boot 3.x+), you should also ensure that useJakartaEe is set to true (default in many newer versions) so that the generator uses jakarta.validation annotations instead of the deprecated javax.validation packages [4]. - When enabled, the controller classes are typically annotated with @Validated, which instructs Spring to perform the validation [5][6]. - For Spring Boot 6.x or later, if you encounter conflicts with class-level @Validated, you may optionally set useSpringBuiltInValidation to true to disable it, aligning with updated Spring Framework guidelines [6]. Example configuration (Maven): true true true By including these options, the OpenAPI Generator will map required schema properties to @NotNull annotations and apply other relevant constraints, providing automatic validation for your request bodies and parameters [2].
Citations:
- 1: https://stackoverflow.com/questions/53443712/how-can-i-use-the-usebeanvalidation-option-when-generating-code-in-swagger
- 2: https://java.elitedev.in/java/contract-first-spring-boot-apis-openapi-generator/
- 3: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/spring.md
- 4: https://stackoverflow.com/questions/75057781/openapi-generator-maven-plugin-field-validation-required-ignored-spring
- 5: https://github.com/OpenAPITools/openapi-generator/blob/v5.4.0/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache
- 6: [java][spring] add useSpringBuiltInValidation option to disable @Validated at class level (fix #20899) OpenAPITools/openapi-generator#20901
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' \
'spring-boot-starter-validation|hibernate-validator|jakarta.validation-api|jakarta.validation' \
backend/user-service/build.gradle backend/user-service/pom.xml . 2>/dev/null || trueRepository: AET-DevOps26/team-colleague-md
Length of output: 1882
🌐 Web query:
Spring Boot 4 spring-boot-starter-web includes validation dependency or not
💡 Result:
No, the spring-boot-starter-web (or its successor, spring-boot-starter-webmvc, in Spring Boot 4) does not include the validation dependency by default [1][2]. If your application requires Bean Validation, you must explicitly add the spring-boot-starter-validation dependency to your project's build file [1][3]. This has been the standard behavior since Spring Boot 2.3 [3][4]. In Spring Boot 4, the web starter was renamed to spring-boot-starter-webmvc as part of a modularization effort, but it maintains the same behavior regarding the validation starter [2]. To include validation, add the following dependency to your project: Maven: org.springframework.boot spring-boot-starter-validation Gradle: implementation 'org.springframework.boot:spring-boot-starter-validation'
Citations:
- 1: https://docs.moderne.io/user-documentation/recipes/recipe-catalog/java/spring/boot4/addvalidationstarterdependency/
- 2: https://medium.com/@davide.mib/spring-boot-4-0-migration-what-nobody-tells-you-about-the-breaking-changes-94482a2f886f
- 3: https://stackoverflow.com/questions/61959918/spring-boot-validations-stopped-working-after-upgrade-from-2-2-5-to-2-3-0
- 4: https://www.masterspringboot.com/data-access/jpa-applications/data-validation-in-spring-boot-2-3-0-and-beyond/
🌐 Web query:
OpenAPI Generator spring interfaceOnly generated interface @Valid @NotNull required request body model
💡 Result:
To ensure the OpenAPI Generator for Spring correctly generates @Valid and @NotNull annotations for request bodies when using interfaceOnly, you must configure specific generator properties. Configuration Requirements: 1. Enable Bean Validation: Set useBeanValidation to true in your configuration [1][2]. While this was previously restricted in some spring-http-interface library configurations, recent updates allow it to be enabled [3][4]. 2. Enable Request Body Nullability Annotations: By default, the generator may not emit @NotNull for request bodies even if required in the OpenAPI specification [5]. You must set the additionalNotNullAnnotations property to true to ensure these are generated on request bodies and path variables [6][7][5]. Example Configuration (Maven/Gradle): In your configuration block, include these properties: true true true true Key Considerations: - Bean Validation: Setting useBeanValidation=true triggers the generation of Jakarta/Javax validation annotations (such as @NotNull) on your model fields based on the OpenAPI schema definition [3][4]. - Interface @Valid: When useBeanValidation is enabled, the generator typically adds @Valid to method parameters (like the request body) in the interface to trigger Spring's validation mechanism when the method is invoked [3]. - Additional Annotations: The additionalNotNullAnnotations option was specifically introduced to provide explicit control over adding @NotNull to required request bodies and path variables, as this behavior changed in recent versions to be disabled by default [5]. Ensure your project includes the necessary validation dependencies (e.g., spring-boot-starter-validation) in your build file (Maven or Gradle) to support these annotations at runtime [3].
Citations:
- 1: [BUG][JavaSpring] Spring HTTP Interface library must support useBeanValidation OpenAPITools/openapi-generator#22859
- 2: [BUG] useBeanValidation is always false OpenAPITools/openapi-generator#23585
- 3: [JAVA-SPRING] - 22859 - spring http interface library should support 'useBeanValidation' OpenAPITools/openapi-generator#23803
- 4: [Java-Spring] Enable bean validation for
spring-http-interfacewhen configured OpenAPITools/openapi-generator#23609 - 5: [spring] Add an option "additionalNotNullAnnotations" to include additional NotNull annotations OpenAPITools/openapi-generator#23096
- 6: https://openapi-generator.tech/docs/generators/spring/
- 7: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/spring.md
🌐 Web query:
Spring Data JPA derived query null parameter translates to IS NULL
💡 Result:
Yes, in Spring Data JPA, when using derived query methods, passing a null value for an equality condition (e.g., using Is or Equals keywords) automatically translates the generated query to use IS NULL instead of an equality check [1][2][3]. For example, a method defined as: List findByFirstname(String firstname); If you call this method with a null argument, the generated SQL clause will be:... where x.firstname IS NULL Similarly, for inequality checks using the Not keyword, passing a null value translates to IS NOT NULL [1][4][5]. If you prefer to be explicit or if you do not want to pass parameters, you can use the dedicated keywords IsNull or IsNotNull in your method name, which do not accept arguments [6][3][7]: List findByNameIsNull; List findByNameIsNotNull; This behavior for equality and inequality keywords ensures that Spring Data JPA handles null parameters consistently with SQL semantics [3].
Citations:
- 1: https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html
- 2: spring-projects/spring-data-jpa@010075d
- 3: https://www.baeldung.com/spring-data-derived-queries
- 4: https://docs.spring.io/spring-data/jpa/reference/4.1-SNAPSHOT/jpa/query-methods.html
- 5: https://docs.spring.io/spring-data/jpa/reference/3.5/jpa/query-methods.html
- 6: https://docs.spring.io/spring-data/jpa/reference/repositories/query-keywords-reference.html
- 7: https://docs.spring.io/spring-data/jpa/docs/current-SNAPSHOT/reference/html/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## PasswordResetService excerpt around forgot/verify/reset\n'
sed -n '1,220p' backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java | cat -n
printf '\n## PasswordResetTokenEntity excerpt\n'
sed -n '1,220p' backend/user-service/src/main/java/com/verita/userservice/entity/PasswordResetTokenEntity.java | cat -nRepository: AET-DevOps26/team-colleague-md
Length of output: 9146
Reject empty reset tokens before the repository lookupResetPasswordRequest.token is required in the OpenAPI spec, but this module doesn’t enable bean-validation enforcement, so a missing token can still reach findByResetToken(...). Spring Data JPA treats a null equality parameter as IS NULL, and resetToken is nullable before verification, so guard this locally.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@backend/user-service/src/main/java/com/verita/userservice/service/PasswordResetService.java`
around lines 120 - 137, The reset flow in PasswordResetService.resetPassword
currently relies on tokenRepository.findByResetToken(...) even when
ResetPasswordRequest.getToken() is empty or null, which can lead to an
unintended repository lookup. Add a local guard at the start of resetPassword to
reject missing/blank tokens and throw InvalidPasswordResetException before
touching tokenRepository, while keeping the existing expiration and user lookup
logic unchanged.
| <div className={styles.otpResend}> | ||
| Didn't get a code?{' '} | ||
| <button className={styles.switchLinkBtn}>Resend</button> | ||
| {resendNotice ? ( | ||
| <span data-testid="otp-resend-notice">{resendNotice}</span> | ||
| ) : ( | ||
| <> | ||
| Didn't get a code?{' '} | ||
| <button | ||
| type="button" | ||
| className={styles.switchLinkBtn} | ||
| data-testid="otp-resend-btn" | ||
| onClick={handleResend} | ||
| disabled={loading} | ||
| > | ||
| Resend | ||
| </button> | ||
| </> | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Resend button is permanently hidden after the first resend.
Once handleResend sets resendNotice, this ternary swaps the "Resend" button for the notice text, and nothing clears resendNotice while the user stays on the OTP screen (handleVerify clears error but not resendNotice). A user who resent once but still didn't receive a code (or whose second code also fails) cannot request another one without leaving and re-entering the flow.
Consider clearing resendNotice when the user next edits the OTP (in handleOtpInput) or on the next verify attempt so the Resend affordance returns.
Proposed fix (clear notice on verify attempt)
async function handleVerify() {
const code = otpDigits.join('');
if (code.length < 6 || loading) return;
setError('');
+ setResendNotice('');
setLoading(true);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className={styles.otpResend}> | |
| Didn't get a code?{' '} | |
| <button className={styles.switchLinkBtn}>Resend</button> | |
| {resendNotice ? ( | |
| <span data-testid="otp-resend-notice">{resendNotice}</span> | |
| ) : ( | |
| <> | |
| Didn't get a code?{' '} | |
| <button | |
| type="button" | |
| className={styles.switchLinkBtn} | |
| data-testid="otp-resend-btn" | |
| onClick={handleResend} | |
| disabled={loading} | |
| > | |
| Resend | |
| </button> | |
| </> | |
| )} | |
| </div> | |
| async function handleVerify() { | |
| const code = otpDigits.join(''); | |
| if (code.length < 6 || loading) return; | |
| setError(''); | |
| setResendNotice(''); | |
| setLoading(true); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/modals/AuthModal/index.tsx` around lines 694 - 711,
The OTP resend UI in AuthModal stays stuck on the notice because resendNotice is
never cleared after handleResend replaces the Resend button. Update the
AuthModal flow so resendNotice is reset when the user starts a new verification
attempt or edits the OTP, likely in handleVerify or handleOtpInput, so the
otpResend section can show the Resend button again after another attempt.
- reject null/blank reset tokens before the repository lookup so a missing token cannot resolve to `reset_token IS NULL` and match an unverified row - enforce UNIQUE(user_id) on password_reset_tokens (migration + entity) so concurrent forgot-password requests cannot create duplicate rows - align reset newPassword complexity (letter + digit) with registration - clear the OTP resend notice on re-verify so the Resend button returns
MichaelChennn
left a comment
There was a problem hiding this comment.
Fix some issued suggested by coderabbitai.
## Linked Issues <!-- Final sprint-end release PR: dev → main --> Resolves # ## Summary of Changes Final release promoting ~30 commits from `dev` to `main`, including: - **Digest**: standalone digest entity with structured events & public/private management (ADR-0019, #203), latest-digest-per-day history fix (#208), deterministic digest titles, daily-digest default for new users - **Admin**: admin panel with user management + GenAI ops surface (ADR-0020) & 404 page (#206), per-day digest generation with tracked status, verified-callable model suggestions - **GenAI**: output sanitization + local Ollama inference support (#210), runtime LLM provider/model selection, LLM env vars across deploy configs (#207) - **User**: password reset via Brevo SMTP (#202), profile postCount/likeReceivedCount write-back fixes (#178/#204) - **Infra/Deploy**: GenAI & mail secrets wired into Helm deploy, `docker-compose.prod.yml` LLM/mail env vars, manual production demo seeding workflow (#209) - **Docs/Tests**: review-ready docs + restructured frontend e2e suite (#211) ## Testing Performed - [x] PR targets `main` intentionally — this is the sprint-end release PR (`dev` → `main`) - [x] `npm run lint` + `npm run build` passed (frontend) - [x] `./gradlew test` passed for user-service, content-service, recommendation-service - [x] GenAI service tests green in CI (`CI - GenAI Service`); all latest per-service CI runs on `dev` are green - [x] Merge `dev` → `main` verified conflict-free locally - [x] All deploy-required GitHub Secrets present (MAIL_*, NVIDIA_NIM_API_KEY, LOGOS_API_KEY, GNEWS_API_KEY, etc.) - [x] Helm Deploy (Rancher/K8s) run on 2026-07-16 succeeded ## Review Notes **Draft reason — one open deployment issue:** the Ansible Deploy to the Azure VM (68.210.231.173) failed on 2026-07-16 with an SSH connection timeout (host unreachable). This is environmental (VM stopped or NSG/firewall), not a code issue — the Helm/Rancher deploy path is healthy. Once the VM is reachable (or if the VM path is out of scope for this release), mark this PR ready for review. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
password reset feature issue #174
Summary by CodeRabbit
New Features
Bug Fixes