Skip to content

Check customer e-mails in the account's store and change passwords on the account - #1009

Open
KrzysztofPajak wants to merge 3 commits into
developfrom
fix/subaccount-password-scope
Open

KrzysztofPajak wants to merge 3 commits into
developfrom
fix/subaccount-password-scope

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Type: bugfix

Issue

Follow-up to #991, and the develop side of #1008. Customer e-mails and passwords were not handled the same way in every step:

  • The storefront validators checked an e-mail or username as typed and in the current store. The commands store it trimmed and lowercased, on an account that has its own store.
  • A password change finds its account by e-mail, even when the caller already holds the account. An e-mail can change between the two steps, and with per-store identity it can belong to several stores.

Solution

  • SubAccountEditValidator and CustomerInfoValidator check the normalized value in the store of the edited account, also when a case-only repair applies.
  • ICustomerManagerService gets ChangePassword(Customer, PasswordFormat, string), which changes the password of that account without an e-mail lookup. The existing method resolves the e-mail and calls it. Both write only the password fields through a new ICustomerService.UpdateCustomerPassword, so a copy the caller loaded earlier cannot undo other edits.
  • The sub-account edit, the customer's own password change and the SCIM credentials reset use the new method.

Password recovery and the admin panel keep the existing method. The API SetPassword also keeps it, because it receives only an e-mail.

No new resources, settings or migrations.

Breaking changes

ICustomerManagerService and ICustomerService each have a new member, so a custom implementation of either must add it. Callers are not affected. An account whose stored e-mail differs only in case now has that address checked on save, even when changing e-mails is turned off.

Testing

Unit tests pass: Grand.Web.Tests (402), Grand.Business.Authentication.Tests (399), Grand.Business.Customers.Tests (142), Grand.Web.Admin.Tests (1839). The new tests failed before the callers were switched. The full solution and the steps below were not run.

  1. Set HideSubAccountsTab to false, sign in on the storefront, add a sub-account, change its password, and check it can sign in with the new one.
  2. With AllowUsersToChangeEmail on, change its e-mail to one with capitals and surrounding spaces, together with a new password. The address is saved trimmed and lowercased, and the sub-account can sign in. An address that is already taken is rejected in any form.
  3. In My account, change your e-mail the same way, and with usernames on, your username. Taken values are rejected.
  4. In My account, change your own password and sign in with the new one.
  5. With Customer:RegisterCustomersPerStore on and the same e-mail registered in two stores, change the password in one store. The account in the other store keeps its password.
support added 2 commits November 1, 2026 09:19
The storefront validators checked an e-mail or username as typed and in the
current store, while the commands store it trimmed and lowercased on an
account that belongs to its own store. They now check the stored form in the
store of the account being edited, including when a case-only repair applies.

A password change finds its account by e-mail, and with per-store identity
one e-mail can belong to several stores. The customer's own change and the
SCIM credentials reset now use the store of the account they act on, and a
sub-account password is changed only when its e-mail resolves to that account.
These callers already hold the account, but the password change looked it up
again by e-mail, a value that can change between the two steps and is unique
only as far as the application keeps it so. A new overload takes the customer
and the existing method now resolves the e-mail and calls it.

The sub-account edit no longer needs its check that the e-mail leads back to
the account, nor can it skip the remaining edits without telling the owner.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The password-change overload writes a stale customer snapshot, potentially overwriting concurrent account edits or reactivating a deactivated account, so credential updates must be persisted atomically by customer ID.

1 open issue

Review effort: Balanced

/// <param name="customer">Customer</param>
/// <param name="passwordFormat">Password format</param>
/// <param name="newPassword">New password</param>
public virtual async Task ChangePassword(Customer customer, PasswordFormat passwordFormat, string newPassword)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d8d1c71: a new ICustomerService.UpdateCustomerPassword writes only the four password fields in one operation by customer id, keeps the system-account check and the update event. Password history and token rotation are unchanged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A concurrent e-mail change can cause the password update to reset a different account’s password, while guard failures silently discard edits without notifying the user.

2 open issues
What changed in this PR

This follow-up to #991 brings account-store validation and password targeting fixes to develop, alongside the related 2.4.x changes in #1008.

Changes:

  • Validate normalized e-mails and usernames in the edited account’s store, including case-only e-mail repairs.
  • Scope storefront and SCIM password changes to the account’s store, and check sub-account identity before changing its password.
  • Add regression tests for normalization, store scoping, and mismatched password targets.

Review covered the supplied changes and relevant lookup and controller behavior. No builds or tests were run during this review.

File Description
src/​Web/​Grand.Web/​Validators/​Customer/​SubAccountEditValidator.cs Checks normalized e-mails in the sub-account’s store.
src/​Web/​Grand.Web/​Validators/​Customer/​CustomerInfoValidator.cs Uses normalized identity values and the customer’s store.
src/​Web/​Grand.Web/​Controllers/​AccountController.cs Passes the customer’s store to password changes.
src/​Web/​Grand.Web/​Commands/​Handler/​Customers/​SubAccountEditCommandHandler.cs Checks that the password lookup resolves to the edited account.
src/​Tests/​Grand.Web.Tests/​Validators/​Customer/​CustomerValidatorsStoreScopingTests.cs Tests normalization, store scoping, and case-repair conflicts.
src/​Tests/​Grand.Web.Tests/​Controllers/​AccountControllerProviderOnlyTests.cs Verifies store-scoped password changes.
src/​Tests/​Grand.Web.Tests/​Commands/​Customers/​SubAccountEditCommandHandlerTests.cs Tests rejection of a mismatched password target.
src/​Tests/​Grand.Business.Authentication.Tests/​Services/​Scim/​ScimAccountLockTests.cs Tests account-store scoping for credential resets.
src/​Business/​Grand.Business.Authentication/​Services/​Scim/​ScimAccountLock.cs Passes the account’s store to credential resets.

Review effort: Balanced

The password change saved the whole customer, including its e-mail, username,
store and active flag. With a copy the caller loaded earlier in the request,
that could undo an edit made in the meantime, such as a deactivation. A
dedicated update now writes the four password fields in one operation, and
still refuses a system account and raises the update event.
@KrzysztofPajak KrzysztofPajak changed the title Check customer e-mails and change passwords in the account's store Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants