Skip to content

Set password with token, disable password changes via patch/replace#3350

Merged
bajtos merged 4 commits into
masterfrom
feature/set-password-with-token
Apr 20, 2017
Merged

Set password with token, disable password changes via patch/replace#3350
bajtos merged 4 commits into
masterfrom
feature/set-password-with-token

Conversation

@bajtos

@bajtos bajtos commented Apr 10, 2017

Copy link
Copy Markdown
Member

Description

Add User.setPassword(id, new, cb)

Implement a new method for changing user password with password-reset token but without the old password.

REST API

POST /api/users/reset-password
Authorization: your-password-reset-token-id
Content-Type: application/json

{"newPassword": "new-pass"}

JavaScript API

User.setPassword(userId, newPassword[, cb])
userInstance.setPassword(newPassword[, cb])

Implement more secure password flow

Improve the flow for setting/changing/resetting User password to make
it more secure.

  1. Modify User.resetPassword to create a token scoped to allow
    invocation of a single remote method: User.setPassword.

  2. Scope the method User.setPassword so that regular tokens created
    by User.login are not allowed to execute it.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting restrictResetPasswordTokenScope is set to true.

  1. Changing the password via User.prototype.patchAttributes
    (and similar DAO methods) is no longer allowed. Applications
    must call User.changePassword and ask the user to provide
    the current (old) password.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting rejectPasswordChangesViaPatchOrReplace is set to true.

Related issues

Checklist

  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style
    guide

Comment thread common/models/user.js Outdated
* @promise
*/
User.setPassword = function(userId, newPassword, options, cb) {
assert(!!userId, 'resetToken is a required argument');

@raymondfeng raymondfeng Apr 10, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The argument name should be resetToken instead of userId.

Comment thread common/models/user.js

// Make sure to use the constructor of the (sub)class
// where the method is invoked from (`this` instead of `User`)
this.findById(userId, options, (err, inst) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC, userId should be resetToken.userId, right?

@raymondfeng raymondfeng left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please address my comments.

@bajtos
bajtos force-pushed the feature/set-password-with-token branch from 25d1ce5 to b0de97d Compare April 11, 2017 15:23

@raymondfeng raymondfeng left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@ebarault ebarault 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.

Very nice first PR leveraging new token's access scopes 👍 .
This looks good to me in general, see my comments.

Comment thread common/models/user.js Outdated
ttl: ttl,
scopes: ['reset-password'],
};
user.createAccessToken(params, onTokenCreated);

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.

i find the createAccessToken() method signature confusing with regard to how it's called here: see here in this file

User.prototype.createAccessToken = function(ttl, options, cb) {...}

At least the jsdoc does not refer to ttl in the options argument, neither does it states the ttl argument as optional.

Also this reference to singular scope in method's options seems to be older than the scopes we are referring to here (refers to scope.js) and will confuse people.

Considering that we might want to use this options (i don't see it right now) argument to forward remote context downstream, it adds to the underlying complexity (non explicit magic under the hood) IMO (where is the appId param of options argument handled btw?)

At the minimum it should be addressed in the jsdoc.

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.

Comment thread common/models/user.js Outdated
// to turn off this password-change check

// Verify that only the password is changed and nothing more or less.
const data = ctx.data || ctx.instance;

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.

suggestion: group ctx.data || ctx.instance as a shared data const at the beginning of the hook as it is also used few lines above

Comment thread common/models/user.js Outdated
// The password can be always set when creating a new User instance
return next();
}
const isPasswordChange = 'password' in (ctx.data || ctx.instance);

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.

curiosity : is it more/less efficient than doing a string search?.

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.

What do you mean by string search? Could you paste a code snippet showing what do you have in mind?

Comment thread test/authorization-scopes.test.js Outdated
}

function givenRemoteMethodWithCustomScope() {
// Delete any previosly registered instance of the method "scoped"

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.

typo

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.

I am afraid I don't see the typo, could you please be more specific?

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.

previosly-->previously

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.

Ah, I see it now. Thank you @loay 🙇

Comment thread common/models/user.js Outdated

if (ctx.options.setPassword) {
// This is the option used by `setPassword()` API
// to turn off this password-change check

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.

i find the comment ambiguous
why not something like
"This is the option set by setPassword() API when calling this.patchAttritubes() to change user's password" ?

Comment thread test/user-password.test.js Outdated
});
});

