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;