Adds reset password logic

This commit is contained in:
Florent Vilmart
2016-02-27 14:46:29 -05:00
parent f3bb2c99e0
commit 91d9724182
10 changed files with 493 additions and 280 deletions

View File

@@ -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) => { it("user login wrong username", (done) => {
Parse.User.signUp("asdf", "zxcv", null, { Parse.User.signUp("asdf", "zxcv", null, {
success: function(user) { success: function(user) {

View File

@@ -12,7 +12,7 @@ describe("public API", () => {
}); });
it("should get choose_password", (done) => { 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); expect(httpResponse.statusCode).toBe(200);
done(); done();
}); });

View File

@@ -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();
}
});
});
});
})

View File

@@ -47,7 +47,7 @@ export class Config {
} }
get choosePasswordURL() { get choosePasswordURL() {
return `${this.serverURL}/apps/${this.applicationId}/choose_password`; return `${this.serverURL}/apps/choose_password`;
} }
get requestResetPasswordURL() { get requestResetPasswordURL() {

View File

@@ -4,6 +4,9 @@ import AdaptableController from './AdaptableController';
import MailAdapter from '../Adapters/Email/MailAdapter'; import MailAdapter from '../Adapters/Email/MailAdapter';
var DatabaseAdapter = require('../DatabaseAdapter'); var DatabaseAdapter = require('../DatabaseAdapter');
var RestWrite = require('../RestWrite');
var hash = require('../password').hash;
var Auth = require('../Auth');
export class UserController extends AdaptableController { 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) => { return new Promise((resolve, reject) => {
@@ -45,7 +48,7 @@ export class UserController extends AdaptableController {
return; return;
} }
var database = this.config.database; var database = config.database;
database.collection('_User').then(coll => { database.collection('_User').then(coll => {
// Need direct database access because verification token is not a parse field // Need direct database access because verification token is not a parse field
@@ -64,44 +67,23 @@ export class UserController extends AdaptableController {
}); });
} }
checkResetTokenValidity(username, token) { checkResetTokenValidity(username, token, config = this.config) {
var database = this.config.database;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
database.collection('_User').then(coll => { return config.database.collection('_User').then(coll => {
// Need direct database access because verification token is not a parse field
return coll.findOne({ return coll.findOne({
username: username, username: username,
_email_reset_token: token, _perishable_token: token,
}, (err, doc) => { }, (err, doc) => {
if (err || !doc.value) { if (err || !doc) {
reject(); reject(err);
} else { } else {
resolve(); resolve(doc);
} }
}); });
}); });
}); });
} }
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);
}
});
});
});
}
sendVerificationEmail(user, config = this.config) { sendVerificationEmail(user, config = this.config) {
if (!this.shouldVerifyEmails) { if (!this.shouldVerifyEmails) {
@@ -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) { if (!this.adapter) {
throw "Trying to send a reset password but no adapter is set";
// TODO: No adapter?
return; return;
} }
const token = encodeURIComponent(user._email_reset_token); return this.setPasswordResetToken(email).then((user) => {
const username = encodeURIComponent(user.username);
let link = `${config.requestPasswordResetURL}?token=${token}&username=${username}` const token = encodeURIComponent(user._perishable_token);
this.adapter.sendPasswordResetEmail({ const username = encodeURIComponent(user.username);
appName: config.appName, let link = `${config.requestResetPasswordURL}?token=${token}&username=${username}`
link: link, this.adapter.sendPasswordResetEmail({
user: inflate('_User', user), 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) { sendMail(options) {
this.adapter.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; export default UserController;

View File

@@ -832,4 +832,5 @@ RestWrite.prototype.objectId = function() {
return this.data.objectId || this.query.objectId; return this.data.objectId || this.query.objectId;
}; };
export default RestWrite;
module.exports = RestWrite; module.exports = RestWrite;

View File

@@ -21,7 +21,7 @@ export class PublicAPIRouter extends PromiseRouter {
} }
let userController = config.userController; let userController = config.userController;
return userController.verifyEmail(username, token, appId).then( () => { return userController.verifyEmail(username, token).then( () => {
return Promise.resolve({ return Promise.resolve({
status: 302, status: 302,
location: `${config.verifyEmailSuccessURL}?username=${username}` location: `${config.verifyEmailSuccessURL}?username=${username}`
@@ -33,7 +33,13 @@ export class PublicAPIRouter extends PromiseRouter {
changePassword(req) { changePassword(req) {
return new Promise((resolve, reject) => { 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? // Should we keep the file in memory or leave like that?
fs.readFile(path.resolve(views, "choose_password"), 'utf-8', (err, data) => { fs.readFile(path.resolve(views, "choose_password"), 'utf-8', (err, data) => {
if (err) { if (err) {
@@ -47,24 +53,52 @@ export class PublicAPIRouter extends PromiseRouter {
}); });
} }
resetPassword(req) { requestResetPassword(req) {
var { username, token } = req.params;
var { username, token } = req.query;
if (!username || !token) { if (!username || !token) {
return this.invalidLink(req); return this.invalidLink(req);
} }
let config = req.config; let config = req.config;
return config.userController.checkResetTokenValidity(username, token).then( () => { return config.userController.checkResetTokenValidity(username, token).then( (user) => {
return Promise.resolve({ return Promise.resolve({
status: 302, 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); 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) { invalidLink(req) {
return Promise.resolve({ return Promise.resolve({
status: 302, status: 302,
@@ -80,13 +114,14 @@ export class PublicAPIRouter extends PromiseRouter {
mountRoutes() { mountRoutes() {
this.route('GET','/apps/:appId/verify_email', this.setConfig, req => { return this.verifyEmail(req); }); 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/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() { expressApp() {
var router = express(); var router = express();
router.use("/apps", express.static(public_html)); router.use("/apps", express.static(public_html));
router.use(super.expressApp()); router.use("/", super.expressApp());
return router; return router;
} }
} }

View File

@@ -34,7 +34,7 @@ export class UsersRouter extends ClassesRouter {
if (req.config.verifyUserEmails) { if (req.config.verifyUserEmails) {
// Send email as fire-and-forget once the user makes it into the DB. // Send email as fire-and-forget once the user makes it into the DB.
p.then(() => { p.then(() => {
req.config.userController.sendVerificationEmail(req.body, req.config); req.config.userController.sendVerificationEmail(req.body);
}); });
} }
return p; return p;
@@ -154,17 +154,16 @@ export class UsersRouter extends ClassesRouter {
} }
handleResetRequest(req) { handleResetRequest(req) {
let { email } = req.body;
let { email } = req.body.email;
if (!email) { if (!email) {
throw "Missing email"; throw new Parse.Error(Parse.Error.EMAIL_MISSING, "you must provide an email");
} }
let userController = req.config.userController; let userController = req.config.userController;
return userController.sendPasswordResetEmail(email).then((token) => { return userController.sendPasswordResetEmail(email).then((token) => {
return Promise.resolve({ return Promise.resolve({
response: {} response: {}
}) });
}, (err) => { }, (err) => {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `no user found with email ${email}`); throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `no user found with email ${email}`);
}); });

View File

@@ -182,7 +182,8 @@ function ParseServer({
})); }));
if (process.env.PARSE_EXPERIMENTAL_EMAIL_VERIFICATION_ENABLED || process.env.TESTING == 1) { 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());
} }

View File

@@ -45,6 +45,9 @@ export function transformKeyValue(schema, className, restKey, restValue, options
case '_email_verify_token': case '_email_verify_token':
key = "_email_verify_token"; key = "_email_verify_token";
break; break;
case '_perishable_token':
key = "_perishable_token";
break;
case 'sessionToken': case 'sessionToken':
case '_session_token': case '_session_token':
key = '_session_token'; key = '_session_token';