function givenAppWithModernFlow() {

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.

Can we find something more explicit than Modern vs. Legacy? (what if we change again)

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.

I see your point. Do you have any proposal in mind?

Note that the User setting is called legacyPasswordFlow right now. This setting controls whether:

  • POST /api/users/reset-password is scoped to ['reset-password']
  • The token created by POST /api/users/reset is granted ['reset-password'] scope
  • User's "before save" hooks rejects attempts to change the password via patch/replace

I have difficulties coming with a descriptive name that would cover all of that.

@ebarault ebarault Apr 19, 2017

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.

we had a similar discussion a few month ago in another PR

Do you have arguments against naming such options and tests according to versioning and add descriptions in the changes log? (a dedicated page on options is required at this point IMO)
e.g. passwordFlow@2.y.z, passwordFlow@3.6.0 (the version being the first LB version introducing the feature)

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.

Do you have arguments against naming such options and tests according to versioning and add descriptions in the changes log? (a dedicated page on options is required at this point IMO)
e.g. passwordFlow@2.y.z, passwordFlow@3.6.0 (the version being the first LB version introducing the feature)

I am not sure if I understand you correctly. What is the model setting name and what are the values that you are proposing?

In general, I prefer feature flags that describe the behaviour of the application. If we cannot come up with a descriptive flag name for this situation, then I guess the second best option is to introduce two feature flags:

  • scopePasswordResetToken controlling the behaviour of POST /api/users/reset-password and POST /api/users/reset
  • rejectPasswordChangesViaPatchOrReplace or perhaps allowInsecurePasswordUpdates to enable the "before save" hook

Thoughts?

Comment thread test/user.test.js
.set('Authorization', info.accessToken.id)
.expect(200));
});

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.

IIUC these tests are for the legacy call flow.

I don't see the two opposite tests for the new call flow that

  • creates token that prevent patching User with new password
  • creates token that allow calling setPassword
  • creates token that prevent calling other methods than setPassword

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.

these tests are for the legacy call flow.

Yes, I am keeping them here to ensure backward-compatibility is preserved. The test cases you have pointed out are covered by test/user-password.test.js:

creates token that prevent patching User with new password

See

it('denies patching user password', () => {
return patchPassword(regularToken).expect(401);
});

creates token that allow calling setPassword

it('allows resetting user password', () => {
return resetPassword(resetToken).expect(204);
});

creates token that prevent calling other methods than setPassword

it('denies patching user name', () => {
return changeName(resetToken).expect(401);
});
it('denies patching user password', () => {
return patchPassword(resetToken).expect(401);
});
it('denies changing user password', () => {
return changePassword(resetToken).expect(401);
});

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.

ok, thanks for pointing out these tests
to ease further reading, could you add context blocks to segregate tests with "legacy" password flow and tests with "modern" password flow?, as in user-password.test.js

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.

Well, I consider the tests in user.test.js as verifying the "default" password flow. In that light, do you still think I should move them to a context('default password flow') block?

Comment thread test/user-password.test.js Outdated
// test passed
});
});
});

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.

very compact style 👍

Comment thread test/user.integration.js
});

it('injects reset password options from remoting context', function() {
const User = app.models.User;

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.

forwards vs. injects? (we shall decide which term to use)

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.

In the light of the discussion under #3350 (comment), maybe a better test name would be:

it('configures "options" argument to be injected from remoting context'

Thoughts?

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.

i reviewed your other comment, let's keep it simple: i agree with inject for the root call, and forwards for downstream calls

Comment thread test/user.test.js
});
});

