diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 8698fa36..58c9e8f3 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -49,231 +49,6 @@ describe('Parse.User testing', () => { }); }); - it('sends verification email if email verification is enabled', done => { - var emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => Promise.resolve() - } - setServerConfiguration({ - serverURL: 'http://localhost:8378/1', - appId: 'test', - appName: 'unused', - javascriptKey: 'test', - dotNetKey: 'windows', - clientKey: 'client', - restAPIKey: 'rest', - masterKey: 'test', - collectionPrefix: 'test_', - fileKey: 'test', - verifyUserEmails: true, - emailAdapter: emailAdapter, - }); - spyOn(emailAdapter, 'sendVerificationEmail'); - var user = new Parse.User(); - user.setPassword("asdf"); - user.setUsername("zxcv"); - user.signUp(null, { - success: function(user) { - expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); - user.fetch() - .then(() => { - expect(user.get('emailVerified')).toEqual(false); - done(); - }); - }, - error: function(userAgain, error) { - fail('Failed to save user'); - done(); - } - }); - }); - - it('does not send verification email if email verification is disabled', done => { - var emailAdapter = { - sendVerificationEmail: () => Promise.resolve(), - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => Promise.resolve() - } - setServerConfiguration({ - serverURL: 'http://localhost:8378/1', - appId: 'test', - appName: 'unused', - javascriptKey: 'test', - dotNetKey: 'windows', - clientKey: 'client', - restAPIKey: 'rest', - masterKey: 'test', - collectionPrefix: 'test_', - fileKey: 'test', - verifyUserEmails: false, - emailAdapter: emailAdapter, - }); - spyOn(emailAdapter, 'sendVerificationEmail'); - var user = new Parse.User(); - user.setPassword("asdf"); - user.setUsername("zxcv"); - user.signUp(null, { - success: function(user) { - user.fetch() - .then(() => { - expect(emailAdapter.sendVerificationEmail.calls.count()).toEqual(0); - expect(user.get('emailVerified')).toEqual(undefined); - done(); - }); - }, - error: function(userAgain, error) { - fail('Failed to save user'); - done(); - } - }); - }); - - it('receives the app name and user in the adapter', done => { - var emailAdapter = { - sendVerificationEmail: options => { - expect(options.appName).toEqual('emailing app'); - expect(options.user.get('email')).toEqual('user@parse.com'); - done(); - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {} - } - setServerConfiguration({ - serverURL: 'http://localhost:8378/1', - appId: 'test', - appName: 'emailing app', - javascriptKey: 'test', - dotNetKey: 'windows', - clientKey: 'client', - restAPIKey: 'rest', - masterKey: 'test', - collectionPrefix: 'test_', - fileKey: 'test', - verifyUserEmails: true, - emailAdapter: emailAdapter, - }); - var user = new Parse.User(); - user.setPassword("asdf"); - user.setUsername("zxcv"); - user.set('email', 'user@parse.com'); - user.signUp(null, { - success: () => {}, - error: function(userAgain, error) { - fail('Failed to save user'); - done(); - } - }); - }) - - it('when you click the link in the email it sets emailVerified to true and redirects you', done => { - var user = new Parse.User(); - var emailAdapter = { - sendVerificationEmail: options => { - request.get(options.link, { - followRedirect: false, - }, (error, response, body) => { - expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=zxcv'); - user.fetch() - .then(() => { - expect(user.get('emailVerified')).toEqual(true); - done(); - }, (err) => { - console.error(err); - fail("this should not fail"); - done(); - }); - }); - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {} - } - setServerConfiguration({ - serverURL: 'http://localhost:8378/1', - appId: 'test', - appName: 'emailing app', - javascriptKey: 'test', - dotNetKey: 'windows', - clientKey: 'client', - restAPIKey: 'rest', - masterKey: 'test', - collectionPrefix: 'test_', - fileKey: 'test', - verifyUserEmails: true, - emailAdapter: emailAdapter, - }); - user.setPassword("asdf"); - user.setUsername("zxcv"); - user.set('email', 'user@parse.com'); - user.signUp(); - }); - - it('redirects you to invalid link if you try to verify email incorrecly', done => { - request.get('http://localhost:8378/1/apps/test/verify_email', { - followRedirect: false, - }, (error, response, body) => { - expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); - done() - }); - }); - - it('redirects you to invalid link if you try to validate a nonexistant users email', done => { - request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', { - followRedirect: false, - }, (error, response, body) => { - expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); - done(); - }); - }); - - it('does not update email verified if you use an invalid token', done => { - var user = new Parse.User(); - var emailAdapter = { - sendVerificationEmail: options => { - request.get('http://localhost:8378/1/apps/test/verify_email?token=invalid&username=zxcv', { - followRedirect: false, - }, (error, response, body) => { - expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); - user.fetch() - .then(() => { - expect(user.get('emailVerified')).toEqual(false); - done(); - }); - }); - }, - sendPasswordResetEmail: () => Promise.resolve(), - sendMail: () => {} - } - setServerConfiguration({ - serverURL: 'http://localhost:8378/1', - appId: 'test', - appName: 'emailing app', - javascriptKey: 'test', - dotNetKey: 'windows', - clientKey: 'client', - restAPIKey: 'rest', - masterKey: 'test', - collectionPrefix: 'test_', - fileKey: 'test', - verifyUserEmails: true, - emailAdapter: emailAdapter, - }); - user.setPassword("asdf"); - user.setUsername("zxcv"); - user.set('email', 'user@parse.com'); - user.signUp(null, { - success: () => {}, - error: function(userAgain, error) { - fail('Failed to save user'); - done(); - } - }); - }); - it("user login wrong username", (done) => { Parse.User.signUp("asdf", "zxcv", null, { success: function(user) { diff --git a/spec/PublicAPI.spec.js b/spec/PublicAPI.spec.js index a61537d0..9979c04d 100644 --- a/spec/PublicAPI.spec.js +++ b/spec/PublicAPI.spec.js @@ -12,7 +12,7 @@ describe("public API", () => { }); it("should get choose_password", (done) => { - request('http://localhost:8378/1/apps/choose_password', (err, httpResponse, body) => { + request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => { expect(httpResponse.statusCode).toBe(200); done(); }); diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js new file mode 100644 index 00000000..e5e07b34 --- /dev/null +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -0,0 +1,374 @@ +"use strict"; + +var request = require('request'); + +describe("Email Verification", () => { + it('sends verification email if email verification is enabled', done => { + var emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve() + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'unused', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + spyOn(emailAdapter, 'sendVerificationEmail'); + var user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.signUp(null, { + success: function(user) { + expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); + user.fetch() + .then(() => { + expect(user.get('emailVerified')).toEqual(false); + done(); + }); + }, + error: function(userAgain, error) { + fail('Failed to save user'); + done(); + } + }); + }); + + it('does not send verification email if email verification is disabled', done => { + var emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve() + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'unused', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: false, + emailAdapter: emailAdapter, + }); + spyOn(emailAdapter, 'sendVerificationEmail'); + var user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.signUp(null, { + success: function(user) { + user.fetch() + .then(() => { + expect(emailAdapter.sendVerificationEmail.calls.count()).toEqual(0); + expect(user.get('emailVerified')).toEqual(undefined); + done(); + }); + }, + error: function(userAgain, error) { + fail('Failed to save user'); + done(); + } + }); + }); + + it('receives the app name and user in the adapter', done => { + var emailAdapter = { + sendVerificationEmail: options => { + expect(options.appName).toEqual('emailing app'); + expect(options.user.get('email')).toEqual('user@parse.com'); + done(); + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'emailing app', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + var user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set('email', 'user@parse.com'); + user.signUp(null, { + success: () => {}, + error: function(userAgain, error) { + fail('Failed to save user'); + done(); + } + }); + }) + + it('when you click the link in the email it sets emailVerified to true and redirects you', done => { + var user = new Parse.User(); + var emailAdapter = { + sendVerificationEmail: options => { + request.get(options.link, { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=zxcv'); + user.fetch() + .then(() => { + expect(user.get('emailVerified')).toEqual(true); + done(); + }, (err) => { + console.error(err); + fail("this should not fail"); + done(); + }); + }); + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'emailing app', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set('email', 'user@parse.com'); + user.signUp(); + }); + + it('redirects you to invalid link if you try to verify email incorrecly', done => { + request.get('http://localhost:8378/1/apps/test/verify_email', { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + done() + }); + }); + + it('redirects you to invalid link if you try to validate a nonexistant users email', done => { + request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + done(); + }); + }); + + it('does not update email verified if you use an invalid token', done => { + var user = new Parse.User(); + var emailAdapter = { + sendVerificationEmail: options => { + request.get('http://localhost:8378/1/apps/test/verify_email?token=invalid&username=zxcv', { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + user.fetch() + .then(() => { + expect(user.get('emailVerified')).toEqual(false); + done(); + }); + }); + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'emailing app', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set('email', 'user@parse.com'); + user.signUp(null, { + success: () => {}, + error: function(userAgain, error) { + fail('Failed to save user'); + done(); + } + }); + }); +}); + +describe("Password Reset", () => { + + it('should send a password reset link', done => { + var user = new Parse.User(); + var emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + request.get(options.link, { + followRedirect: false, + }, (error, response, body) => { + if (error) { + console.error(error); + fail("Failed to get the reset link"); + return; + } + expect(response.statusCode).toEqual(302); + var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=[a-zA-Z0-9]+\&id=test\&username=zxcv/; + expect(response.body.match(re)).not.toBe(null); + done(); + }); + }, + sendMail: () => {} + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'emailing app', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set('email', 'user@parse.com'); + user.signUp().then(() => { + Parse.User.requestPasswordReset('user@parse.com', { + error: (err) => { + console.error(err); + fail("Should not fail"); + done(); + } + }); + }); + }); + + it('redirects you to invalid link if you try to request password for a nonexistant users email', done => { + request.get('http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf&username=sadfasga', { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + done(); + }); + }); + + it('should programatically reset password', done => { + var user = new Parse.User(); + var emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + request.get(options.link, { + followRedirect: false, + }, (error, response, body) => { + if (error) { + console.error(error); + fail("Failed to get the reset link"); + return; + } + expect(response.statusCode).toEqual(302); + var re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=zxcv/; + var match = response.body.match(re); + if (!match) { + fail("should have a token"); + done(); + return; + } + var token = match[1]; + + request.post({ + url: "http://localhost:8378/1/apps/test/request_password_reset" , + body: `new_password=hello&token=${token}&username=zxcv`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + followRedirect: false, + }, (error, response, body) => { + if (error) { + console.error(error); + fail("Failed to POST request password reset"); + return; + } + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'); + + Parse.User.logIn("zxcv", "hello").then(function(user){ + done(); + }, (err) => { + console.error(err); + fail("should login with new password"); + done(); + }); + + }); + }); + }, + sendMail: () => {} + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'emailing app', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set('email', 'user@parse.com'); + user.signUp().then(() => { + Parse.User.requestPasswordReset('user@parse.com', { + error: (err) => { + console.error(err); + fail("Should not fail"); + done(); + } + }); + }); + }); + +}) + diff --git a/src/Config.js b/src/Config.js index c3d7317d..12059993 100644 --- a/src/Config.js +++ b/src/Config.js @@ -47,7 +47,7 @@ export class Config { } get choosePasswordURL() { - return `${this.serverURL}/apps/${this.applicationId}/choose_password`; + return `${this.serverURL}/apps/choose_password`; } get requestResetPasswordURL() { diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index e9e0551d..2abd7f49 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -4,6 +4,9 @@ import AdaptableController from './AdaptableController'; import MailAdapter from '../Adapters/Email/MailAdapter'; var DatabaseAdapter = require('../DatabaseAdapter'); +var RestWrite = require('../RestWrite'); +var hash = require('../password').hash; +var Auth = require('../Auth'); export class UserController extends AdaptableController { @@ -35,7 +38,7 @@ export class UserController extends AdaptableController { } - verifyEmail(username, token) { + verifyEmail(username, token, config = this.config) { return new Promise((resolve, reject) => { @@ -45,7 +48,7 @@ export class UserController extends AdaptableController { return; } - var database = this.config.database; + var database = config.database; database.collection('_User').then(coll => { // Need direct database access because verification token is not a parse field @@ -64,45 +67,24 @@ export class UserController extends AdaptableController { }); } - checkResetTokenValidity(username, token) { - var database = this.config.database; + checkResetTokenValidity(username, token, config = this.config) { return new Promise((resolve, reject) => { - database.collection('_User').then(coll => { - // Need direct database access because verification token is not a parse field + return config.database.collection('_User').then(coll => { return coll.findOne({ username: username, - _email_reset_token: token, + _perishable_token: token, }, (err, doc) => { - if (err || !doc.value) { - reject(); + if (err || !doc) { + reject(err); } else { - resolve(); - } - }); - }); - }); - } - - setPasswordResetToken(email) { - var database = this.config.database; - var token = randomString(25); - return new Promise((resolve, reject) => { - database.collection('_User').then(coll => { - // Need direct database access because verification token is not a parse field - return coll.findAndModify({ - email: email, - }, null, {$set: {_email_reset_token: token}}, (err, doc) => { - if (err || !doc.value) { - reject(); - } else { - console.log(doc); - resolve(token); + resolve(doc); } }); }); }); } + sendVerificationEmail(user, config = this.config) { if (!this.shouldVerifyEmails) { return; @@ -119,25 +101,68 @@ export class UserController extends AdaptableController { }); } - sendPasswordResetEmail(user, config = this.config) { + setPasswordResetToken(email, config = this.config) { + var database = config.database; + var token = randomString(25); + return new Promise((resolve, reject) => { + return database.collection('_User').then(coll => { + // Need direct database access because verification token is not a parse field + return coll.findAndModify({ + email: email, + }, null, {$set: {_perishable_token: token}}, (err, doc) => { + if (err || !doc.value) { + console.error(err); + reject(err); + } else { + doc.value._perishable_token = token; + resolve(doc.value); + } + }); + }); + }); + } + + sendPasswordResetEmail(email, config = this.config) { if (!this.adapter) { + throw "Trying to send a reset password but no adapter is set"; + // TODO: No adapter? return; } - const token = encodeURIComponent(user._email_reset_token); - const username = encodeURIComponent(user.username); - - let link = `${config.requestPasswordResetURL}?token=${token}&username=${username}` - this.adapter.sendPasswordResetEmail({ - appName: config.appName, - link: link, - user: inflate('_User', user), + return this.setPasswordResetToken(email).then((user) => { + + const token = encodeURIComponent(user._perishable_token); + const username = encodeURIComponent(user.username); + let link = `${config.requestResetPasswordURL}?token=${token}&username=${username}` + this.adapter.sendPasswordResetEmail({ + appName: config.appName, + link: link, + user: inflate('_User', user), + }); + return Promise.resolve(user); + }, (err) => { + return Promise.reject(err); }); } + + updatePassword(username, token, password, config = this.config) { + return this.checkResetTokenValidity(username, token, config).then(() => { + return updateUserPassword(username, token, password, config); + }); + } sendMail(options) { this.adapter.sendMail(options); } } +// Mark this private +function updateUserPassword(username, token, password, config) { + var write = new RestWrite(config, Auth.master(config), '_User', { + username: username, + _perishable_token: token + }, {password: password, _perishable_token: null }, undefined); + return write.execute(); + } + export default UserController; diff --git a/src/RestWrite.js b/src/RestWrite.js index 66ea69ff..31d8f125 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -832,4 +832,5 @@ RestWrite.prototype.objectId = function() { return this.data.objectId || this.query.objectId; }; +export default RestWrite; module.exports = RestWrite; diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index 40c6180b..78565311 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -21,7 +21,7 @@ export class PublicAPIRouter extends PromiseRouter { } let userController = config.userController; - return userController.verifyEmail(username, token, appId).then( () => { + return userController.verifyEmail(username, token).then( () => { return Promise.resolve({ status: 302, location: `${config.verifyEmailSuccessURL}?username=${username}` @@ -33,7 +33,13 @@ export class PublicAPIRouter extends PromiseRouter { changePassword(req) { return new Promise((resolve, reject) => { - var config = new Config(req.params.appId); + var config = new Config(req.query.id); + if (!config.serverURL) { + return Promise.resolve({ + status: 404, + text: 'Not found.' + }); + } // Should we keep the file in memory or leave like that? fs.readFile(path.resolve(views, "choose_password"), 'utf-8', (err, data) => { if (err) { @@ -47,23 +53,51 @@ export class PublicAPIRouter extends PromiseRouter { }); } - resetPassword(req) { - var { username, token } = req.params; + requestResetPassword(req) { + + var { username, token } = req.query; if (!username || !token) { return this.invalidLink(req); } let config = req.config; - return config.userController.checkResetTokenValidity(username, token).then( () => { + return config.userController.checkResetTokenValidity(username, token).then( (user) => { return Promise.resolve({ status: 302, - location: `${config.choosePasswordURL}?token=${token}&id=${config.applicationId}&username=${username}` + location: `${config.choosePasswordURL}?token=${token}&id=${config.applicationId}&username=${username}&app=${config.appName}` }) }, () => { return this.invalidLink(req); }) } + + resetPassword(req) { + var { + username, + token, + new_password + } = req.body; + + if (!username || !token || !new_password) { + return this.invalidLink(req); + } + + let config = req.config; + return config.userController.updatePassword(username, token, new_password).then((result) => { + return Promise.resolve({ + status: 302, + location: config.passwordResetSuccessURL + }); + }, (err) => { + console.error(err); + return Promise.resolve({ + status: 302, + location: `${config.choosePasswordURL}?token=${token}&id=${config.applicationId}&username=${username}&error=${err}&app=${config.appName}` + }); + }); + + } invalidLink(req) { return Promise.resolve({ @@ -80,13 +114,14 @@ export class PublicAPIRouter extends PromiseRouter { mountRoutes() { this.route('GET','/apps/:appId/verify_email', this.setConfig, req => { return this.verifyEmail(req); }); this.route('GET','/apps/choose_password', req => { return this.changePassword(req); }); - this.route('GET','/apps/:appId/request_password_reset', this.setConfig, req => { return this.resetPassword(req); }); + this.route('POST','/apps/:appId/request_password_reset', this.setConfig, req => { return this.resetPassword(req); }); + this.route('GET','/apps/:appId/request_password_reset', this.setConfig, req => { return this.requestResetPassword(req); }); } expressApp() { var router = express(); router.use("/apps", express.static(public_html)); - router.use(super.expressApp()); + router.use("/", super.expressApp()); return router; } } diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 72d14b3a..2d63d701 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -34,7 +34,7 @@ export class UsersRouter extends ClassesRouter { if (req.config.verifyUserEmails) { // Send email as fire-and-forget once the user makes it into the DB. p.then(() => { - req.config.userController.sendVerificationEmail(req.body, req.config); + req.config.userController.sendVerificationEmail(req.body); }); } return p; @@ -154,17 +154,16 @@ export class UsersRouter extends ClassesRouter { } handleResetRequest(req) { - - let { email } = req.body.email; + let { email } = req.body; if (!email) { - throw "Missing email"; + throw new Parse.Error(Parse.Error.EMAIL_MISSING, "you must provide an email"); } let userController = req.config.userController; return userController.sendPasswordResetEmail(email).then((token) => { return Promise.resolve({ response: {} - }) + }); }, (err) => { throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `no user found with email ${email}`); }); diff --git a/src/index.js b/src/index.js index f4146312..84ab3f55 100644 --- a/src/index.js +++ b/src/index.js @@ -182,7 +182,8 @@ function ParseServer({ })); if (process.env.PARSE_EXPERIMENTAL_EMAIL_VERIFICATION_ENABLED || process.env.TESTING == 1) { - api.use('/', new PublicAPIRouter().expressApp()); + // need the body parser for the password reset + api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp()); } diff --git a/src/transform.js b/src/transform.js index 7ff570c0..8829f394 100644 --- a/src/transform.js +++ b/src/transform.js @@ -45,6 +45,9 @@ export function transformKeyValue(schema, className, restKey, restValue, options case '_email_verify_token': key = "_email_verify_token"; break; + case '_perishable_token': + key = "_perishable_token"; + break; case 'sessionToken': case '_session_token': key = '_session_token';