diff --git a/spec/PasswordResetLink.spec.js b/spec/PasswordResetLink.spec.js new file mode 100644 index 0000000000..8edee660a8 --- /dev/null +++ b/spec/PasswordResetLink.spec.js @@ -0,0 +1,82 @@ +'use strict'; + +describe('Password Reset Link', () => { + it('should generate a password reset link with actual appId, not {appId} or undefined', async () => { + let emailLinkReceived = null; + + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + emailLinkReceived = options.link; + // Check that the link contains the actual appId ('test'), not the literal string '{appId}' + expect(options.link).toBeDefined(); + expect(options.link).not.toContain('{appId}'); + expect(options.link).toContain('/test/'); + return Promise.resolve(); + }, + sendMail: () => Promise.resolve(), + }; + + await reconfigureServer({ + appId: 'test', + appName: 'Test App', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + const user = new Parse.User(); + user.setPassword('password'); + user.setUsername('testuser'); + user.set('email', 'test@example.com'); + await user.signUp(); + + await Parse.User.requestPasswordReset('test@example.com'); + + // Verify the link was generated correctly + expect(emailLinkReceived).not.toBeNull(); + expect(emailLinkReceived).toMatch(/\/test\/request_password_reset\?token=/); + }); + + it('should render password reset page with actual appId in form action', async () => { + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + + await reconfigureServer({ + appId: 'test', + appName: 'Test App', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + const user = new Parse.User(); + user.setPassword('password'); + user.setUsername('testuser2'); + user.set('email', 'test2@example.com'); + await user.signUp(); + + // Trigger password reset to get a token + await Parse.User.requestPasswordReset('test2@example.com'); + + // Find the user to get the reset token + const results = await new Parse.Query('_User') + .equalTo('email', 'test2@example.com') + .find({ useMasterKey: true }); + const resetToken = results[0].get('_perishable_token'); + + // Request the password reset page + const response = await request({ + url: `http://localhost:8378/1/apps/test/request_password_reset?token=${resetToken}`, + followRedirects: false, + }); + + expect(response.status).toBe(200); + // The form action should contain the actual appId 'test', not '{appId}' + expect(response.text).toContain('/apps/test/request_password_reset'); + expect(response.text).not.toContain('{appId}'); + }); +}); diff --git a/src/Config.js b/src/Config.js index 54e3cc5ca4..323009ffdd 100644 --- a/src/Config.js +++ b/src/Config.js @@ -49,7 +49,7 @@ export class Config { Object.keys(cacheInfo).forEach(key => { if (key == 'databaseController') { config.database = new DatabaseController(cacheInfo.databaseController.adapter, config); - } else { + } else if (key !== 'applicationId') { config[key] = cacheInfo[key]; } }); @@ -789,7 +789,7 @@ export class Config { } get requestResetPasswordURL() { - return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/request_password_reset`; + return `${this.publicServerURL}/${this.pagesEndpoint}/${this.appId}/request_password_reset`; } get passwordResetSuccessURL() { @@ -804,7 +804,7 @@ export class Config { } get verifyEmailURL() { - return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/verify_email`; + return `${this.publicServerURL}/${this.pagesEndpoint}/${this.appId}/verify_email`; } async loadMasterKey() { diff --git a/src/Routers/PagesRouter.js b/src/Routers/PagesRouter.js index 74beec770c..906bc62a49 100644 --- a/src/Routers/PagesRouter.js +++ b/src/Routers/PagesRouter.js @@ -160,7 +160,7 @@ export class PagesRouter extends PromiseRouter { () => { const params = { [pageParams.token]: token, - [pageParams.appId]: config.applicationId, + [pageParams.appId]: config.appId, [pageParams.appName]: config.appName, }; return this.goToPage(req, pages.passwordReset, params); @@ -225,7 +225,7 @@ export class PagesRouter extends PromiseRouter { ? {} : { [pageParams.token]: token, - [pageParams.appId]: config.applicationId, + [pageParams.appId]: config.appId, [pageParams.error]: result.err, [pageParams.appName]: config.appName, };