diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 8d4d7e1a7f..e80e86d782 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -14,7 +14,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | DEPPS8 | Login with expired 3rd party authentication token defaults to `false` | [#7079](https://github.com/parse-community/parse-server/pull/7079) | 5.3.0 (2022) | 7.0.0 (2024) | removed | - | | DEPPS9 | Rename LiveQuery `fields` option to `keys` | [#8389](https://github.com/parse-community/parse-server/issues/8389) | 6.0.0 (2023) | 7.0.0 (2024) | removed | - | | DEPPS10 | Encode `Parse.Object` in Cloud Function and remove option `encodeParseObjectInCloudFunction` | [#8634](https://github.com/parse-community/parse-server/issues/8634) | 6.2.0 (2023) | 9.0.0 (2026) | removed | - | -| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | deprecated | - | +| DEPPS11 | Replace `PublicAPIRouter` with `PagesRouter` | [#7625](https://github.com/parse-community/parse-server/issues/7625) | 8.0.0 (2025) | 9.0.0 (2026) | removed | - | | DEPPS12 | Database option `allowPublicExplain` will default to `true` | [#7519](https://github.com/parse-community/parse-server/issues/7519) | 8.5.0 (2025) | 9.0.0 (2026) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." diff --git a/Dockerfile b/Dockerfile index f51759e941..bda315c7d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ COPY --from=build /tmp/lib lib COPY package*.json ./ COPY bin bin -COPY public_html public_html +COPY public public COPY views views RUN mkdir -p logs && chown -R node: logs diff --git a/public_html/invalid_link.html b/public_html/invalid_link.html deleted file mode 100644 index b19044e52f..0000000000 --- a/public_html/invalid_link.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Invalid Link - - - - -
-

Invalid Link

-
- - diff --git a/public_html/invalid_verification_link.html b/public_html/invalid_verification_link.html deleted file mode 100644 index 063ac354f4..0000000000 --- a/public_html/invalid_verification_link.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - Invalid Link - - - - - -
-

Invalid Verification Link

-
- - -
-
- - diff --git a/public_html/link_send_fail.html b/public_html/link_send_fail.html deleted file mode 100644 index 7f817a2cc4..0000000000 --- a/public_html/link_send_fail.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Invalid Link - - - - -
-

No link sent. User not found or email already verified

-
- - diff --git a/public_html/link_send_success.html b/public_html/link_send_success.html deleted file mode 100644 index 55d9cad6f6..0000000000 --- a/public_html/link_send_success.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Invalid Link - - - - -
-

Link Sent! Check your email.

-
- - diff --git a/public_html/password_reset_success.html b/public_html/password_reset_success.html deleted file mode 100644 index 774cbb350c..0000000000 --- a/public_html/password_reset_success.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Password Reset - - -

Successfully updated your password!

- - diff --git a/public_html/verify_email_success.html b/public_html/verify_email_success.html deleted file mode 100644 index 774ea38a0d..0000000000 --- a/public_html/verify_email_success.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Email Verification - - -

Successfully verified your email!

- - diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 1a235d8d13..a0834196af 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -40,12 +40,8 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); - const url = new URL(sendEmailOptions.link); - const token = url.searchParams.get('token'); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); }); it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', async () => { @@ -81,7 +77,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); await user.fetch(); expect(user.get('emailVerified')).toEqual(false); }); @@ -114,10 +110,8 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Email verified!'); }); it_id('94956799-c85e-4297-b879-e2d1f985394c')(it)('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', async () => { @@ -148,7 +142,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); await user.fetch(); expect(user.get('emailVerified')).toEqual(true); }); @@ -181,7 +175,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); const verifiedUser = await Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken'); expect(typeof verifiedUser).toBe('object'); expect(verifiedUser.get('emailVerified')).toBe(true); @@ -268,9 +262,7 @@ describe('Email Verification Token Expiration:', () => { url: `http://localhost:8378/1/apps/test/verify_email?token=${token}`, method: 'GET', }); - expect(res.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}` - ); + expect(res.text).toContain('Invalid verification link!'); const formUrl = `http://localhost:8378/1/apps/test/resend_verification_email`; const formResponse = await request({ @@ -282,9 +274,7 @@ describe('Email Verification Token Expiration:', () => { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, followRedirects: false, }); - expect(formResponse.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/link_send_success.html` - ); + expect(formResponse.text).toContain('email_verification_send_success.html'); }); it_id('9365c53c-b8b4-41f7-a3c1-77882f76a89c')(it)('can conditionally send emails', async () => { @@ -493,7 +483,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); const config = Config.get('test'); const results = await config.database.find('_User', { username: 'unsets_email_verify_token_expires_at', @@ -536,7 +526,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); const config = Config.get('test'); const results = await config.database.find('_User', { username: 'unsets_email_verify_token_expires_at', @@ -580,7 +570,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); await user.fetch(); expect(user.get('emailVerified')).toEqual(true); // RECONFIGURE the server i.e., ENABLE the expire email verify token flag @@ -591,12 +581,8 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); - const url = new URL(sendEmailOptions.link); - const token = url.searchParams.get('token'); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); }); it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', async () => { @@ -637,12 +623,8 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); - const url = new URL(sendEmailOptions.link); - const token = url.searchParams.get('token'); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); }); it_id('b6c87f35-d887-477d-bc86-a9217a424f53')(it)('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', async () => { @@ -958,7 +940,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); expect(sendVerificationEmailCallCount).toBe(1); response = await request({ @@ -1143,7 +1125,7 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); user = await Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken'); expect(typeof user).toBe('object'); expect(user.get('emailVerified')).toBe(true); @@ -1159,6 +1141,6 @@ describe('Email Verification Token Expiration:', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); + expect(response.status).toEqual(200); }); }); diff --git a/spec/PasswordPolicy.spec.js b/spec/PasswordPolicy.spec.js index 1fd2e6aa50..27145015bc 100644 --- a/spec/PasswordPolicy.spec.js +++ b/spec/PasswordPolicy.spec.js @@ -46,10 +46,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid password reset link!'); done(); }) .catch(error => { @@ -106,9 +104,8 @@ describe('Password Policy: ', () => { followRedirects: false, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=[a-zA-Z0-9]+\&id=test\&/; - expect(response.text.match(re)).not.toBe(null); + expect(response.status).toEqual(200); + expect(response.text).toContain('password'); done(); }) .catch(error => { @@ -621,8 +618,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -643,10 +640,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Success!'); Parse.User.logIn('user1', 'has2init') .then(function () { @@ -713,8 +708,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -735,10 +730,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/choose_password?token=${token}&id=test&error=Password%20should%20contain%20at%20least%20one%20digit.&app=passwordPolicy` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Password should contain at least one digit.'); Parse.User.logIn('user1', 'has 1 digit') .then(function () { @@ -899,8 +892,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -921,10 +914,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/choose_password?token=${token}&id=test&error=Password%20cannot%20contain%20your%20username.&app=passwordPolicy` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Password cannot contain your username.'); Parse.User.logIn('user1', 'r@nd0m') .then(function () { @@ -990,8 +981,8 @@ describe('Password Policy: ', () => { simple: false, resolveWithFullResponse: true, }); - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -1050,8 +1041,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -1072,10 +1063,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Success!'); Parse.User.logIn('user1', 'uuser11') .then(function () { @@ -1316,8 +1305,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -1338,10 +1327,8 @@ describe('Password Policy: ', () => { resolveWithFullResponse: true, }) .then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Success!'); Parse.User.logIn('user1', 'uuser11') .then(function () { @@ -1471,8 +1458,8 @@ describe('Password Policy: ', () => { followRedirects: false, }) .then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -1498,10 +1485,8 @@ describe('Password Policy: ', () => { .then(data => { const response = data[0]; const token = data[1]; - expect(response.status).toEqual(302); - expect(response.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/choose_password?token=${token}&id=test&error=New%20password%20should%20not%20be%20the%20same%20as%20last%201%20passwords.&app=passwordPolicy` - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('New password should not be the same as last 1 passwords.'); done(); return Promise.resolve(); }) diff --git a/spec/PublicAPI.spec.js b/spec/PublicAPI.spec.js deleted file mode 100644 index 940417ad24..0000000000 --- a/spec/PublicAPI.spec.js +++ /dev/null @@ -1,162 +0,0 @@ -const req = require('../lib/request'); - -const request = function (url, callback) { - return req({ - url, - }).then( - response => callback(null, response), - err => callback(err, err) - ); -}; - -describe('public API', () => { - it('should return missing token error on ajax request without token provided', async () => { - await reconfigureServer({ - publicServerURL: 'http://localhost:8378/1', - }); - - try { - await req({ - method: 'POST', - url: 'http://localhost:8378/1/apps/test/request_password_reset', - body: `new_password=user1&token=`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-Requested-With': 'XMLHttpRequest', - }, - followRedirects: false, - }); - } catch (error) { - expect(error.status).not.toBe(302); - expect(error.text).toEqual('{"code":-1,"error":"Missing token"}'); - } - }); - - it('should return missing password error on ajax request without password provided', async () => { - await reconfigureServer({ - publicServerURL: 'http://localhost:8378/1', - }); - - try { - await req({ - method: 'POST', - url: 'http://localhost:8378/1/apps/test/request_password_reset', - body: `new_password=&token=132414`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-Requested-With': 'XMLHttpRequest', - }, - followRedirects: false, - }); - } catch (error) { - expect(error.status).not.toBe(302); - expect(error.text).toEqual('{"code":201,"error":"Missing password"}'); - } - }); - - it('should get invalid_link.html', done => { - request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse) => { - expect(httpResponse.status).toBe(200); - done(); - }); - }); - - it('should get choose_password', done => { - reconfigureServer({ - appName: 'unused', - publicServerURL: 'http://localhost:8378/1', - }).then(() => { - request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => { - expect(httpResponse.status).toBe(200); - done(); - }); - }); - }); - - it('should get verify_email_success.html', done => { - request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse) => { - expect(httpResponse.status).toBe(200); - done(); - }); - }); - - it('should get password_reset_success.html', done => { - request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse) => { - expect(httpResponse.status).toBe(200); - done(); - }); - }); -}); - -describe('public API without publicServerURL', () => { - beforeEach(async () => { - await reconfigureServer({ appName: 'unused' }); - }); - it('should get 404 on verify_email', done => { - request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse) => { - expect(httpResponse.status).toBe(404); - done(); - }); - }); - - it('should get 404 choose_password', done => { - request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse) => { - expect(httpResponse.status).toBe(404); - done(); - }); - }); - - it('should get 404 on request_password_reset', done => { - request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse) => { - expect(httpResponse.status).toBe(404); - done(); - }); - }); -}); - -describe('public API supplied with invalid application id', () => { - beforeEach(async () => { - await reconfigureServer({ appName: 'unused' }); - }); - - it('should get 403 on verify_email', done => { - request('http://localhost:8378/1/apps/invalid/verify_email', (err, httpResponse) => { - expect(httpResponse.status).toBe(403); - done(); - }); - }); - - it('should get 403 choose_password', done => { - request('http://localhost:8378/1/apps/choose_password?id=invalid', (err, httpResponse) => { - expect(httpResponse.status).toBe(403); - done(); - }); - }); - - it('should get 403 on get of request_password_reset', done => { - request('http://localhost:8378/1/apps/invalid/request_password_reset', (err, httpResponse) => { - expect(httpResponse.status).toBe(403); - done(); - }); - }); - - it('should get 403 on post of request_password_reset', done => { - req({ - url: 'http://localhost:8378/1/apps/invalid/request_password_reset', - method: 'POST', - }).then(done.fail, httpResponse => { - expect(httpResponse.status).toBe(403); - done(); - }); - }); - - it('should get 403 on resendVerificationEmail', done => { - request( - 'http://localhost:8378/1/apps/invalid/resend_verification_email', - (err, httpResponse) => { - expect(httpResponse.status).toBe(403); - done(); - } - ); - }); -}); diff --git a/spec/RegexVulnerabilities.spec.js b/spec/RegexVulnerabilities.spec.js index 8418494bac..aab0241ad8 100644 --- a/spec/RegexVulnerabilities.spec.js +++ b/spec/RegexVulnerabilities.spec.js @@ -147,8 +147,8 @@ describe('Regex Vulnerabilities', () => { url: `${serverURL}/apps/test/request_password_reset?token[$regex]=`, method: 'GET', }); - expect(passwordResetResponse.status).toEqual(302); - expect(passwordResetResponse.headers.location).toMatch(`\\/invalid\\_link\\.html`); + expect(passwordResetResponse.status).toEqual(200); + expect(passwordResetResponse.text).toContain('Invalid password reset link!'); await request({ url: `${serverURL}/apps/test/request_password_reset`, method: 'POST', @@ -195,10 +195,8 @@ describe('Regex Vulnerabilities', () => { url: `${serverURL}/apps/test/request_password_reset?token=${token}`, method: 'GET', }); - expect(passwordResetResponse.status).toEqual(302); - expect(passwordResetResponse.headers.location).toMatch( - `\\/choose\\_password\\?token\\=${token}\\&` - ); + expect(passwordResetResponse.status).toEqual(200); + expect(passwordResetResponse.text).toContain('Reset Your Password'); await request({ url: `${serverURL}/apps/test/request_password_reset`, method: 'POST', diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 3f6d4048c5..e2113c2804 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -333,10 +333,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: sendEmailOptions.link, followRedirects: false, }); - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Email verified!'); user = await new Parse.Query(Parse.User).first({ useMasterKey: true }); expect(user.get('emailVerified')).toEqual(true); user = await Parse.User.logIn('user', 'other-password'); @@ -674,10 +672,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: sendEmailOptions.link, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Email verified!'); user .fetch() .then( @@ -714,10 +710,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: 'http://localhost:8378/1/apps/test/verify_email', followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); done(); }); }); @@ -738,10 +732,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: 'http://localhost:8378/1/apps/test/verify_email?token=asdfasdf', followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=asdfasdf' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); done(); }); }); @@ -766,10 +758,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { username: 'sadfasga', }, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/link_send_fail.html' - ); + expect(response.status).toEqual(303); + expect(response.text).toContain('email_verification_send_fail.html'); done(); }); }); @@ -783,10 +773,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: 'http://localhost:8378/1/apps/test/verify_email?token=invalid', followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=invalid' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid verification link!'); user.fetch().then(() => { expect(user.get('emailVerified')).toEqual(false); done(); @@ -824,8 +812,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: options.link, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=[a-zA-Z0-9]+\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; expect(response.text.match(re)).not.toBe(null); done(); }); @@ -868,10 +856,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: 'http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf', followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Invalid password reset link!'); done(); }); }); @@ -886,8 +872,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: options.link, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -905,10 +891,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Success!'); Parse.User.logIn('zxcv', 'hello').then( function () { @@ -963,8 +947,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: options.link, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -982,10 +966,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { }, followRedirects: false, }).then(response => { - expect(response.status).toEqual(302); - expect(response.text).toEqual( - 'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html' - ); + expect(response.status).toEqual(200); + expect(response.text).toContain('Success!'); done(); }); }); @@ -1022,8 +1004,8 @@ describe('Custom Pages, Email Verification, Password Reset', () => { url: options.link, followRedirects: false, }); - expect(response.status).toEqual(302); - const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/; + expect(response.status).toEqual(200); + const re = /name="token"[^>]*value="([^"]+)"/; const match = response.text.match(re); if (!match) { fail('should have a token'); @@ -1197,9 +1179,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { new_password: 'newpassword', }, }); - expect(res.text).toEqual( - `Found. Redirecting to http://localhost:8378/1/apps/choose_password?id=test&error=The%20password%20reset%20link%20has%20expired&app=emailVerifyToken&token=${token}` - ); + expect(res.text).toContain('The password reset link has expired'); await request({ url: `http://localhost:8378/1/requestPasswordReset`, diff --git a/src/Config.js b/src/Config.js index 879aebd77a..54e3cc5ca4 100644 --- a/src/Config.js +++ b/src/Config.js @@ -828,10 +828,8 @@ export class Config { return this.masterKey; } - // TODO: Remove this function once PagesRouter replaces the PublicAPIRouter; - // the (default) endpoint has to be defined in PagesRouter only. get pagesEndpoint() { - return this.pages && this.pages.enableRouter && this.pages.pagesEndpoint + return this.pages && this.pages.pagesEndpoint ? this.pages.pagesEndpoint : 'apps'; } diff --git a/src/ParseServer.ts b/src/ParseServer.ts index 04543ac1c3..fea0764cf5 100644 --- a/src/ParseServer.ts +++ b/src/ParseServer.ts @@ -27,7 +27,6 @@ import { InstallationsRouter } from './Routers/InstallationsRouter'; import { LogsRouter } from './Routers/LogsRouter'; import { ParseLiveQueryServer } from './LiveQuery/ParseLiveQueryServer'; import { PagesRouter } from './Routers/PagesRouter'; -import { PublicAPIRouter } from './Routers/PublicAPIRouter'; import { PushRouter } from './Routers/PushRouter'; import { CloudCodeRouter } from './Routers/CloudCodeRouter'; import { RolesRouter } from './Routers/RolesRouter'; @@ -330,9 +329,7 @@ class ParseServer { api.use( '/', express.urlencoded({ extended: false }), - pages.enableRouter - ? new PagesRouter(pages).expressRouter() - : new PublicAPIRouter().expressRouter() + new PagesRouter(pages).expressRouter() ); api.use(express.json({ type: '*/*', limit: maxUploadSize })); diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js deleted file mode 100644 index 2ec993f390..0000000000 --- a/src/Routers/PublicAPIRouter.js +++ /dev/null @@ -1,332 +0,0 @@ -import PromiseRouter from '../PromiseRouter'; -import Config from '../Config'; -import express from 'express'; -import path from 'path'; -import fs from 'fs'; -import qs from 'querystring'; -import { Parse } from 'parse/node'; -import Deprecator from '../Deprecator/Deprecator'; - -const public_html = path.resolve(__dirname, '../../public_html'); -const views = path.resolve(__dirname, '../../views'); - -export class PublicAPIRouter extends PromiseRouter { - constructor() { - super(); - Deprecator.logRuntimeDeprecation({ - usage: 'PublicAPIRouter', - solution: 'pages.enableRouter' - }); - } - verifyEmail(req) { - const { token: rawToken } = req.query; - const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; - - const appId = req.params.appId; - const config = Config.get(appId); - - if (!config) { - this.invalidRequest(); - } - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - if (!token) { - return this.invalidLink(req); - } - - const userController = config.userController; - return userController.verifyEmail(token).then( - () => { - return Promise.resolve({ - status: 302, - location: `${config.verifyEmailSuccessURL}`, - }); - }, - () => { - return this.invalidVerificationLink(req, token); - } - ); - } - - resendVerificationEmail(req) { - const username = req.body?.username; - const appId = req.params.appId; - const config = Config.get(appId); - - if (!config) { - this.invalidRequest(); - } - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - const token = req.body.token; - - if (!username && !token) { - return this.invalidLink(req); - } - - const userController = config.userController; - - return userController.resendVerificationEmail(username, req, token).then( - () => { - return Promise.resolve({ - status: 302, - location: `${config.linkSendSuccessURL}`, - }); - }, - () => { - return Promise.resolve({ - status: 302, - location: `${config.linkSendFailURL}`, - }); - } - ); - } - - changePassword(req) { - return new Promise((resolve, reject) => { - const config = Config.get(req.query.id); - - if (!config) { - this.invalidRequest(); - } - - if (!config.publicServerURL) { - return 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) { - return reject(err); - } - data = data.replace('PARSE_SERVER_URL', `'${config.publicServerURL}'`); - resolve({ - text: data, - }); - }); - }); - } - - requestResetPassword(req) { - const config = req.config; - - if (!config) { - this.invalidRequest(); - } - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - const { token: rawToken } = req.query; - const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; - - if (!token) { - return this.invalidLink(req); - } - - return config.userController.checkResetTokenValidity(token).then( - () => { - const params = qs.stringify({ - token, - id: config.applicationId, - app: config.appName, - }); - return Promise.resolve({ - status: 302, - location: `${config.choosePasswordURL}?${params}`, - }); - }, - () => { - return this.invalidLink(req); - } - ); - } - - resetPassword(req) { - const config = req.config; - - if (!config) { - this.invalidRequest(); - } - - if (!config.publicServerURL) { - return this.missingPublicServerURL(); - } - - const { new_password, token: rawToken } = req.body || {}; - const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken; - - if ((!token || !new_password) && req.xhr === false) { - return this.invalidLink(req); - } - - if (!token) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token'); - } - - if (!new_password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'Missing password'); - } - - return config.userController - .updatePassword(token, new_password) - .then( - () => { - return Promise.resolve({ - success: true, - }); - }, - err => { - return Promise.resolve({ - success: false, - err, - }); - } - ) - .then(result => { - const queryString = { - token: token, - id: config.applicationId, - error: result.err, - app: config.appName, - }; - - if (result?.err === 'The password reset link has expired') { - delete queryString.token; - queryString.token = token; - } - const params = qs.stringify(queryString); - - if (req.xhr) { - if (result.success) { - return Promise.resolve({ - status: 200, - response: 'Password successfully reset', - }); - } - if (result.err) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${result.err}`); - } - } - - const location = result.success - ? `${config.passwordResetSuccessURL}` - : `${config.choosePasswordURL}?${params}`; - - return Promise.resolve({ - status: 302, - location, - }); - }); - } - - invalidLink(req) { - return Promise.resolve({ - status: 302, - location: req.config.invalidLinkURL, - }); - } - - invalidVerificationLink(req, token) { - const config = req.config; - if (req.params.appId) { - const params = qs.stringify({ - appId: req.params.appId, - token, - }); - return Promise.resolve({ - status: 302, - location: `${config.invalidVerificationLinkURL}?${params}`, - }); - } else { - return this.invalidLink(req); - } - } - - missingPublicServerURL() { - return Promise.resolve({ - text: 'Not found.', - status: 404, - }); - } - - invalidRequest() { - const error = new Error(); - error.status = 403; - error.message = 'unauthorized'; - throw error; - } - - setConfig(req) { - req.config = Config.get(req.params.appId); - return Promise.resolve(); - } - - mountRoutes() { - this.route( - 'GET', - '/apps/:appId/verify_email', - req => { - this.setConfig(req); - }, - req => { - return this.verifyEmail(req); - } - ); - - this.route( - 'POST', - '/apps/:appId/resend_verification_email', - req => { - this.setConfig(req); - }, - req => { - return this.resendVerificationEmail(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); - } - ); - } - - expressRouter() { - const router = express.Router(); - router.use('/apps', express.static(public_html)); - router.use('/', super.expressRouter()); - return router; - } -} - -export default PublicAPIRouter;