Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
We need an endpoint to allow users to trigger the reset password process.
Proposed resolution
Create a router method within Drupal\user\Controller\UserAuthenticationController
Remaining tasks
Create the endpoint.Create tests.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#48 | rpc_endpoint_to_reset-2847708-48.patch | 10.84 KB | larowlan |
#48 | rpc_endpoint_to_reset-2847708-48.interdiff.txt | 4.81 KB | larowlan |
#43 | 2847708-43.patch | 10.68 KB | tedbow |
#40 | 2847708-40.patch | 11.56 KB | tedbow |
#40 | interdiff-2847708-38-40.txt | 1.25 KB | tedbow |
Comments
Comment #2
shadcn CreditAttribution: shadcn at Chapter Three commentedOK let's get this going.
Comment #3
shadcn CreditAttribution: shadcn at Chapter Three commentedComment #4
dawehnerOne thing I a wondering about ... should there be some kind of flood protection on this route as well?
Comment #5
shadcn CreditAttribution: shadcn at Chapter Three commented@dawehner I'd say yes. We'll also need it in
\Drupal\user\Form\UserPasswordForm
.Comment #6
Wim LeersWhy should this not be added to
UserLoginHttpTest
?We're duplicating these helpers from
UserLoginHttpTest
.Comment #7
shadcn CreditAttribution: shadcn at Chapter Three commented@Wim Leers we could move the tests to
UserLoginHttpTest
.I was not sure if password reset would be qualify under user login.
I'll make the change.
Comment #8
shadcn CreditAttribution: shadcn at Chapter Three commentedUpdated patch as per #6.
Comment #10
Wim LeersRetesting.
Comment #11
dawehner@Wim Leers
Do you have an opinion regarding some flooding protection?
Comment #12
Wim LeersI was going to say "do the same as the form", assuming that the form had flood protection. But apparently it does not!
\Drupal\user\Form\UserPasswordForm::buildForm()
nor
\Drupal\user\Controller\UserController::getResetPassForm()
contain flood protection.Therefore I don't think we need to do it here either. Nothing is preventing you from mass-triggering the password reset form today. So then there's no need to do so in the RPC endpoint either. If we ever add it, we should add it to both. But that should not block this issue.
Comment #13
Wim LeersThanks, this is looking great already!
s/credential/credentials/
This looks strange…
Pointless comment.
Let's use exactly the same message as
\Drupal\user\Controller\UserAuthenticationController::login()
.This all matches what's in
\Drupal\user\Form\UserPasswordForm::submitForm()
, great!The message seems rather pointless. Especially translating it seems pointless. I think a simple 200 response is sufficient.
These are all pointless comments TBH… they don't add any value. The test code is very clear already without them.
:D
Comment #14
shadcn CreditAttribution: shadcn at Chapter Three commentedThanks @Wim Leers. I'll make the changes.
Do we need a separate issue to eventually add flood protection to password reset?
Comment #15
Wim LeersYes, that'd be splendid :)
Comment #16
shadcn CreditAttribution: shadcn at Chapter Three commentedUpdated patch as per #13.
\Drupal\user\Form\UserPasswordForm::validateForm
is doing. I reversed what we had to match it better. i.e use email if provided, fallback to username.Comment #17
Wim LeersJust fixing a few nits. Other than that, I think this is good to go! Thanks for rerolling so quickly!
Comment #18
Wim LeersComment #19
alexpottThis is an odd construction - I think we should load by mail or by name depending on what is provided. The form only has a single name field but here we have $credentials['name'] and $credentials['mail']. And the name will accept either a name of an email but mail will only accept a mail. And what happens when both are provided? Maybe we should entirely duplicate the form functionality and just call the input
name_or_mail
- @Wim Leers - maybe you have some thoughts on this?I don't think we should be saying "Sorry" here - it's kind of like the please thing. I think the message should be We could improve it and say which of the provided way is wrong but that might be unnecessary.
Comment #20
dawehnerOne thing I am wondering, could these information be sent via query parameters instead? Do they have to be part of the request body? Maybe i'm overthinking things a lot, but for me its a bit like you reset the account of a specific user
Comment #21
shadcn CreditAttribution: shadcn at Chapter Three commentedHere's an updated patch as per #19 that loads by name or by mail depending on what is provided.
I've also updated the error message.
@alexpott, the patch in #8 had
$name_or_mail
like\Drupal\user\Form\UserPasswordForm::validateForm
.Comment #22
Wim Leers#19.1: I shared your concerns, but this is literally what the existing code in
\Drupal\user\Form\UserPasswordForm::validateForm()
was doing, so I let it pass.#20: I don't see what the advantage of that would be? This must be a POST request anyway. If we the need arises, we could add that capability in the future. For now, I think this makes sense.
Concerns in #19 are addressed.
Comment #23
dawehnerWell, maybe its more concrete as you would kinda act on the resource with name "X".
Comment #24
ruloweb CreditAttribution: ruloweb at Anexus commentedThis is awesome! attached is a patch which adds the language code in the _user_mail_notify function, just as \Drupal\user\Form\UserPasswordForm::submitForm() does.
Also, this is the first step on the Password Reset workflow, what about processing the token and allow the user edit endpoint to bypass the Current password field? Sooner I will upload a patch with this proposal that I used for one project.
Comment #25
shadcn CreditAttribution: shadcn at Chapter Three commentedThanks for your work on this @ruloweb. +1
Comment #27
Wim LeersComment #29
Wim LeersSigh, still random CI fails.
Comment #31
Wim LeersComment #33
Wim LeersComment #34
Gábor HojtsyDiscussing with @alexpott, this same logic is used in UserPasswordForm but there the user initated the request I believe so using the page language is fine, so since the user could interact in that language, they could receive mail in that language. But here, there is no guarantee the endpoint was requested in a language the user can interact in, so the logic should ensure the user preferred language is used.
Comment #35
Wim LeersSo you're saying that the REST request should specify the language?
Comment #36
Gábor HojtsyI think pulling the user preferred language from the user profile would be the most solid solution here. Since the REST request may or may not be initiated by the user themselves, I think that would be the only stable way to always send email in a language the user should understand. AccountInterface::getPreferredLangcode() gets you the right langcode to use.
Comment #37
Wim LeersAlright, that makes this issue actionable :)
Comment #38
tedbowOk adding get langcode for email from AccountInterface::getPreferredLangcode() as per #36
So looking at \Drupal\Core\Session\UserSession::getPreferredLangcode it looks like if there is not a preferred language the langcode will be from
\Drupal::languageManager()->getDefaultLanguage()->getId()
where has before we were doing
$this->languageManager()->getCurrentLanguage()->getId();
That is probably fine but just want to point that out.
This is the same logic that is already in UserLoginTest so I made a new trait UserResetEmailTestTrait that both now use.
Comment #40
tedbowWhat happened here?
Fixing and removing unused use statement.
Comment #41
Wim Leers#40: hah, I wondered the same!
Thanks for bringing this one over the finish line!
Comment #43
tedbowRe-roll because #992540: Nothing clears the "5 failed login attempts" security message when a user resets their own password was reverted.
Will comment there about new trait.
Comment #44
Wim LeersManually compared #40 and #43. The only change is the removal of the hunk to update
core/modules/user/src/Tests/UserLoginTest.php
, because the revert of #992540: Nothing clears the "5 failed login attempts" security message when a user resets their own password makes that change unnecessary. Other than that, it's identical.Therefore, back to RTBC.
Comment #45
Wim LeersComment #46
larowlanWhilst this feels like username/email enumeration - it already exists in the existing form-based approach and is consistent with security team policy - see https://www.drupal.org/node/1004778
Disclosure of usernames and user ids is not considered a weakness
Are we avoiding using a
@dataProvider
here because of setup cost? Feels like that is what we actually want...going to ask others their thoughtsComment #47
Wim Leers\Drupal\Tests\user\Functional\UserLoginHttpTest::testLogin()
. Refactoring this seems like an out-of-scope nice-to-have to me.Comment #48
larowlanI respectfully disagree, see #1847540: [META] Clean up comment module tests and decouple from node for example of where technical debt in tests is hard to clean up later.
I'll meet you half-way, I'll refactor it here before it goes in (see attached patch) if you open a novice follow-up to refactor
\Drupal\Tests\user\Functional\UserLoginHttpTest::testLogin
, linking to this comment for an example? Should be plenty of takers for a simple task.Comment #49
Wim LeersThanks for that! Done: #2886198: Refactor \UserLoginHttpTest::testLogin() to use a protected method and remove the nested loops.
Comment #51
catchCommitted/pushed to 8.4.x, thanks!
Comment #53
hanoii@ruloweb have you progressed with the other endpoint to complete the actual password reset through rest?
Comment #54
hanoiiI just added #2904451: Allow to change password through RPC endpoints through the reset password workflow to follow up discussion on what was missing here.