-
Notifications
You must be signed in to change notification settings - Fork 78
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
[PASSWORD]: reset and forgot WIP #1231
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.
Per some discussion outside this PR, we're going to limit the scope to unauthenticated users who forgot their password. This means there won't be anything in the user's settings, and everything can work without the authenticated route mixin.
app/routes/password/reset.js
Outdated
method: 'POST', | ||
data: { | ||
password, | ||
passwordConfirmation |
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.
Do we need to send the confirmation, as well, or can we just use that on the client-side? I don't believe we have a concept on the server of a confirmation.
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.
@joshsmith so on the backend we do have a check validate_confirmation
check
https://github.com/code-corps/code-corps-api/blob/develop/web/models/user.ex#L101
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.
Oh, I missed that this was added. My mistake. Is it added to the user
model in Ember here, too?
<div class="container"> | ||
<form> | ||
|
||
{{input name="email" autocapitalize="off" type="text" placeholder='Your email' value=email key-down="keyDown"}} |
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.
Above this we can have a p
that says:
Enter your email and we'll send you a link to reset your password
If the user's authenticated, this could even be auto-populated with the user's email!
Don't think keyDown
is needed here.
{{input name="email" autocapitalize="off" type="text" placeholder='Your email' value=email key-down="keyDown"}} | ||
|
||
<div class="input-group"> | ||
<input class="button default {{if hasError "has-error"}}" name="forgot-password" type="submit" value='Forgot Password' {{action forgotPassword email}} /> |
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 this or the text input
have the error?
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 think so. If the ajax request hit a 500 or something...
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'm asking which one would have it. I think the button having it is a little weird, but the text having it makes more sense.
<form> | ||
{{input autofocus="true" class="has-progress" name="password" type="password" placeholder='Enter new password' autocomplete="off" value=password}} | ||
|
||
{{input class="has-progress" name="password" type="password" placeholder='Confirm password' autocomplete="off" value=passwordConfirmation}} |
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 wouldn't do placeholder
on either of these.
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 should probably have a label
of "Confirm password".
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'm guessing this applies to forgot pwd as well?
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.
Yeah, I don't think passwords should typically have a placeholder since it's a little confusing UX.
@@ -0,0 +1,15 @@ | |||
<div class="container"> | |||
<form> | |||
{{input autofocus="true" class="has-progress" name="password" type="password" placeholder='Enter new password' autocomplete="off" value=password}} |
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 should probably have a label
of "Password".
{{input class="has-progress" name="password" type="password" placeholder='Confirm password' autocomplete="off" value=passwordConfirmation}} | ||
|
||
<div class="input-group"> | ||
<input class="button default {{if hasError "has-error"}}" name="reset-password" type="submit" value='Reset Password' {{action resetPassword password passwordConfirmation}} /> |
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 change value
to "Change password".
@@ -0,0 +1,3 @@ | |||
{{password/reset-password | |||
resetPassword=(route-action "resetPassword") |
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 think route-action
's are less testable in component tests, though you can correct me if I'm wrong on this. Also fairly certain that you don't need it here if the action is on the route, and similarly wonder if it shouldn't be in a controller.
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.
So I believe route-action is for discoverability. If I change to action, it errors. I can create a controller for this action if you would like.
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 is at a route level, so no need to generate a controller just for the sake of not using route-action
, I think. The component is just as easily testable this way as it would be with a controller, but the boilerplate is reduced.
Also, thank you for that input, @snewcomer. I was in a mental place where I wasn't sure how route actions are useful exactly, since, in my experience, using them in nested components makes everything harder to test.
This example is where using a route action is useful for reducing boilerplate with no (to me) apparent negatives.
@joshsmith I'm guessing a |
@snewcomer I think actually if we can do it just below the password field, that might be ideal. I'd like to have a "Don't have an account? Sign up." below the button on Sign In, and a "Have an account? Log in." below the button on Sign Up. |
error: null, | ||
|
||
/** | ||
* @property resetPasswordTask |
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.
@begedin Could you review this file? Was wondering what you thought of it as it follows a bit diff pattern than I have seen so far. Also, how should I handle errors here? This displays the error as you mentioned in the slack a while back. But have no way of getting rid of 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.
Overall, it looks nice and clean. There isn't anything that seriously clashes with our usual approach.
@@ -1,3 +1,5 @@ | |||
import Ember from 'ember'; | |||
const { RSVP } = Ember; | |||
import { moduleForComponent, test } from 'ember-qunit'; | |||
import hbs from 'htmlbars-inline-precompile'; | |||
|
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.
Here is the associated component test.
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.
@joshsmith forgot password for your review. Once this is reviewed and g2g, will cleanup reset password w/ page objects and such and checkoff items in #1125
@@ -9,6 +9,9 @@ | |||
<div class="input-group"> | |||
{{input name="password" id="password" placeholder="Password" type="password" value=password}} | |||
</div> | |||
{{#link-to 'password.forgot' classNames="t-forgot-password"}} |
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.
@joshsmith this forgot-password link could use some styling when you get a chance.
@@ -1,13 +1,13 @@ | |||
<div class="container"> | |||
<form> | |||
|
|||
<p>Enter your email and we'll send you a link to reset your password</p> | |||
<p data-test-id="forgot-password-header">Enter your email and we'll send you a link to reset your password</p> |
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.
data test ids if you want to use them to target for tests. Thoughts?
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.
We've been discussing the usage of https://github.com/simplabs/ember-test-selectors in the past, but I'm nut sure where we're with it now. The addon makes it so these test-only attributes are not present in the final build.
I think, let's not introduce it now, but what you could do is create an issue where we can discuss it and, if we opt into it, create a bunch of other issues. It's a tasks that would be very suitable for newbie volunteer.
*/ | ||
resetPasswordTask: task(function* (password, passwordConfirmation) { | ||
try { | ||
yield get(this, 'resetPassword')(password, passwordConfirmation); |
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.
Will get(this, 'resetPassword')
actually return the function here? Isn't it defined under this.actions.resetPassword
?
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.
Also, this is where I would clear the error. Simply do a set(this, 'error', null)
before the yield
.
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.
Yeah to it is passed in as closure action, so just need to get it off the props and call that function. This way also allows me to patch that action in the integration test.
*/ | ||
resetPasswordTask: task(function* (password, passwordConfirmation) { | ||
try { | ||
yield get(this, 'resetPassword')(password, passwordConfirmation); |
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.
Yeah to it is passed in as closure action, so just need to get it off the props and call that function. This way also allows me to patch that action in the integration test.
app/routes/password/reset.js
Outdated
password, | ||
passwordConfirmation | ||
} | ||
}); |
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.
Need to handle success/error cases still.
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 love your comment on the merging of page objects. Otherwise looks great. Nicely written and well-tested.
tests/pages/password.js
Outdated
form, | ||
resetPasswordForm, | ||
errorFormatter | ||
}); |
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 believe a page object should match with a route 1 to 1. Otherwise, we risk confusing people. Is there any particular reason why you chose to merge the two password pages?
Split up, we would not have visitReset
and visitForgot
. The two pages would simply have a visit
, a form
and an errorFormatter
.
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.
Yeah I see your point. Well the components are based on the components and the password page b/c of the parent password route. Also because I have an acceptance/password-test
.
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.
@begedin ^^
@joshsmith all checks have passed. Probably a good idea to style some pieces here. I can tackle, but didn't know if you wanted to do that. |
mirage/config.js
Outdated
let passwordConfirmation = params.split('&')[1].split('=')[1]; | ||
let [first, second] = params.split('&'); | ||
let password = first.split('=')[1]; | ||
let passwordConfirmation = second.split('=')[1]; |
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.
There should be a this._getAttrsForFormRequest()
which returns a POJO containing the form data, doing this job for you. It's undocumented, but it is supported by mirage.
However, you are using an arrow function, so you do not have access to this
.
I believe something like
this.post('/password/reset', function() {
let { password, passwordConfirmation } = this._getAttrsForFormRequest();
// continue with the rest of your code here
));
ought to do the trick.
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.
Awesome trick!
@joshsmith fyi this is failing only on phantom. That is why I put in PR #1253 |
app/routes/password/reset.js
Outdated
|
||
ajax: service(), | ||
session: service(), | ||
|
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.
One thing I need here is a model hook that extracts the token from params
and used to send up with the POST request.
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.
To get the same functionality you proposed by changing the route in API, you could do something like:
queryParams: {
token: { refreshModel: true }
}
If you add that code into the route, the params
hash in the model hook will contain a token
key.
The standard way to specify these query parameters which are not part of the dynamic route URL would be to specify them in the controller:
export default Controller.extend({
queryParams: ['token']
});
but the way I described above allows us to skip unnecessarily defining a controller file.
@joshsmith @begedin So I tried this exact same code out on a simple app I just spun up and it works. Just need the email created part. |
d6b23d0
to
c335d4d
Compare
fa5d7a6
to
21f22fc
Compare
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.
Awesome! No real comments. I def forgot about the reset password url reflecting the token_id.
app/routes/password/reset.js
Outdated
@@ -24,10 +24,10 @@ export default Route.extend({ | |||
data: { | |||
token: get(this, 'controller.token'), | |||
password, | |||
passwordConfirmation | |||
"password-confirmation": passwordConfirmation |
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.
Curious about this change. Is this a stylistic thing? Just asking for future ref.
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.
It's because most of the keys in JSON API are dasherized by default. There's some lack of consistency in some places at present that are not standard CRUD (like here), but would like to bring at least that consistency to bear here, even if the resource itself isn't a JSON API resource.
tests/acceptance/password-test.js
Outdated
|
||
andThen(() => { | ||
assert.equal(currentURL(), '/password/reset'); | ||
assert.equal(currentURL(), '/password/reset?token=abc123'); |
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.
ah yep, I forgot about those changes.
tests/acceptance/password-test.js
Outdated
@@ -53,7 +59,9 @@ test('visiting /password/forgot', function(assert) { | |||
assert.equal(currentURL(), '/password/forgot'); | |||
}); | |||
|
|||
passwordPage.forgotPasswordForm.sendForgotPasswordSuccessfully('admin@gmail.com'); | |||
andThen(() => { |
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.
surprised this has to be wrapped in an andThen
.
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.
It's because the Mirage response is a promise and we don't know when it will return.
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 great. Merging as soon as build passes. Thanks for all the hard work on this @snewcomer! 👍
References
Progress on: #1126 #1125