New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: allow retries of accept login request #2914
fix: allow retries of accept login request #2914
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy new year @jonkjenn :)
Thank you for working on this issue and your PR! I have two asks of you:
- Would it be possible to make your changes against the feat: implement Ory Hydra v2.0 #2796 branch (in particular PR feat: introduce the concept of a flow in the persistence layer #2836) as we are changing the database layout.
- As described in issue Login request not fully marked as used until browser navigates to /oauth2/auth #2824 we originally wanted to return 429 with an URL the developer can use for further propagation. The reason for not allowing double submit here is that we want to prevent conflicting submissions and potential race conditions. It's been quite some time that we introduced this and I don't quite remember the reasoning. But I think we need a sound overview of the potential attack vectors of allowing double submission here before we can change the behavior to just override the previous request or ignore it.
- Can you introduce this feature to both handling consent and login?
persistence/sql/persister_consent.go
Outdated
if err != nil { | ||
return sqlcon.HandleError(err) | ||
} | ||
|
||
if !found { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would only work in the case of an exact retry. It will be confusing for developers who expect the second call to override the first one, which is why we wanted to return 429 with an URL the developers can use for next action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've refactored it to the 2.x branch. You now get the 409 redirect_to. If it's conceptually ok I can add it to the consent step also.
I would maybe have chosen to use a specific error instead of x.ErrConflict
so can change that if the approach seems ok otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like consent is supported already #2914 (comment). Do you see the need for further work to ensure that enabling this at all is a secure solution @aeneasr? I.e. is it not realistic to get a change like this merged as more of a quick fix?
Codecov Report
@@ Coverage Diff @@
## v2.x #2914 +/- ##
=======================================
Coverage ? 76.32%
=======================================
Files ? 107
Lines ? 7426
Branches ? 0
=======================================
Hits ? 5668
Misses ? 1349
Partials ? 409 Continue to review full report at Codecov.
|
a1469e4
to
c0f3843
Compare
b734c5e
to
534d128
Compare
It already seems to work to retry the accept consent requests. I tested v1.10.6 towards our project and this branch by modifying the Cypress e2e tests with duplicate requests and also some unit test but those gets quite convoluted quickly when you get to the consent step since there's so much setup and options. |
460079b
to
50bec3f
Compare
This looks great and as far as I can tell, would make the behavior consistent with Login Requests when the token is attempted to be reused? I'm not sure there's a security risk associated if it means that via the redirect_uri, the browser can reattempt the auth flow. Presumably the token would be different once it arrives at the consent screen again as part of that auth flow. Would love to see this in the next release! |
@jonkjenn is this ready for review? :) |
I changed to returning 200 on retries to stay consistent with how accept consent works also it feels more correct not to use an error for this case and it's simpler but could easily switch back to 409. The new tests shows more clearly how retries of both accept login and accept consent works. |
Thank you! I think the tests are awesome! I would like to follow up with a comment made in this issue: #2824 (comment) which would basically mean that we return an error like 409/410 but include a
which would help the developer implementing this flow to identify the URL they need to return the user to. What do you think about that solution? In the issue thread, it appears that most agree :) I think it would be relatively straight forward to modify this PR to follow that behavior as all the tests and everything is already in place :) |
@aeneasr So 409 for /accept/login and leave /accept/consent as 200? |
409 for both - or is consent currently returning 200 on conflict? Sorry I didn’t have time yet to refresh my knowledge on the issue… |
No
Yes (409 for both, but more importantly, redirect_to parameter in both the 409 error responses) Let me try and clarify: Both Login and Consent flows can experience a '410 Gone' on In both cases, the 410 Gone error has a But a 409 Conflict on the Consent flow ( In other words, in both Login and Consent flows, a PUT with the challenge token throws a 409 but does not have a We are hoping for the same The 409 Conflict on the Consent form is easy to trigger, assuming you are not 'skipping' the consent:
... and there is no
(Obviously if you optionally 'save' the consent the first time, you tend not to hit this problem again (at least until any such consent is revoked by the user), because the redirects happen automatically and the Consent form thus never has to be submitted again.) Ultimately the most important thing from an implementor's perspective is to be able to gracefully recover by consuming the I hope that clarifies things! |
Edited my comment above to note that it's named |
@mig5 You're talking about the case where the user has visited the the All my tests indicate that before the user visits the
Yes it is currently returning 200 with the same |
Right - I am saying, when the 'multiple' request fails on 409 (PUT), it would be nice to have the same |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grantzvolsky can you maybe take a look, since you wrote the state machine? Does the code change make sense in your view?
if f.State == FlowStateLoginUnused { | ||
return nil | ||
} else if f.State != FlowStateLoginInitialized { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to use the same logic as in the consent flow?
if f.State != FlowStateConsentInitialized && f.State != FlowStateConsentUnused && f.State != FlowStateConsentError {
return errors.Errorf("invalid flow state: expected %d/%d/%d, got %d", FlowStateConsentInitialized, FlowStateConsentUnused, FlowStateConsentError, f.State)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second look I am still confused though. The 409 error does not happen in this logic here, it comes from the database or explicitly returning x.ErrConflict
.
I then executed the tests included in this PR without the changes above, and the tests pass, indicating that the change has not the desired effect and the code on this branch already behaves the way we want it to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if f.State != FlowStateLoginInitialized && f.State != FlowStateLoginUnused {
return errors.Errorf("invalid flow state: expected %d/%d, got %d", FlowStateLoginInitialized, FlowStateLoginUnused, f.State)
}
I don't understand exactly what you're saying is working or not but the suggestion of changing it to be more similar to the HandleConsentRequest
function makes sense to me. You seem to get some extra consistency validation compared to just jumping out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for getting back :) I ran the tests you provided but changed the code in flow.go back to what it was before your changes, and the tests you wrote still pass, making me wonder whether we need this change in flow.go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm they fail locally for me but I could be doing something wrong. I created #3085 to attempt to run tests in the pipeline but if there is a PR pipeline I didn't manage to trigger it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad @jonkjenn ! You're right :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonkjenn cc @aeneasr you are right that there are some opportunities for more consistency checks. When I introduced Flow, I initially tried to validate everything. But it turned out that some tests depend on imperfections in the validation, and that updating them would be a lot of work, so I changed my strategy and just made the minimum necessary changes.
if f.State == FlowStateLoginUnused { | ||
return nil | ||
} else if f.State != FlowStateLoginInitialized { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second look I am still confused though. The 409 error does not happen in this logic here, it comes from the database or explicitly returning x.ErrConflict
.
I then executed the tests included in this PR without the changes above, and the tests pass, indicating that the change has not the desired effect and the code on this branch already behaves the way we want it to?
Thank you all for the explanations! As per my review, it appears that the code is already behaving as expected in the test cases as they seem to pass without the changes to |
e4c89e4
to
022fef3
Compare
Oops didn't notice your PR |
Sorry about that @jonkjenn - I should have linked it here! I basically used all of your code but restructured the logic a bit in the handler :) Getting the tests to pass now and then it's good for merge! Thank you for the hard work and sorry for the long turn-around! |
Related issue(s)
#2824
Checklist
introduces a new feature.
contributing code guidelines.
vulnerability. If this pull request addresses a security. vulnerability, I
confirm that I got green light (please contact
security@ory.sh) from the maintainers to push
the changes.
works.
Further Comments