From 28d1a8afe4a843baff0b70e75dff0c0182fecd48 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 29 Feb 2016 20:51:13 -0500 Subject: [PATCH] Sends 404 when parseServerURL is not set on public pages - throws when verifyEmail = true && publicServerURL not set --- spec/PublicAPI.spec.js | 56 ++++++++++++++++++-- spec/ValidationAndPasswordsReset.spec.js | 66 ++++++++++++++++++++++++ spec/helper.js | 1 + spec/index.spec.js | 5 ++ src/Config.js | 28 ++++------ src/Routers/PublicAPIRouter.js | 66 +++++++++++++++++------- src/index.js | 2 +- src/middlewares.js | 3 ++ 8 files changed, 186 insertions(+), 41 deletions(-) diff --git a/spec/PublicAPI.spec.js b/spec/PublicAPI.spec.js index 9979c04d..008d544a 100644 --- a/spec/PublicAPI.spec.js +++ b/spec/PublicAPI.spec.js @@ -1,9 +1,23 @@ var request = require('request'); - describe("public API", () => { - + beforeEach(done => { + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'unused', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + publicServerURL: 'http://localhost:8378/1' + }); + done(); + }) it("should get invalid_link.html", (done) => { request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => { expect(httpResponse.statusCode).toBe(200); @@ -31,6 +45,42 @@ describe("public API", () => { done(); }); }); +}); + +describe("public API without publicServerURL", () => { + beforeEach(done => { + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'unused', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + }); + done(); + }) + it("should get 404 on verify_email", (done) => { + request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => { + expect(httpResponse.statusCode).toBe(404); + done(); + }); + }); + it("should get 404 choose_password", (done) => { + request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => { + expect(httpResponse.statusCode).toBe(404); + done(); + }); + }); -}) \ No newline at end of file + it("should get 404 on request_password_reset", (done) => { + request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => { + expect(httpResponse.statusCode).toBe(404); + done(); + }); + }); +}); diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 91f7ddce..6ac874cd 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -56,6 +56,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); spyOn(emailAdapter, 'sendVerificationEmail'); var user = new Parse.User(); @@ -97,6 +98,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); spyOn(emailAdapter, 'sendVerificationEmail'); var user = new Parse.User(); @@ -137,6 +139,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); spyOn(emailAdapter, 'sendVerificationEmail'); var user = new Parse.User(); @@ -196,6 +199,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); var user = new Parse.User(); user.setPassword("asdf"); @@ -284,6 +288,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); var user = new Parse.User(); user.setPassword("asdf"); @@ -334,6 +339,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); user.setPassword("asdf"); user.setUsername("zxcv"); @@ -342,6 +348,25 @@ describe("Email Verification", () => { }); it('redirects you to invalid link if you try to verify email incorrecly', done => { + 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: { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + }, + publicServerURL: "http://localhost:8378/1" + }); request.get('http://localhost:8378/1/apps/test/verify_email', { followRedirect: false, }, (error, response, body) => { @@ -352,6 +377,25 @@ describe("Email Verification", () => { }); it('redirects you to invalid link if you try to validate a nonexistant users email', done => { + 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: { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + }, + publicServerURL: "http://localhost:8378/1" + }); request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', { followRedirect: false, }, (error, response, body) => { @@ -393,6 +437,7 @@ describe("Email Verification", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); user.setPassword("asdf"); user.setUsername("zxcv"); @@ -443,6 +488,7 @@ describe("Password Reset", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); user.setPassword("asdf"); user.setUsername("zxcv"); @@ -459,6 +505,25 @@ describe("Password Reset", () => { }); it('redirects you to invalid link if you try to request password for a nonexistant users email', done => { + 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: { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + }, + publicServerURL: "http://localhost:8378/1" + }); request.get('http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf&username=sadfasga', { followRedirect: false, }, (error, response, body) => { @@ -533,6 +598,7 @@ describe("Password Reset", () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" }); user.setPassword("asdf"); user.setUsername("zxcv"); diff --git a/spec/helper.js b/spec/helper.js index 92231393..e2daa6ed 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -250,3 +250,4 @@ global.arrayContains = arrayContains; global.jequal = jequal; global.range = range; global.setServerConfiguration = setServerConfiguration; +global.defaultConfiguration = defaultConfiguration; diff --git a/spec/index.spec.js b/spec/index.spec.js index 005b9c76..e3e2cb0b 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -56,6 +56,7 @@ describe('server', () => { apiKey: 'k', domain: 'd', }), + publicServerURL: 'http://localhost:8378/1' }); done(); }); @@ -80,6 +81,7 @@ describe('server', () => { domain: 'd', } }, + publicServerURL: 'http://localhost:8378/1' }); done(); }); @@ -104,6 +106,7 @@ describe('server', () => { domain: 'd', } }, + publicServerURL: 'http://localhost:8378/1' }); done(); }); @@ -122,6 +125,7 @@ describe('server', () => { fileKey: 'test', verifyUserEmails: true, emailAdapter: './Email/SimpleMailgunAdapter', + publicServerURL: 'http://localhost:8378/1' })).toThrow('SimpleMailgunAdapter requires an API Key and domain.'); done(); }); @@ -145,6 +149,7 @@ describe('server', () => { domain: 'd', } }, + publicServerURL: 'http://localhost:8378/1' })).toThrow('SimpleMailgunAdapter requires an API Key and domain.'); done(); }); diff --git a/src/Config.js b/src/Config.js index ae656011..8042d6db 100644 --- a/src/Config.js +++ b/src/Config.js @@ -50,44 +50,34 @@ export class Config { if (typeof appName !== 'string') { throw 'An app name is required when using email verification.'; } - if (!process.env.TESTING && typeof publicServerURL !== 'string') { - if (process.env.NODE_ENV === 'production') { - throw 'A public server url is required when using email verification.'; - } else { - console.warn(""); - console.warn("You should set publicServerURL to serve the public pages"); - console.warn(""); - } + if (typeof publicServerURL !== 'string') { + throw 'A public server url is required when using email verification.'; } } } - - get linksServerURL() { - return this.publicServerURL || this.serverURL; - } - + get invalidLinkURL() { - return this.customPages.invalidLink || `${this.linksServerURL}/apps/invalid_link.html`; + return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`; } get verifyEmailSuccessURL() { - return this.customPages.verifyEmailSuccess || `${this.linksServerURL}/apps/verify_email_success.html`; + return this.customPages.verifyEmailSuccess || `${this.publicServerURL}/apps/verify_email_success.html`; } get choosePasswordURL() { - return this.customPages.choosePassword || `${this.linksServerURL}/apps/choose_password`; + return this.customPages.choosePassword || `${this.publicServerURL}/apps/choose_password`; } get requestResetPasswordURL() { - return `${this.linksServerURL}/apps/${this.applicationId}/request_password_reset`; + return `${this.publicServerURL}/apps/${this.applicationId}/request_password_reset`; } get passwordResetSuccessURL() { - return this.customPages.passwordResetSuccess || `${this.linksServerURL}/apps/password_reset_success.html`; + return this.customPages.passwordResetSuccess || `${this.publicServerURL}/apps/password_reset_success.html`; } get verifyEmailURL() { - return `${this.linksServerURL}/apps/${this.applicationId}/verify_email`; + return `${this.publicServerURL}/apps/${this.applicationId}/verify_email`; } }; diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index 78565311..017caef3 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -11,10 +11,13 @@ let views = path.resolve(__dirname, '../../views'); export class PublicAPIRouter extends PromiseRouter { verifyEmail(req) { - var token = req.query.token; - var username = req.query.username; - var appId = req.params.appId; - var config = new Config(appId); + let { token, username }= req.query; + let appId = req.params.appId; + let config = new Config(appId); + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } if (!token || !username) { return this.invalidLink(req); @@ -33,9 +36,9 @@ export class PublicAPIRouter extends PromiseRouter { changePassword(req) { return new Promise((resolve, reject) => { - var config = new Config(req.query.id); - if (!config.serverURL) { - return Promise.resolve({ + let config = new Config(req.query.id); + if (!config.publicServerURL) { + return resolve({ status: 404, text: 'Not found.' }); @@ -45,7 +48,7 @@ export class PublicAPIRouter extends PromiseRouter { if (err) { return reject(err); } - data = data.replace("PARSE_SERVER_URL", `'${config.serverURL}'`); + data = data.replace("PARSE_SERVER_URL", `'${config.publicServerURL}'`); resolve({ text: data }) @@ -55,13 +58,18 @@ export class PublicAPIRouter extends PromiseRouter { requestResetPassword(req) { - var { username, token } = req.query; + let config = req.config; + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + let { username, token } = req.query; if (!username || !token) { return this.invalidLink(req); } - let config = req.config; return config.userController.checkResetTokenValidity(username, token).then( (user) => { return Promise.resolve({ status: 302, @@ -73,7 +81,14 @@ export class PublicAPIRouter extends PromiseRouter { } resetPassword(req) { - var { + + let config = req.config; + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + let { username, token, new_password @@ -83,14 +98,12 @@ export class PublicAPIRouter extends PromiseRouter { 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}` @@ -106,20 +119,37 @@ export class PublicAPIRouter extends PromiseRouter { }); } + missingPublicServerURL() { + return Promise.resolve({ + text: 'Not found.', + status: 404 + }); + } + setConfig(req) { req.config = new Config(req.params.appId); return Promise.resolve(); } 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('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); }); + this.route('GET','/apps/:appId/verify_email', + req => { this.setConfig(req) }, + req => { return this.verifyEmail(req); }); + + this.route('GET','/apps/choose_password', + req => { return this.changePassword(req); }); + + this.route('POST','/apps/:appId/request_password_reset', + req => { this.setConfig(req) }, + req => { return this.resetPassword(req); }); + + this.route('GET','/apps/:appId/request_password_reset', + req => { this.setConfig(req) }, + req => { return this.requestResetPassword(req); }); } expressApp() { - var router = express(); + let router = express(); router.use("/apps", express.static(public_html)); router.use("/", super.expressApp()); return router; diff --git a/src/index.js b/src/index.js index 3eebb483..4ee5d140 100644 --- a/src/index.js +++ b/src/index.js @@ -182,7 +182,7 @@ function ParseServer({ maxUploadSize: maxUploadSize })); - api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp()); + api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp()); // TODO: separate this from the regular ParseServer object if (process.env.TESTING == 1) { diff --git a/src/middlewares.js b/src/middlewares.js index b9a8d6ec..8489cda0 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -174,6 +174,9 @@ var handleParseErrors = function(err, req, res, next) { res.status(httpStatus); res.json({code: err.code, error: err.message}); + } else if (err.status && err.message) { + res.status(err.status); + res.json({error: err.message}); } else { console.log('Uncaught internal server error.', err, err.stack); res.status(500);