it('forwards the "options" argument', () => {

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.

see my previous comment, here it's: forwards

@bajtos bajtos Apr 18, 2017

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.

I am afraid I don't understand. Which previous comment do you mean?

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.

I think I found the other comment: #3350 (comment)

Let me explain why I think we should keep using both "forwards" and "injects":

The test it forwards the "options" argument verifies that the implementation of User.setPassword is forwarding theoptions argument whenever calling other User and DAO methods, to ensure that context can be correctly propagated.

The second test, "it injects reset password options from remoting context" verifies that remoting metadata of setPassword provides correct configuration for the options argument, a configuration instructing loopback/strong-remoting to inject options built from the request context.

In my mind, these two test cases are different, and therefore it makes sense to me to use different verbs to describe them.

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.

👍

@bajtos

bajtos commented Apr 13, 2017

Copy link
Copy Markdown
Member Author

#3350 (comment)

i find the createAccessToken() method signature confusing (...)

Good point 💯

I am proposing the change the signature of createAccessToken in a different way then. Let's keep the currently supported forms:

createAccessToken(ttl, cb)
createAccessToken(ttl, options, cb);
createAccessToken(options, cb);

and add a new form that accepts a data object as the first arg:

createAccessToken(data, options, cb);

thoughts?

@raymondfeng

@bajtos

bajtos commented Apr 13, 2017

Copy link
Copy Markdown
Member Author

@ebarault thanks for your thoughtful review! I have addressed the first two comments, PTAL. I'll work on the remaining comments later. Most likely I won't get to this patch until next week, because Good Friday is a Public Holiday here in CZ.

bajtos added 2 commits April 18, 2017 13:01
Fix the code builing a scoped method to correctly handle the case
when the setup method is called twice and the previously defined
method has to be overriden with new remoting metadata.
@bajtos
bajtos force-pushed the feature/set-password-with-token branch from 745d20a to a5ea90e Compare April 18, 2017 11:02
@bajtos

bajtos commented Apr 18, 2017

Copy link
Copy Markdown
Member Author

@ebarault I addressed all of your review comments, either by changing the relevant code, or by starting a discussion. Could you PTAL again?

@ebarault ebarault 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.

@bajtos : i generally approve your changes. Please see some further comments below.

Options naming convention (modern, legacy, ...) would maybe require further discussion. I let up to you whether you'd like to wait for my feedback or land with a chosen convention of your taste.

Comment thread common/models/user.js Outdated
* or an object with token properties to set (see below).
* @property {Number} [ttl] The requested ttl
* @property {String[]} [scopes] The access scopes granted to the token.
* @param {Object} [properties] Additional options (e.g. the context).

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.

LGTM 👍

regarding @param {Object} [properties] Additional options (e.g. the context).

  • it should be @param {Object} [options] ...

  • we should come up with a final (unique) description for the options object where it's meant to possibly convey remote context options. Maybe something like:
    Additional options including remoting context

Comment thread common/models/user.js Outdated
*
* @param {*} userId Id of the user changing the password
* @param {string} newPassword The new password to use.
* @param {object} [options]

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.

missing description for options object

Comment thread common/models/user.js Outdated
* current password or a password-reset token.
*
* @param {string} newPassword The new password to use.
* @param {object} [options]

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.

missing description for options object

Comment thread common/models/user.js

// We need to modify options passed to patchAttributes, but we don't want
// to modify the original options object passed to us by setPassword caller
options = Object.assign({}, options);

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.

i'm probably missing something here, but as you assign the result of Object.assign({}, options) back to options, i don't see how this does not modify the original options object.

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.

Consider the following code:

const options = { foo: 'bar' };
user.setPassword('pass', options, cb);
console.log(options);

In my proposed implementation, the snippet prints { foo: 'bar' }.

Without the Object.assign call, the snippet would print { foo: 'bar', setPassword: true }.

In other words, the code inside setPassword is changing the value of the options argument (the reference to the options object), but it does not mutate the original object referenced by the initial options value.

I hope my comment have clarified the problem, please do keep asking if there is anything still not clear yet.

Comment thread test/user-password.test.js Outdated
});
});

function givenAppWithModernFlow() {

@ebarault ebarault Apr 19, 2017

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.

we had a similar discussion a few month ago in another PR

Do you have arguments against naming such options and tests according to versioning and add descriptions in the changes log? (a dedicated page on options is required at this point IMO)
e.g. passwordFlow@2.y.z, passwordFlow@3.6.0 (the version being the first LB version introducing the feature)

Comment thread test/user.integration.js
});

it('injects reset password options from remoting context', function() {
const User = app.models.User;

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.

i reviewed your other comment, let's keep it simple: i agree with inject for the root call, and forwards for downstream calls

Comment thread test/user.test.js
});
});

it('forwards the "options" argument', () => {

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.

👍

Comment thread test/user.test.js
.set('Authorization', info.accessToken.id)
.expect(200));
});

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.

ok, thanks for pointing out these tests
to ease further reading, could you add context blocks to segregate tests with "legacy" password flow and tests with "modern" password flow?, as in user-password.test.js

@bajtos

bajtos commented Apr 19, 2017

