Skip to content

fix(auth): don't issue password reset links without a keycloak id - #769

Open
Bulibas wants to merge 1 commit into
podkrepi-bg:masterfrom
Bulibas:fix/password-reset-missing-keycloak-id
Open

Bulibas wants to merge 1 commit into
podkrepi-bg:masterfrom
Bulibas:fix/password-reset-missing-keycloak-id

Conversation

@Bulibas

@Bulibas Bulibas commented Oct 26, 2026

Copy link
Copy Markdown

Refs podkrepi-bg/frontend#2232

Hi, and thanks for maintaining this.

A quick note first: I'm not familiar enough with your stack to be confident about this change. It's a suggestion based on an analysis I did with Claude (an AI assistant), so please review it carefully, and feel free to adjust or close it.

What I think is happening

When I tried to reset my password, the token in the email link had "sub": null. Reading the code, it looks like a person record can exist without a keycloakId. sendMailForPasswordChange then signs the reset token with sub: null, and when the new password is submitted, updateForgottenPassword calls Keycloak's reset-password with id: null. That seems bound to fail, and the change-password page shows its generic error.

Suggested change

  • sendMailForPasswordChange: if keycloakId is missing, look up the Keycloak user by email (admin.users.find({ email, exact: true })) and save the link on the person.
  • If there's no Keycloak user, return 404 instead of sending a link that can't work. The forgot-password form already shows "User not found" for a 404, so the user at least gets a clearer message.
  • updateForgottenPassword: reject tokens without a sub before calling Keycloak.

Security, as far as I can tell

The reset link still goes only to the email address on the person record, so only the owner of that address can use it. Registration already links a person to a Keycloak user by email in a similar way (the createPerson upsert). I may be missing something here, though, so a second look would be very welcome.

An edge case I'm unsure about

If another person record already holds the same keycloakId (the column is @unique), the update would fail with a 500. I'd expect this to be rare, but I'm happy to handle it differently if you prefer.

Tests

  • 4 new tests: a stored id is used as-is; a missing id is linked by email; no link is sent when there's no Keycloak user; a token without sub is rejected.
  • jest apps/api/src/auth: 32 passed, and tsc --noEmit is clean.

Thank you for your time!

🤖 Generated with Claude Code

Persons created without a Keycloak account (e.g. guest donors), or whose
link to Keycloak was lost, have keycloakId = null. Forgot-password still
signed a reset token with sub: null, so submitting the new password
called Keycloak's reset-password with id null and always failed with a
generic error on the change-password page.

- When keycloakId is missing, look up the Keycloak user by email and
  store the link on the person.
- If no Keycloak user exists, return 404 instead of sending a link that
  can never work.
- Reject reset tokens without a sub before calling Keycloak.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

❌ Not all tests have run for this PR. Please add the run tests label to trigger them.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant