Skip to content

Commit

Permalink
feat: Add Parse Server option resetPasswordSuccessOnInvalidEmail to…
Browse files Browse the repository at this point in the history
… choose success or error response on password reset with invalid email (parse-community#7551)
  • Loading branch information
dblythy authored Feb 24, 2023
1 parent 5477848 commit e5d610e
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 16 deletions.
39 changes: 39 additions & 0 deletions spec/ValidationAndPasswordsReset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1082,4 +1082,43 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
done();
});
});

it('should throw on an invalid reset password', async () => {
await reconfigureServer({
appName: 'coolapp',
publicServerURL: 'http://localhost:1337/1',
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
passwordPolicy: {
resetPasswordSuccessOnInvalidEmail: false,
},
});

await expectAsync(Parse.User.requestPasswordReset('test@example.com')).toBeRejectedWith(
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'A user with that email does not exist.')
);
});

it('validate resetPasswordSuccessonInvalidEmail', async () => {
const invalidValues = [[], {}, 1, 'string'];
for (const value of invalidValues) {
await expectAsync(
reconfigureServer({
appName: 'coolapp',
publicServerURL: 'http://localhost:1337/1',
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: 'parse@example.com',
apiKey: 'k',
domain: 'd',
}),
passwordPolicy: {
resetPasswordSuccessOnInvalidEmail: value,
},
})
).toBeRejectedWith('resetPasswordSuccessOnInvalidEmail must be a boolean value');
}
});
});
7 changes: 7 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,13 @@ export class Config {
if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) {
throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration';
}

if (
passwordPolicy.resetPasswordSuccessOnInvalidEmail &&
typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean'
) {
throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value';
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,13 @@ module.exports.PasswordPolicyOptions = {
'Set the number of previous password that will not be allowed to be set as new password. If the option is not set or set to `0`, no previous passwords will be considered.<br><br>Valid values are >= `0` and <= `20`.<br>Default is `0`.',
action: parsers.numberParser('maxPasswordHistory'),
},
resetPasswordSuccessOnInvalidEmail: {
env: 'PARSE_SERVER_PASSWORD_POLICY_RESET_PASSWORD_SUCCESS_ON_INVALID_EMAIL',
help:
'Set to `true` if a request to reset the password should return a success response even if the provided email address is invalid, or `false` if the request should return an error response if the email address is invalid.<br><br>Default is `true`.',
action: parsers.booleanParser,
default: true,
},
resetTokenReuseIfValid: {
env: 'PARSE_SERVER_PASSWORD_POLICY_RESET_TOKEN_REUSE_IF_VALID',
help:
Expand Down
1 change: 1 addition & 0 deletions src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,11 @@ export interface PasswordPolicyOptions {
Default is `false`.
:DEFAULT: false */
resetTokenReuseIfValid: ?boolean;
/* Set to `true` if a request to reset the password should return a success response even if the provided email address is invalid, or `false` if the request should return an error response if the email address is invalid.
<br><br>
Default is `true`.
:DEFAULT: true */
resetPasswordSuccessOnInvalidEmail: ?boolean;
}

export interface FileUploadOptions {
Expand Down
30 changes: 14 additions & 16 deletions src/Routers/UsersRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ export class UsersRouter extends ClassesRouter {
}
}

handleResetRequest(req) {
async handleResetRequest(req) {
this._throwOnBadEmailConfig(req);

const { email } = req.body;
Expand All @@ -428,24 +428,22 @@ export class UsersRouter extends ClassesRouter {
);
}
const userController = req.config.userController;
return userController.sendPasswordResetEmail(email).then(
() => {
return Promise.resolve({
response: {},
});
},
err => {
if (err.code === Parse.Error.OBJECT_NOT_FOUND) {
// Return success so that this endpoint can't
// be used to enumerate valid emails
return Promise.resolve({
try {
await userController.sendPasswordResetEmail(email);
return {
response: {},
};
} catch (err) {
if (err.code === Parse.Error.OBJECT_NOT_FOUND) {
if (req.config.passwordPolicy?.resetPasswordSuccessOnInvalidEmail ?? true) {
return {
response: {},
});
} else {
throw err;
};
}
err.message = `A user with that email does not exist.`;
}
);
throw err;
}
}

handleVerificationEmailRequest(req) {
Expand Down

0 comments on commit e5d610e

Please sign in to comment.