Copy link
Copy Markdown
Member Author

@ebarault thank you for another review, I think we are getting close :)

AFAICT, there are three outstanding discussion points:

@ebarault

Copy link
Copy Markdown
Contributor

Feature flag naming - #3350 (comment)

my proposal was to version the features such as password flow such as: passwordFlow@3.6.0 : true, and describe the content elsewhere. But one in all i prefer your proposal to split favor feature flags that describe the behaviour of the application. a similar situations we should use several feature flags:

scopePasswordResetToken controlling the behaviour of POST /api/users/reset-password and POST /api/users/reset

what about restrictResetPasswordTokenScope ? at least something with an explicit verb

rejectPasswordChangesViaPatchOrReplace or perhaps allowInsecurePasswordUpdates to enable the "before save" hook

rejectPasswordChangesViaPatchOrReplace 👍

@ebarault

Copy link
Copy Markdown
Contributor

Context blocks in user.test.js - #3350 (comment)

hmm, so when the default behavior comes to be the feature flagged one ("modern password flow") we should review the code in user.test.js to match the correct behavior? I believe i'm not aware enough of the logic that leads to gather some tests in user.test.js, compared to dedicating other files to specific features. My only concern is about code maintenance when going to next major release. I leave this to you to choose the approach that minimize the code maintenance IYO.

@ebarault

Copy link
Copy Markdown
Contributor

Object.assign confusion - #3350 (comment)

I see your point, but i'd need to do some tests on my side to be 100% convinced that assigning the options var inside the function block does not modify the original options arg.
So as your code is nevertheless 100% safe, let's close this discussion and keep your proposal.

@bajtos

bajtos commented Apr 19, 2017

Copy link
Copy Markdown
Member Author

Feature flag naming

what about restrictResetPasswordTokenScope
rejectPasswordChangesViaPatchOrReplace

Done in 3dc012d

Context blocks in user.test.js

hmm, so when the default behavior comes to be the feature flagged one ("modern password flow") we should review the code in user.test.js to match the correct behavior?

Yes.

I believe i'm not aware enough of the logic that leads to gather some tests in user.test.js, compared to dedicating other files to specific features.

Well, I don't think there is much logic here. I am keeping existing tests to ensure full backwards compatibility is preserved.

My only concern is about code maintenance when going to next major release.

I think we have two options:

  • Clean the tests right now. Unless I temporarily change the default value of these flags, I may miss some tests that would break when defaults are changed in the future. When moving the tests around, one may accidentally change their behaviour, which would go against the goal of verifying backwards compatibility.
  • Defer test cleanup until we are changing the defaults, even though it will make such change more costly. OTOH if we never change the default, then we saved time.

I leave this to you to choose the approach that minimize the code maintenance IYO.

Let's leave test cleanup for later then.

Object.assign confusion

So as your code is nevertheless 100% safe, let's close this discussion and keep your proposal.

Sounds good 👍


@ebarault Could you please review the latest changes in 3dc012d? I think that's the last step before this patch can be landed.

@loay loay 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.

LGTM

@loay

loay commented Apr 19, 2017

Copy link
Copy Markdown
Contributor

@slnode test please

@ebarault

ebarault commented Apr 19, 2017

Copy link
Copy Markdown
Contributor

OTOH if we never change the default, then we saved time

oh gosh, let's hope we'll not end there !! ;-)

@ebarault ebarault 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.

👍 your changes looks good to me, let's land it ! 🎉

could you please open a new issue/PR so that we don't forget to add these 2 new options in the new project templates ?

@loay

loay commented Apr 19, 2017

Copy link
Copy Markdown
Contributor

@slnode test please

@superkhau superkhau 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.

LGTM with some minor nits.

var loopback = require('../');
const Promise = require('bluebird');
var request = require('supertest');
const waitForEvent = require('./helpers/wait-for-event');

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.

Why vars? Seems like we can use all consts.

Comment thread test/user-password.test.js Outdated
}
});

context('rejectPasswordChangesViaPatchOrReplace', () => {

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.

normal sentence for consistency with context names below? 'reject password changes via patchOrReplace'?

Comment thread test/user-password.test.js Outdated

let app, User, testUser, regularToken, resetToken;

context('restrictResetPasswordTokenScope', () => {

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.

'restrict reset password token scope'?

Comment thread test/user-password.test.js Outdated
}
});

context('no limitations', () => {

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.

with no restrictions/limitations? (consistent wording with line 100)

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.

The idea is that this context disables all new feature flags. I am going to use context('all feature flags disabled')

Comment thread test/user-password.test.js Outdated
context('no limitations', () => {
beforeEach(givenAppWithNoRestrictions);

context('with regular access token', () => {

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.

using regular access tokens

Comment thread test/user.integration.js Outdated
});
});

it('resets user\'s password', function() {

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.

resets the

Comment thread test/user.integration.js Outdated
.then(isMatch => expect(isMatch, 'user has new password').to.be.true());
});

it('rejects unauthenticated reset password request', function() {

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.

rejects unauthenticated password reset requests

Comment thread test/user.integration.js Outdated
.expect(401);
});

it('injects reset password options from remoting context', function() {

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.

uses password reset options provided by the remoting context

Comment thread test/user.integration.js Outdated
const User = app.models.User;
const credentials = {email: 'inject-reset@example.com', password: 'pass'};

let injectedOptions;

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.

replace injected with remotingContextOptions? and below

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.

Let's use observedOptions.

Comment thread test/user.test.js Outdated
});
});

it('fails with 401 for unknown user', () => {

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.

unknown users

@loay

loay commented Apr 19, 2017

Copy link
Copy Markdown
Contributor

linting issues should be fixed now on gateway-director-bluemix@develop .. the next build should be a success.

@bajtos

bajtos commented Apr 20, 2017

Copy link
Copy Markdown
Member Author

@ebarault

could you please open a new issue/PR so that we don't forget to add these 2 new options in the new project templates ?

I have already done that, see #3349

@bajtos

bajtos commented Apr 20, 2017

Copy link
Copy Markdown
Member Author

@loay

linting issues should be fixed now on gateway-director-bluemix@develop .. the next build should be a success.

Thanks! 🙇

@bajtos

bajtos commented Apr 20, 2017

Copy link
Copy Markdown
Member Author

@superkhau thank you for the detailed review, I addressed your comments in b1aeb8c.

Implement a new method for changing user password with password-reset
token but without the old password.

REST API

    POST /api/users/reset-password
    Authorization: your-password-reset-token-id
    Content-Type: application/json

    {"newPassword": "new-pass"}

JavaScript API

    User.setPassword(userId, newPassword[, cb])
    userInstance.setPassword(newPassword[, cb])

Note: the new REST endpoint is not protected by scopes yet, therefore
any valid access token can invoke it (similarly to how any valid access
token can change the password via PATCH /api/users/:id).
@bajtos
bajtos force-pushed the feature/set-password-with-token branch from b1aeb8c to 662b288 Compare April 20, 2017 08:20
Improve the flow for setting/changing/resetting User password to make
it more secure.

 1. Modify `User.resetPassword` to create a token scoped to allow
    invocation of a single remote method: `User.setPassword`.

 2. Scope the method `User.setPassword` so that regular tokens created
    by `User.login` are not allowed to execute it.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting `restrictResetPasswordTokenScope` is set to
`true`.

 3. Changing the password via `User.prototype.patchAttributes`
    (and similar DAO methods) is no longer allowed. Applications
    must call `User.changePassword` and ask the user to provide
    the current (old) password.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting `rejectPasswordChangesViaPatchOrReplace` is set
to `true`.
@bajtos
bajtos force-pushed the feature/set-password-with-token branch from 662b288 to c5ca2e1 Compare April 20, 2017 08:22
@bajtos bajtos added this to the Sprint 34 milestone Apr 20, 2017
@bajtos

bajtos commented Apr 20, 2017

Copy link
Copy Markdown
Member Author

[cis-jenkins] downstream: loopback-getting-started@master — Failed! (c5ca2e1)

That was a (temporary?) CI issue:

FATAL: java.io.IOException: Unexpected termination of the channel
java.io.EOFException
	at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2353)

@bajtos
bajtos merged commit b96605c into master Apr 20, 2017
@bajtos
bajtos deleted the feature/set-password-with-token branch April 20, 2017 08:47
@bajtos bajtos removed the #review label Apr 20, 2017
@bajtos

bajtos commented Apr 20, 2017

Copy link
Copy Markdown
Member Author

Landed. Thank you everybody for keeping the fast pace, allowing me to iterate on your feedback and get this complex change landed in less than two weeks 🎉

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants