fix: Remove username from email verification and password reset process (#8488)
BREAKING CHANGE: This removes the username from the email verification and password reset process to prevent storing personally identifiable information (PII) in server and infrastructure logs. Customized HTML pages or emails related to email verification and password reset may need to be adapted accordingly. See the new templates that come bundled with Parse Server and the [migration guide](https://github.com/parse-community/parse-server/blob/alpha/8.0.0.md) for more details.
This commit is contained in:
27
8.0.0.md
Normal file
27
8.0.0.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Parse Server 8 Migration Guide <!-- omit in toc -->
|
||||||
|
|
||||||
|
This document only highlights specific changes that require a longer explanation. For a full list of changes in Parse Server 8 please refer to the [changelog](https://github.com/parse-community/parse-server/blob/alpha/CHANGELOG.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [Email Verification](#email-verification)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Email Verification
|
||||||
|
|
||||||
|
In order to remove sensitive information (PII) from technical logs, the `Parse.User.username` field has been removed from the email verification process. This means the username will no longer be used and the already existing verification token, that is internal to Parse Server and associated with the user, will be used instead. This makes use of the fact that an expired verification token is not deleted from the database by Parse Server, despite being expired, and can therefore be used to identify a user.
|
||||||
|
|
||||||
|
This change affects how verification emails with expired tokens are handled. When opening a verification link that contains an expired token, the page that the user is redirected to will no longer provide the `username` as a URL query parameter. Instead, the URL query parameter `token` will be provided.
|
||||||
|
|
||||||
|
The request to re-send a verification email changed to sending a `POST` request to the endpoint `/resend_verification_email` with `token` in the body, instead of `username`. If you have customized the HTML pages for email verification either for the `PagesRouter` in `/public/` or the deprecated `PublicAPIRouter` in `/public_html/`, you need to adapt the form request in your custom pages. See the example pages in these aforementioned directories for how the forms must be set up.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> An expired verification token is not automatically deleted from the database by Parse Server even though it has expired. If you have implemented a custom clean-up logic that removes expired tokens, this will break the form request to re-send a verification email as the expired token won't be found and cannot be associated with any user. In that case you'll have to implement your custom process to re-send a verification email.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Parse Server does not keep a history of verification tokens but only stores the most recently generated verification token in the database. Every time Parse Server generates a new verification token, the currently stored token is replaced. If a user opens a link with an expired token, and that token has already been replaced in the database, Parse Server cannot associate the expired token with any user. In this case, another way has to be offered to the user to re-send a verification email. To mitigate this issue, set the Parse Server option `emailVerifyTokenReuseIfValid: true` and set `emailVerifyTokenValidityDuration` to a longer duration, which ensures that the currently stored verification token is not replaced too soon.
|
||||||
|
|
||||||
|
Related pull requests:
|
||||||
|
|
||||||
|
- https://github.com/parse-community/parse-server/pull/8488
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||||
<input name="username" type="hidden" value="{{{username}}}">
|
<input name="token" type="hidden" value="{{{token}}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||||
<input name="username" type="hidden" value="{{{username}}}">
|
<input name="token" type="hidden" value="{{{token}}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||||
<input name="username" type="hidden" value="{{{username}}}">
|
<input name="token" type="hidden" value="{{{token}}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
window.onload = addDataToForm;
|
window.onload = addDataToForm;
|
||||||
|
|
||||||
function addDataToForm() {
|
function addDataToForm() {
|
||||||
var username = getUrlParameter("username");
|
const token = getUrlParameter("token");
|
||||||
document.getElementById("usernameField").value = username;
|
document.getElementById("token").value = token;
|
||||||
|
|
||||||
var appId = getUrlParameter("appId");
|
var appId = getUrlParameter("appId");
|
||||||
document.getElementById("resendForm").action = '/apps/' + appId + '/resend_verification_email'
|
document.getElementById("resendForm").action = '/apps/' + appId + '/resend_verification_email'
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Invalid Verification Link</h1>
|
<h1>Invalid Verification Link</h1>
|
||||||
<form id="resendForm" method="POST" action="/resend_verification_email">
|
<form id="resendForm" method="POST" action="/resend_verification_email">
|
||||||
<input id="usernameField" class="form-control" name="username" type="hidden" value="">
|
<input id="token" class="form-control" name="token" type="hidden" value="">
|
||||||
<button type="submit" class="btn btn-default">Resend Link</button>
|
<button type="submit" class="btn btn-default">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ describe('lockout with password reset option', () => {
|
|||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
||||||
body: `new_password=${newPassword}&token=${token}&username=${username}`,
|
body: `new_password=${newPassword}&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -454,7 +454,7 @@ describe('lockout with password reset option', () => {
|
|||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
||||||
body: `new_password=${newPassword}&token=${token}&username=${username}`,
|
body: `new_password=${newPassword}&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ describe('Email Verification Token Expiration: ', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
|
const url = new URL(sendEmailOptions.link);
|
||||||
|
const token = url.searchParams.get('token');
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
|
`Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -135,7 +137,7 @@ describe('Email Verification Token Expiration: ', () => {
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
|
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html'
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -292,6 +294,64 @@ describe('Email Verification Token Expiration: ', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can resend email using an expired token', async () => {
|
||||||
|
const user = new Parse.User();
|
||||||
|
const emailAdapter = {
|
||||||
|
sendVerificationEmail: () => {},
|
||||||
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
|
sendMail: () => {},
|
||||||
|
};
|
||||||
|
await reconfigureServer({
|
||||||
|
appName: 'emailVerifyToken',
|
||||||
|
verifyUserEmails: true,
|
||||||
|
emailAdapter: emailAdapter,
|
||||||
|
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||||
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
|
});
|
||||||
|
user.setUsername('test');
|
||||||
|
user.setPassword('password');
|
||||||
|
user.set('email', 'user@example.com');
|
||||||
|
await user.signUp();
|
||||||
|
|
||||||
|
await Parse.Server.database.update(
|
||||||
|
'_User',
|
||||||
|
{ objectId: user.id },
|
||||||
|
{
|
||||||
|
_email_verify_token_expires_at: Parse._encode(new Date('2000')),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const obj = await Parse.Server.database.find(
|
||||||
|
'_User',
|
||||||
|
{ objectId: user.id },
|
||||||
|
{},
|
||||||
|
Auth.maintenance(Parse.Server)
|
||||||
|
);
|
||||||
|
const token = obj[0]._email_verify_token;
|
||||||
|
|
||||||
|
const res = await request({
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const formUrl = `http://localhost:8378/1/apps/test/resend_verification_email`;
|
||||||
|
const formResponse = await request({
|
||||||
|
url: formUrl,
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
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`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it_id('9365c53c-b8b4-41f7-a3c1-77882f76a89c')(it)('can conditionally send emails', async () => {
|
it_id('9365c53c-b8b4-41f7-a3c1-77882f76a89c')(it)('can conditionally send emails', async () => {
|
||||||
let sendEmailOptions;
|
let sendEmailOptions;
|
||||||
const emailAdapter = {
|
const emailAdapter = {
|
||||||
@@ -614,8 +674,10 @@ describe('Email Verification Token Expiration: ', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
|
const url = new URL(sendEmailOptions.link);
|
||||||
|
const token = url.searchParams.get('token');
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'
|
`Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -667,8 +729,10 @@ describe('Email Verification Token Expiration: ', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
|
const url = new URL(sendEmailOptions.link);
|
||||||
|
const token = url.searchParams.get('token');
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'
|
`Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ describe('Pages Router', () => {
|
|||||||
const res = await request({
|
const res = await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=user1&token=43634643&username=username`,
|
body: `new_password=user1&token=43634643`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -124,7 +124,7 @@ describe('Pages Router', () => {
|
|||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=&token=132414&username=Johnny`,
|
body: `new_password=&token=132414`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -137,30 +137,12 @@ describe('Pages Router', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('request_password_reset: responds with AJAX error on missing username', async () => {
|
|
||||||
try {
|
|
||||||
await request({
|
|
||||||
method: 'POST',
|
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
|
||||||
body: `new_password=user1&token=43634643&username=`,
|
|
||||||
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":200,"error":"Missing username"}');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('request_password_reset: responds with AJAX error on missing token', async () => {
|
it('request_password_reset: responds with AJAX error on missing token', async () => {
|
||||||
try {
|
try {
|
||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=user1&token=&username=Johnny`,
|
body: `new_password=user1&token=`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -577,7 +559,7 @@ describe('Pages Router', () => {
|
|||||||
spyOnProperty(Page.prototype, 'defaultFile').and.returnValue(jsonPageFile);
|
spyOnProperty(Page.prototype, 'defaultFile').and.returnValue(jsonPageFile);
|
||||||
|
|
||||||
const response = await request({
|
const response = await request({
|
||||||
url: `http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=${exampleLocale}`,
|
url: `http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&locale=${exampleLocale}`,
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).catch(e => e);
|
}).catch(e => e);
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
@@ -626,7 +608,7 @@ describe('Pages Router', () => {
|
|||||||
await reconfigureServer(config);
|
await reconfigureServer(config);
|
||||||
const response = await request({
|
const response = await request({
|
||||||
url:
|
url:
|
||||||
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=de-AT',
|
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&locale=de-AT',
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
@@ -640,7 +622,7 @@ describe('Pages Router', () => {
|
|||||||
await reconfigureServer(config);
|
await reconfigureServer(config);
|
||||||
const response = await request({
|
const response = await request({
|
||||||
url:
|
url:
|
||||||
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=de-AT',
|
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&locale=de-AT',
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
@@ -676,13 +658,11 @@ describe('Pages Router', () => {
|
|||||||
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
||||||
const token = linkResponse.headers['x-parse-page-param-token'];
|
const token = linkResponse.headers['x-parse-page-param-token'];
|
||||||
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
||||||
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
||||||
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
||||||
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
||||||
expect(appId).toBeDefined();
|
expect(appId).toBeDefined();
|
||||||
expect(token).toBeDefined();
|
expect(token).toBeDefined();
|
||||||
expect(locale).toBeDefined();
|
expect(locale).toBeDefined();
|
||||||
expect(username).toBeDefined();
|
|
||||||
expect(publicServerUrl).toBeDefined();
|
expect(publicServerUrl).toBeDefined();
|
||||||
expect(passwordResetPagePath).toMatch(
|
expect(passwordResetPagePath).toMatch(
|
||||||
new RegExp(`\/${exampleLocale}\/${pages.passwordReset.defaultFile}`)
|
new RegExp(`\/${exampleLocale}\/${pages.passwordReset.defaultFile}`)
|
||||||
@@ -696,7 +676,6 @@ describe('Pages Router', () => {
|
|||||||
body: {
|
body: {
|
||||||
token,
|
token,
|
||||||
locale,
|
locale,
|
||||||
username,
|
|
||||||
new_password: 'newPassword',
|
new_password: 'newPassword',
|
||||||
},
|
},
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
@@ -793,15 +772,13 @@ describe('Pages Router', () => {
|
|||||||
|
|
||||||
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
||||||
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
||||||
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
||||||
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
||||||
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
||||||
expect(appId).toBeDefined();
|
expect(appId).toBeDefined();
|
||||||
expect(locale).toBe(exampleLocale);
|
expect(locale).toBe(exampleLocale);
|
||||||
expect(username).toBeDefined();
|
|
||||||
expect(publicServerUrl).toBeDefined();
|
expect(publicServerUrl).toBeDefined();
|
||||||
expect(invalidVerificationPagePath).toMatch(
|
expect(invalidVerificationPagePath).toMatch(
|
||||||
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`)
|
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkInvalid.defaultFile}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`;
|
const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`;
|
||||||
@@ -810,7 +787,7 @@ describe('Pages Router', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
locale,
|
locale,
|
||||||
username,
|
username: 'exampleUsername',
|
||||||
},
|
},
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
@@ -847,17 +824,15 @@ describe('Pages Router', () => {
|
|||||||
|
|
||||||
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
||||||
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
||||||
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
||||||
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
||||||
await jasmine.timeout();
|
await jasmine.timeout();
|
||||||
|
|
||||||
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
||||||
expect(appId).toBeDefined();
|
expect(appId).toBeDefined();
|
||||||
expect(locale).toBe(exampleLocale);
|
expect(locale).toBe(exampleLocale);
|
||||||
expect(username).toBeDefined();
|
|
||||||
expect(publicServerUrl).toBeDefined();
|
expect(publicServerUrl).toBeDefined();
|
||||||
expect(invalidVerificationPagePath).toMatch(
|
expect(invalidVerificationPagePath).toMatch(
|
||||||
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`)
|
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkInvalid.defaultFile}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
spyOn(UserController.prototype, 'resendVerificationEmail').and.callFake(() =>
|
spyOn(UserController.prototype, 'resendVerificationEmail').and.callFake(() =>
|
||||||
@@ -870,7 +845,7 @@ describe('Pages Router', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
locale,
|
locale,
|
||||||
username,
|
username: 'exampleUsername',
|
||||||
},
|
},
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
@@ -1155,12 +1130,10 @@ describe('Pages Router', () => {
|
|||||||
|
|
||||||
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
||||||
const token = linkResponse.headers['x-parse-page-param-token'];
|
const token = linkResponse.headers['x-parse-page-param-token'];
|
||||||
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
||||||
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
||||||
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
||||||
expect(appId).toBeDefined();
|
expect(appId).toBeDefined();
|
||||||
expect(token).toBeDefined();
|
expect(token).toBeDefined();
|
||||||
expect(username).toBeDefined();
|
|
||||||
expect(publicServerUrl).toBeDefined();
|
expect(publicServerUrl).toBeDefined();
|
||||||
expect(passwordResetPagePath).toMatch(new RegExp(`\/${pages.passwordReset.defaultFile}`));
|
expect(passwordResetPagePath).toMatch(new RegExp(`\/${pages.passwordReset.defaultFile}`));
|
||||||
pageResponse.calls.reset();
|
pageResponse.calls.reset();
|
||||||
@@ -1171,7 +1144,6 @@ describe('Pages Router', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
token,
|
token,
|
||||||
username,
|
|
||||||
new_password: 'newPassword',
|
new_password: 'newPassword',
|
||||||
},
|
},
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
|||||||
@@ -969,7 +969,7 @@ describe('ParseLiveQuery', function () {
|
|||||||
const userController = new UserController(emailAdapter, 'test', {
|
const userController = new UserController(emailAdapter, 'test', {
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
});
|
});
|
||||||
userController.verifyEmail(foundUser.username, foundUser._email_verify_token);
|
userController.verifyEmail(foundUser._email_verify_token);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=[a-zA-Z0-9]+\&id=test\&username=testResetTokenValidity/;
|
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.text.match(re)).not.toBe(null);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@@ -622,7 +622,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -634,7 +634,7 @@ describe('Password Policy: ', () => {
|
|||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=has2init&token=${token}&username=user1`,
|
body: `new_password=has2init&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -645,7 +645,7 @@ describe('Password Policy: ', () => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1'
|
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('user1', 'has2init')
|
Parse.User.logIn('user1', 'has2init')
|
||||||
@@ -714,7 +714,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -726,7 +726,7 @@ describe('Password Policy: ', () => {
|
|||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=hasnodigit&token=${token}&username=user1`,
|
body: `new_password=hasnodigit&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -737,7 +737,7 @@ describe('Password Policy: ', () => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20should%20contain%20at%20least%20one%20digit.&app=passwordPolicy`
|
`Found. Redirecting to http://localhost:8378/1/apps/choose_password?token=${token}&id=test&error=Password%20should%20contain%20at%20least%20one%20digit.&app=passwordPolicy`
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('user1', 'has 1 digit')
|
Parse.User.logIn('user1', 'has 1 digit')
|
||||||
@@ -900,7 +900,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -912,7 +912,7 @@ describe('Password Policy: ', () => {
|
|||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=xuser12&token=${token}&username=user1`,
|
body: `new_password=xuser12&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -923,7 +923,7 @@ describe('Password Policy: ', () => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=Password%20cannot%20contain%20your%20username.&app=passwordPolicy`
|
`Found. Redirecting to http://localhost:8378/1/apps/choose_password?token=${token}&id=test&error=Password%20cannot%20contain%20your%20username.&app=passwordPolicy`
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('user1', 'r@nd0m')
|
Parse.User.logIn('user1', 'r@nd0m')
|
||||||
@@ -991,7 +991,7 @@ describe('Password Policy: ', () => {
|
|||||||
resolveWithFullResponse: true,
|
resolveWithFullResponse: true,
|
||||||
});
|
});
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -1003,7 +1003,7 @@ describe('Password Policy: ', () => {
|
|||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=xuser12&token=${token}&username=user1`,
|
body: `new_password=xuser12&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -1051,7 +1051,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -1063,7 +1063,7 @@ describe('Password Policy: ', () => {
|
|||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=uuser11&token=${token}&username=user1`,
|
body: `new_password=uuser11&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -1074,7 +1074,7 @@ describe('Password Policy: ', () => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1'
|
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('user1', 'uuser11')
|
Parse.User.logIn('user1', 'uuser11')
|
||||||
@@ -1317,7 +1317,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -1329,7 +1329,7 @@ describe('Password Policy: ', () => {
|
|||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=uuser11&token=${token}&username=user1`,
|
body: `new_password=uuser11&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -1340,7 +1340,7 @@ describe('Password Policy: ', () => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=user1'
|
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('user1', 'uuser11')
|
Parse.User.logIn('user1', 'uuser11')
|
||||||
@@ -1472,7 +1472,7 @@ describe('Password Policy: ', () => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=user1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -1484,7 +1484,7 @@ describe('Password Policy: ', () => {
|
|||||||
return request({
|
return request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=user1&token=${token}&username=user1`,
|
body: `new_password=user1&token=${token}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
@@ -1500,7 +1500,7 @@ describe('Password Policy: ', () => {
|
|||||||
const token = data[1];
|
const token = data[1];
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
`Found. Redirecting to http://localhost:8378/1/apps/choose_password?username=user1&token=${token}&id=test&error=New%20password%20should%20not%20be%20the%20same%20as%20last%201%20passwords.&app=passwordPolicy`
|
`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`
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@@ -10,28 +10,6 @@ const request = function (url, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('public API', () => {
|
describe('public API', () => {
|
||||||
it('should return missing username error on ajax request without username 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=43634643&username=`,
|
|
||||||
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":200,"error":"Missing username"}');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return missing token error on ajax request without token provided', async () => {
|
it('should return missing token error on ajax request without token provided', async () => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
publicServerURL: 'http://localhost:8378/1',
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
@@ -41,7 +19,7 @@ describe('public API', () => {
|
|||||||
await req({
|
await req({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=user1&token=&username=Johnny`,
|
body: `new_password=user1&token=`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -63,7 +41,7 @@ describe('public API', () => {
|
|||||||
await req({
|
await req({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=&token=132414&username=Johnny`,
|
body: `new_password=&token=132414`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ describe('Regex Vulnerabilities', () => {
|
|||||||
it('should not work with regex', async () => {
|
it('should not work with regex', async () => {
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
await request({
|
await request({
|
||||||
url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token[$regex]=`,
|
url: `${serverURL}/apps/test/verify_email?token[$regex]=`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
await user.fetch({ useMasterKey: true });
|
await user.fetch({ useMasterKey: true });
|
||||||
@@ -117,7 +117,7 @@ describe('Regex Vulnerabilities', () => {
|
|||||||
}).then(res => res.data);
|
}).then(res => res.data);
|
||||||
// It should work
|
// It should work
|
||||||
await request({
|
await request({
|
||||||
url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${current._email_verify_token}`,
|
url: `${serverURL}/apps/test/verify_email?token=${current._email_verify_token}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
await user.fetch({ useMasterKey: true });
|
await user.fetch({ useMasterKey: true });
|
||||||
@@ -144,7 +144,7 @@ describe('Regex Vulnerabilities', () => {
|
|||||||
});
|
});
|
||||||
await user.fetch({ useMasterKey: true });
|
await user.fetch({ useMasterKey: true });
|
||||||
const passwordResetResponse = await request({
|
const passwordResetResponse = await request({
|
||||||
url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token[$regex]=`,
|
url: `${serverURL}/apps/test/request_password_reset?token[$regex]=`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
expect(passwordResetResponse.status).toEqual(302);
|
expect(passwordResetResponse.status).toEqual(302);
|
||||||
@@ -192,7 +192,7 @@ describe('Regex Vulnerabilities', () => {
|
|||||||
}).then(res => res.data);
|
}).then(res => res.data);
|
||||||
const token = current._perishable_token;
|
const token = current._perishable_token;
|
||||||
const passwordResetResponse = await request({
|
const passwordResetResponse = await request({
|
||||||
url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token=${token}`,
|
url: `${serverURL}/apps/test/request_password_reset?token=${token}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
expect(passwordResetResponse.status).toEqual(302);
|
expect(passwordResetResponse.status).toEqual(302);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ describe('UserController', () => {
|
|||||||
let emailOptions;
|
let emailOptions;
|
||||||
emailAdapter.sendVerificationEmail = options => {
|
emailAdapter.sendVerificationEmail = options => {
|
||||||
emailOptions = options;
|
emailOptions = options;
|
||||||
return Promise.resolve();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const username = 'verificationUser';
|
const username = 'verificationUser';
|
||||||
@@ -35,7 +34,8 @@ describe('UserController', () => {
|
|||||||
const rawToken = rawUser[0]._email_verify_token;
|
const rawToken = rawUser[0]._email_verify_token;
|
||||||
expect(rawToken).toBeDefined();
|
expect(rawToken).toBeDefined();
|
||||||
expect(rawUsername).toBe(username);
|
expect(rawUsername).toBe(username);
|
||||||
expect(emailOptions.link).toEqual(`http://www.example.com/apps/test/verify_email?token=${rawToken}&username=${username}`);
|
|
||||||
|
expect(emailOptions.link).toEqual(`http://www.example.com/apps/test/verify_email?token=${rawToken}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,7 +54,6 @@ describe('UserController', () => {
|
|||||||
let emailOptions;
|
let emailOptions;
|
||||||
emailAdapter.sendVerificationEmail = options => {
|
emailAdapter.sendVerificationEmail = options => {
|
||||||
emailOptions = options;
|
emailOptions = options;
|
||||||
return Promise.resolve();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const username = 'verificationUser';
|
const username = 'verificationUser';
|
||||||
@@ -70,7 +69,8 @@ describe('UserController', () => {
|
|||||||
const rawToken = rawUser[0]._email_verify_token;
|
const rawToken = rawUser[0]._email_verify_token;
|
||||||
expect(rawToken).toBeDefined();
|
expect(rawToken).toBeDefined();
|
||||||
expect(rawUsername).toBe(username);
|
expect(rawUsername).toBe(username);
|
||||||
expect(emailOptions.link).toEqual(`http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=${rawToken}&username=${username}`);
|
|
||||||
|
expect(emailOptions.link).toEqual(`http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=${rawToken}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
|
const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
|
||||||
const request = require('../lib/request');
|
const request = require('../lib/request');
|
||||||
const Config = require('../lib/Config');
|
const Config = require('../lib/Config');
|
||||||
|
const Auth = require('../lib/Auth');
|
||||||
|
|
||||||
describe('Custom Pages, Email Verification, Password Reset', () => {
|
describe('Custom Pages, Email Verification, Password Reset', () => {
|
||||||
it('should set the custom pages', done => {
|
it('should set the custom pages', done => {
|
||||||
@@ -334,7 +335,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
});
|
});
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user'
|
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html'
|
||||||
);
|
);
|
||||||
user = await new Parse.Query(Parse.User).first({ useMasterKey: true });
|
user = await new Parse.Query(Parse.User).first({ useMasterKey: true });
|
||||||
expect(user.get('emailVerified')).toEqual(true);
|
expect(user.get('emailVerified')).toEqual(true);
|
||||||
@@ -675,7 +676,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user'
|
'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html'
|
||||||
);
|
);
|
||||||
user
|
user
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -734,12 +735,12 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
publicServerURL: 'http://localhost:8378/1',
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
request({
|
request({
|
||||||
url: 'http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga',
|
url: 'http://localhost:8378/1/apps/test/verify_email?token=asdfasdf',
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=sadfasga&appId=test'
|
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=asdfasdf'
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -779,12 +780,12 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
const emailAdapter = {
|
const emailAdapter = {
|
||||||
sendVerificationEmail: () => {
|
sendVerificationEmail: () => {
|
||||||
request({
|
request({
|
||||||
url: 'http://localhost:8378/1/apps/test/verify_email?token=invalid&username=zxcv',
|
url: 'http://localhost:8378/1/apps/test/verify_email?token=invalid',
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=zxcv&appId=test'
|
'Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=invalid'
|
||||||
);
|
);
|
||||||
user.fetch().then(() => {
|
user.fetch().then(() => {
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
@@ -824,7 +825,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=[a-zA-Z0-9]+\&id=test\&username=zxcv%2Bzxcv/;
|
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.text.match(re)).not.toBe(null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -864,8 +865,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
publicServerURL: 'http://localhost:8378/1',
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
request({
|
request({
|
||||||
url:
|
url: 'http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf',
|
||||||
'http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf&username=sadfasga',
|
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
@@ -887,7 +887,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=zxcv/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -907,7 +907,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=zxcv'
|
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'
|
||||||
);
|
);
|
||||||
|
|
||||||
Parse.User.logIn('zxcv', 'hello').then(
|
Parse.User.logIn('zxcv', 'hello').then(
|
||||||
@@ -964,7 +964,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=zxcv%2B1/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -984,7 +984,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
expect(response.text).toEqual(
|
expect(response.text).toEqual(
|
||||||
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=zxcv%2B1'
|
'Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html'
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -1023,7 +1023,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
});
|
});
|
||||||
expect(response.status).toEqual(302);
|
expect(response.status).toEqual(302);
|
||||||
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&username=zxcv/;
|
const re = /http:\/\/localhost:8378\/1\/apps\/choose_password\?token=([a-zA-Z0-9]+)\&id=test\&/;
|
||||||
const match = response.text.match(re);
|
const match = response.text.match(re);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
fail('should have a token');
|
fail('should have a token');
|
||||||
@@ -1081,7 +1081,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
await request({
|
await request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
||||||
body: `new_password=user1&token=12345&username=Johnny`,
|
body: `new_password=user1&token=12345`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
@@ -1150,6 +1150,80 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can resend email using an expired reset password token', async () => {
|
||||||
|
const user = new Parse.User();
|
||||||
|
const emailAdapter = {
|
||||||
|
sendVerificationEmail: () => {},
|
||||||
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
|
sendMail: () => {},
|
||||||
|
};
|
||||||
|
await reconfigureServer({
|
||||||
|
appName: 'emailVerifyToken',
|
||||||
|
verifyUserEmails: true,
|
||||||
|
emailAdapter: emailAdapter,
|
||||||
|
emailVerifyTokenValidityDuration: 5, // 5 seconds
|
||||||
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
|
passwordPolicy: {
|
||||||
|
resetTokenValidityDuration: 5 * 60, // 5 minutes
|
||||||
|
},
|
||||||
|
silent: false,
|
||||||
|
});
|
||||||
|
user.setUsername('test');
|
||||||
|
user.setPassword('password');
|
||||||
|
user.set('email', 'user@example.com');
|
||||||
|
await user.signUp();
|
||||||
|
await Parse.User.requestPasswordReset('user@example.com');
|
||||||
|
|
||||||
|
await Parse.Server.database.update(
|
||||||
|
'_User',
|
||||||
|
{ objectId: user.id },
|
||||||
|
{
|
||||||
|
_perishable_token_expires_at: Parse._encode(new Date('2000')),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = await Parse.Server.database.find(
|
||||||
|
'_User',
|
||||||
|
{ objectId: user.id },
|
||||||
|
{},
|
||||||
|
Auth.maintenance(Parse.Server)
|
||||||
|
);
|
||||||
|
const token = obj[0]._perishable_token;
|
||||||
|
const res = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/test/request_password_reset`,
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
token,
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await request({
|
||||||
|
url: `http://localhost:8378/1/requestPasswordReset`,
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
obj = await Parse.Server.database.find(
|
||||||
|
'_User',
|
||||||
|
{ objectId: user.id },
|
||||||
|
{},
|
||||||
|
Auth.maintenance(Parse.Server)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(obj._perishable_token).not.toBe(token);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw on an invalid reset password', async () => {
|
it('should throw on an invalid reset password', async () => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
appName: 'coolapp',
|
appName: 'coolapp',
|
||||||
|
|||||||
102
spec/helper.js
102
spec/helper.js
@@ -227,65 +227,55 @@ beforeAll(async () => {
|
|||||||
Parse.serverURL = 'http://localhost:' + port + '/1';
|
Parse.serverURL = 'http://localhost:' + port + '/1';
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function (done) {
|
global.afterEachFn = async () => {
|
||||||
const afterLogOut = async () => {
|
|
||||||
// Jasmine process uses one connection
|
|
||||||
if (Object.keys(openConnections).length > 1) {
|
|
||||||
console.warn(`There were ${Object.keys(openConnections).length} open connections to the server left after the test finished`);
|
|
||||||
}
|
|
||||||
await TestUtils.destroyAllDataPermanently(true);
|
|
||||||
SchemaCache.clear();
|
|
||||||
if (didChangeConfiguration) {
|
|
||||||
await reconfigureServer();
|
|
||||||
} else {
|
|
||||||
await databaseAdapter.performInitialization({ VolatileClassesSchemas });
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
Parse.Cloud._removeAllHooks();
|
Parse.Cloud._removeAllHooks();
|
||||||
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient();
|
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient();
|
||||||
defaults.protectedFields = { _User: { '*': ['email'] } };
|
defaults.protectedFields = { _User: { '*': ['email'] } };
|
||||||
databaseAdapter
|
|
||||||
.getAllClasses()
|
const allSchemas = await databaseAdapter.getAllClasses().catch(() => []);
|
||||||
.then(allSchemas => {
|
|
||||||
allSchemas.forEach(schema => {
|
allSchemas.forEach(schema => {
|
||||||
const className = schema.className;
|
const className = schema.className;
|
||||||
expect(className).toEqual({
|
expect(className).toEqual({
|
||||||
asymmetricMatch: className => {
|
asymmetricMatch: className => {
|
||||||
if (!className.startsWith('_')) {
|
if (!className.startsWith('_')) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
// Other system classes will break Parse.com, so make sure that we don't save anything to _SCHEMA that will
|
return [
|
||||||
// break it.
|
'_User',
|
||||||
return (
|
'_Installation',
|
||||||
[
|
'_Role',
|
||||||
'_User',
|
'_Session',
|
||||||
'_Installation',
|
'_Product',
|
||||||
'_Role',
|
'_Audience',
|
||||||
'_Session',
|
'_Idempotency',
|
||||||
'_Product',
|
].includes(className);
|
||||||
'_Audience',
|
},
|
||||||
'_Idempotency',
|
});
|
||||||
].indexOf(className) >= 0
|
});
|
||||||
);
|
|
||||||
}
|
await Parse.User.logOut().catch(() => {});
|
||||||
},
|
|
||||||
});
|
// Connection close events are not immediate on node 10+, so wait a bit
|
||||||
});
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
})
|
|
||||||
.then(() => Parse.User.logOut())
|
// After logout operations
|
||||||
.then(
|
if (Object.keys(openConnections).length > 1) {
|
||||||
() => {},
|
console.warn(
|
||||||
() => {}
|
`There were ${Object.keys(openConnections).length} open connections to the server left after the test finished`
|
||||||
) // swallow errors
|
);
|
||||||
.then(() => {
|
}
|
||||||
// Connection close events are not immediate on node 10+... wait a bit
|
|
||||||
return new Promise(resolve => {
|
await TestUtils.destroyAllDataPermanently(true);
|
||||||
setTimeout(resolve, 0);
|
SchemaCache.clear();
|
||||||
});
|
|
||||||
})
|
if (didChangeConfiguration) {
|
||||||
.then(afterLogOut);
|
await reconfigureServer();
|
||||||
});
|
} else {
|
||||||
|
await databaseAdapter.performInitialization({ VolatileClassesSchemas });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterEach(global.afterEachFn);
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
global.displayTestStats();
|
global.displayTestStats();
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ global.retryFlakyTests = function() {
|
|||||||
}
|
}
|
||||||
if (isFlaky) {
|
if (isFlaky) {
|
||||||
retryMap[spec.result.fullName] = (retryMap[spec.result.fullName] || 0) + 1;
|
retryMap[spec.result.fullName] = (retryMap[spec.result.fullName] || 0) + 1;
|
||||||
|
await global.afterEachFn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exceptionCaught) {
|
if (exceptionCaught) {
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ export class UserController extends AdaptableController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyEmail(username, token) {
|
async verifyEmail(token) {
|
||||||
if (!this.shouldVerifyEmails) {
|
if (!this.shouldVerifyEmails) {
|
||||||
// Trying to verify email when not enabled
|
// Trying to verify email when not enabled
|
||||||
// TODO: Better error here.
|
// TODO: Better error here.
|
||||||
throw undefined;
|
throw undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = { username: username, _email_verify_token: token };
|
const query = { _email_verify_token: token };
|
||||||
const updateFields = {
|
const updateFields = {
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
_email_verify_token: { __op: 'Delete' },
|
_email_verify_token: { __op: 'Delete' },
|
||||||
@@ -82,50 +82,45 @@ export class UserController extends AdaptableController {
|
|||||||
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
||||||
}
|
}
|
||||||
const maintenanceAuth = Auth.maintenance(this.config);
|
const maintenanceAuth = Auth.maintenance(this.config);
|
||||||
var findUserForEmailVerification = await RestQuery({
|
const restQuery = await RestQuery({
|
||||||
method: RestQuery.Method.get,
|
method: RestQuery.Method.get,
|
||||||
config: this.config,
|
config: this.config,
|
||||||
auth: maintenanceAuth,
|
auth: maintenanceAuth,
|
||||||
className: '_User',
|
className: '_User',
|
||||||
restWhere: {
|
restWhere: query,
|
||||||
username,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return findUserForEmailVerification.execute().then(result => {
|
|
||||||
if (result.results.length && result.results[0].emailVerified) {
|
|
||||||
return Promise.resolve(result.results.length[0]);
|
|
||||||
} else if (result.results.length) {
|
|
||||||
query.objectId = result.results[0].objectId;
|
|
||||||
}
|
|
||||||
return rest.update(this.config, maintenanceAuth, '_User', query, updateFields);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const result = await restQuery.execute();
|
||||||
|
if (result.results.length) {
|
||||||
|
query.objectId = result.results[0].objectId;
|
||||||
|
}
|
||||||
|
return await rest.update(this.config, maintenanceAuth, '_User', query, updateFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkResetTokenValidity(username, token) {
|
async checkResetTokenValidity(token) {
|
||||||
return this.config.database
|
const results = await this.config.database.find(
|
||||||
.find(
|
'_User',
|
||||||
'_User',
|
{
|
||||||
{
|
_perishable_token: token,
|
||||||
username: username,
|
},
|
||||||
_perishable_token: token,
|
{ limit: 1 },
|
||||||
},
|
Auth.maintenance(this.config)
|
||||||
{ limit: 1 },
|
);
|
||||||
Auth.maintenance(this.config)
|
if (results.length !== 1) {
|
||||||
)
|
throw 'Failed to reset password: username / email / token is invalid';
|
||||||
.then(results => {
|
}
|
||||||
if (results.length != 1) {
|
|
||||||
throw 'Failed to reset password: username / email / token is invalid';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||||
let expiresDate = results[0]._perishable_token_expires_at;
|
let expiresDate = results[0]._perishable_token_expires_at;
|
||||||
if (expiresDate && expiresDate.__type == 'Date') {
|
if (expiresDate && expiresDate.__type == 'Date') {
|
||||||
expiresDate = new Date(expiresDate.iso);
|
expiresDate = new Date(expiresDate.iso);
|
||||||
}
|
}
|
||||||
if (expiresDate < new Date()) { throw 'The password reset link has expired'; }
|
if (expiresDate < new Date()) {
|
||||||
}
|
throw 'The password reset link has expired';
|
||||||
return results[0];
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return results[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserIfNeeded(user) {
|
async getUserIfNeeded(user) {
|
||||||
@@ -136,6 +131,9 @@ export class UserController extends AdaptableController {
|
|||||||
if (user.email) {
|
if (user.email) {
|
||||||
where.email = user.email;
|
where.email = user.email;
|
||||||
}
|
}
|
||||||
|
if (user._email_verify_token) {
|
||||||
|
where._email_verify_token = user._email_verify_token;
|
||||||
|
}
|
||||||
|
|
||||||
var query = await RestQuery({
|
var query = await RestQuery({
|
||||||
method: RestQuery.Method.get,
|
method: RestQuery.Method.get,
|
||||||
@@ -173,9 +171,7 @@ export class UserController extends AdaptableController {
|
|||||||
if (!shouldSendEmail) {
|
if (!shouldSendEmail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const username = encodeURIComponent(fetchedUser.username);
|
const link = buildEmailLink(this.config.verifyEmailURL, token, this.config);
|
||||||
|
|
||||||
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
|
|
||||||
const options = {
|
const options = {
|
||||||
appName: this.config.appName,
|
appName: this.config.appName,
|
||||||
link: link,
|
link: link,
|
||||||
@@ -221,8 +217,8 @@ export class UserController extends AdaptableController {
|
|||||||
return this.config.database.update('_User', { username: user.username }, user);
|
return this.config.database.update('_User', { username: user.username }, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resendVerificationEmail(username, req) {
|
async resendVerificationEmail(username, req, token) {
|
||||||
const aUser = await this.getUserIfNeeded({ username: username });
|
const aUser = await this.getUserIfNeeded({ username, _email_verify_token: token });
|
||||||
if (!aUser || aUser.emailVerified) {
|
if (!aUser || aUser.emailVerified) {
|
||||||
throw undefined;
|
throw undefined;
|
||||||
}
|
}
|
||||||
@@ -286,9 +282,8 @@ export class UserController extends AdaptableController {
|
|||||||
user = await this.setPasswordResetToken(email);
|
user = await this.setPasswordResetToken(email);
|
||||||
}
|
}
|
||||||
const token = encodeURIComponent(user._perishable_token);
|
const token = encodeURIComponent(user._perishable_token);
|
||||||
const username = encodeURIComponent(user.username);
|
|
||||||
|
|
||||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
const link = buildEmailLink(this.config.requestResetPasswordURL, token, this.config);
|
||||||
const options = {
|
const options = {
|
||||||
appName: this.config.appName,
|
appName: this.config.appName,
|
||||||
link: link,
|
link: link,
|
||||||
@@ -304,21 +299,20 @@ export class UserController extends AdaptableController {
|
|||||||
return Promise.resolve(user);
|
return Promise.resolve(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePassword(username, token, password) {
|
async updatePassword(token, password) {
|
||||||
return this.checkResetTokenValidity(username, token)
|
try {
|
||||||
.then(user => updateUserPassword(user, password, this.config))
|
const rawUser = await this.checkResetTokenValidity(token);
|
||||||
.then(user => {
|
const user = await updateUserPassword(rawUser, password, this.config);
|
||||||
const accountLockoutPolicy = new AccountLockout(user, this.config);
|
|
||||||
return accountLockoutPolicy.unlockAccount();
|
const accountLockoutPolicy = new AccountLockout(user, this.config);
|
||||||
})
|
return await accountLockoutPolicy.unlockAccount();
|
||||||
.catch(error => {
|
} catch (error) {
|
||||||
if (error && error.message) {
|
if (error && error.message) {
|
||||||
// in case of Parse.Error, fail with the error message only
|
// in case of Parse.Error, fail with the error message only
|
||||||
return Promise.reject(error.message);
|
return Promise.reject(error.message);
|
||||||
} else {
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultVerificationEmail({ link, user, appName }) {
|
defaultVerificationEmail({ link, user, appName }) {
|
||||||
@@ -368,17 +362,14 @@ function updateUserPassword(user, password, config) {
|
|||||||
.then(() => user);
|
.then(() => user);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEmailLink(destination, username, token, config) {
|
function buildEmailLink(destination, token, config) {
|
||||||
const usernameAndToken = `token=${token}&username=${username}`;
|
token = `token=${token}`;
|
||||||
|
|
||||||
if (config.parseFrameURL) {
|
if (config.parseFrameURL) {
|
||||||
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
||||||
|
|
||||||
return `${config.parseFrameURL}?link=${encodeURIComponent(
|
return `${config.parseFrameURL}?link=${encodeURIComponent(destinationWithoutHost)}&${token}`;
|
||||||
destinationWithoutHost
|
|
||||||
)}&${usernameAndToken}`;
|
|
||||||
} else {
|
} else {
|
||||||
return `${destination}?${usernameAndToken}`;
|
return `${destination}?${token}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -302,11 +302,8 @@ const load = parseGraphQLSchema => {
|
|||||||
type: new GraphQLNonNull(GraphQLBoolean),
|
type: new GraphQLNonNull(GraphQLBoolean),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutateAndGetPayload: async ({ username, password, token }, context) => {
|
mutateAndGetPayload: async ({ password, token }, context) => {
|
||||||
const { config } = context;
|
const { config } = context;
|
||||||
if (!username) {
|
|
||||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'you must provide a username');
|
|
||||||
}
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'you must provide a password');
|
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'you must provide a password');
|
||||||
}
|
}
|
||||||
@@ -315,7 +312,7 @@ const load = parseGraphQLSchema => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userController = config.userController;
|
const userController = config.userController;
|
||||||
await userController.updatePassword(username, token, password);
|
await userController.updatePassword(token, password);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,30 +83,24 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
|
|
||||||
verifyEmail(req) {
|
verifyEmail(req) {
|
||||||
const config = req.config;
|
const config = req.config;
|
||||||
const { username, token: rawToken } = req.query;
|
const { token: rawToken } = req.query;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
this.invalidRequest();
|
this.invalidRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token || !username) {
|
if (!token) {
|
||||||
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userController = config.userController;
|
const userController = config.userController;
|
||||||
return userController.verifyEmail(username, token).then(
|
return userController.verifyEmail(token).then(
|
||||||
() => {
|
() => {
|
||||||
const params = {
|
return this.goToPage(req, pages.emailVerificationSuccess);
|
||||||
[pageParams.username]: username,
|
|
||||||
};
|
|
||||||
return this.goToPage(req, pages.emailVerificationSuccess, params);
|
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
const params = {
|
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||||
[pageParams.username]: username,
|
|
||||||
};
|
|
||||||
return this.goToPage(req, pages.emailVerificationLinkExpired, params);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -114,18 +108,19 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
resendVerificationEmail(req) {
|
resendVerificationEmail(req) {
|
||||||
const config = req.config;
|
const config = req.config;
|
||||||
const username = req.body.username;
|
const username = req.body.username;
|
||||||
|
const token = req.body.token;
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
this.invalidRequest();
|
this.invalidRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) {
|
if (!username && !token) {
|
||||||
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userController = config.userController;
|
const userController = config.userController;
|
||||||
|
|
||||||
return userController.resendVerificationEmail(username, req).then(
|
return userController.resendVerificationEmail(username, req, token).then(
|
||||||
() => {
|
() => {
|
||||||
return this.goToPage(req, pages.emailVerificationSendSuccess);
|
return this.goToPage(req, pages.emailVerificationSendSuccess);
|
||||||
},
|
},
|
||||||
@@ -154,28 +149,24 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
this.invalidRequest();
|
this.invalidRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, token: rawToken } = req.query;
|
const { token: rawToken } = req.query;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
if (!username || !token) {
|
if (!token) {
|
||||||
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.userController.checkResetTokenValidity(username, token).then(
|
return config.userController.checkResetTokenValidity(token).then(
|
||||||
() => {
|
() => {
|
||||||
const params = {
|
const params = {
|
||||||
[pageParams.token]: token,
|
[pageParams.token]: token,
|
||||||
[pageParams.username]: username,
|
|
||||||
[pageParams.appId]: config.applicationId,
|
[pageParams.appId]: config.applicationId,
|
||||||
[pageParams.appName]: config.appName,
|
[pageParams.appName]: config.appName,
|
||||||
};
|
};
|
||||||
return this.goToPage(req, pages.passwordReset, params);
|
return this.goToPage(req, pages.passwordReset, params);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
const params = {
|
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||||
[pageParams.username]: username,
|
|
||||||
};
|
|
||||||
return this.goToPage(req, pages.passwordResetLinkInvalid, params);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -187,17 +178,13 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
this.invalidRequest();
|
this.invalidRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, new_password, token: rawToken } = req.body;
|
const { new_password, token: rawToken } = req.body;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
if ((!username || !token || !new_password) && req.xhr === false) {
|
if ((!token || !new_password) && req.xhr === false) {
|
||||||
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'Missing username');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
||||||
}
|
}
|
||||||
@@ -207,7 +194,7 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return config.userController
|
return config.userController
|
||||||
.updatePassword(username, token, new_password)
|
.updatePassword(token, new_password)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -235,16 +222,18 @@ export class PagesRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const query = result.success
|
const query = result.success
|
||||||
? {
|
? {}
|
||||||
[pageParams.username]: username,
|
|
||||||
}
|
|
||||||
: {
|
: {
|
||||||
[pageParams.username]: username,
|
|
||||||
[pageParams.token]: token,
|
[pageParams.token]: token,
|
||||||
[pageParams.appId]: config.applicationId,
|
[pageParams.appId]: config.applicationId,
|
||||||
[pageParams.error]: result.err,
|
[pageParams.error]: result.err,
|
||||||
[pageParams.appName]: config.appName,
|
[pageParams.appName]: config.appName,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (result?.err === 'The password reset link has expired') {
|
||||||
|
delete query[pageParams.token];
|
||||||
|
query[pageParams.token] = token;
|
||||||
|
}
|
||||||
const page = result.success ? pages.passwordResetSuccess : pages.passwordReset;
|
const page = result.success ? pages.passwordResetSuccess : pages.passwordReset;
|
||||||
|
|
||||||
return this.goToPage(req, page, query, false);
|
return this.goToPage(req, page, query, false);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
verifyEmail(req) {
|
verifyEmail(req) {
|
||||||
const { username, token: rawToken } = req.query;
|
const { token: rawToken } = req.query;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
const appId = req.params.appId;
|
const appId = req.params.appId;
|
||||||
@@ -33,21 +33,20 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
return this.missingPublicServerURL();
|
return this.missingPublicServerURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token || !username) {
|
if (!token) {
|
||||||
return this.invalidLink(req);
|
return this.invalidLink(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userController = config.userController;
|
const userController = config.userController;
|
||||||
return userController.verifyEmail(username, token).then(
|
return userController.verifyEmail(token).then(
|
||||||
() => {
|
() => {
|
||||||
const params = qs.stringify({ username });
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
status: 302,
|
status: 302,
|
||||||
location: `${config.verifyEmailSuccessURL}?${params}`,
|
location: `${config.verifyEmailSuccessURL}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
return this.invalidVerificationLink(req);
|
return this.invalidVerificationLink(req, token);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -65,13 +64,15 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
return this.missingPublicServerURL();
|
return this.missingPublicServerURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) {
|
const token = req.body.token;
|
||||||
|
|
||||||
|
if (!username && !token) {
|
||||||
return this.invalidLink(req);
|
return this.invalidLink(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userController = config.userController;
|
const userController = config.userController;
|
||||||
|
|
||||||
return userController.resendVerificationEmail(username, req).then(
|
return userController.resendVerificationEmail(username, req, token).then(
|
||||||
() => {
|
() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -125,19 +126,18 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
return this.missingPublicServerURL();
|
return this.missingPublicServerURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, token: rawToken } = req.query;
|
const { token: rawToken } = req.query;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
if (!username || !token) {
|
if (!token) {
|
||||||
return this.invalidLink(req);
|
return this.invalidLink(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.userController.checkResetTokenValidity(username, token).then(
|
return config.userController.checkResetTokenValidity(token).then(
|
||||||
() => {
|
() => {
|
||||||
const params = qs.stringify({
|
const params = qs.stringify({
|
||||||
token,
|
token,
|
||||||
id: config.applicationId,
|
id: config.applicationId,
|
||||||
username,
|
|
||||||
app: config.appName,
|
app: config.appName,
|
||||||
});
|
});
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -162,17 +162,13 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
return this.missingPublicServerURL();
|
return this.missingPublicServerURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, new_password, token: rawToken } = req.body;
|
const { new_password, token: rawToken } = req.body;
|
||||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||||
|
|
||||||
if ((!username || !token || !new_password) && req.xhr === false) {
|
if ((!token || !new_password) && req.xhr === false) {
|
||||||
return this.invalidLink(req);
|
return this.invalidLink(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'Missing username');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
||||||
}
|
}
|
||||||
@@ -182,7 +178,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return config.userController
|
return config.userController
|
||||||
.updatePassword(username, token, new_password)
|
.updatePassword(token, new_password)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -197,13 +193,18 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
const params = qs.stringify({
|
const queryString = {
|
||||||
username: username,
|
|
||||||
token: token,
|
token: token,
|
||||||
id: config.applicationId,
|
id: config.applicationId,
|
||||||
error: result.err,
|
error: result.err,
|
||||||
app: config.appName,
|
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 (req.xhr) {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -217,9 +218,8 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodedUsername = encodeURIComponent(username);
|
|
||||||
const location = result.success
|
const location = result.success
|
||||||
? `${config.passwordResetSuccessURL}?username=${encodedUsername}`
|
? `${config.passwordResetSuccessURL}`
|
||||||
: `${config.choosePasswordURL}?${params}`;
|
: `${config.choosePasswordURL}?${params}`;
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -236,12 +236,12 @@ export class PublicAPIRouter extends PromiseRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidVerificationLink(req) {
|
invalidVerificationLink(req, token) {
|
||||||
const config = req.config;
|
const config = req.config;
|
||||||
if (req.query.username && req.params.appId) {
|
if (req.params.appId) {
|
||||||
const params = qs.stringify({
|
const params = qs.stringify({
|
||||||
username: req.query.username,
|
|
||||||
appId: req.params.appId,
|
appId: req.params.appId,
|
||||||
|
token,
|
||||||
});
|
});
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
status: 302,
|
status: 302,
|
||||||
|
|||||||
@@ -438,10 +438,20 @@ export class UsersRouter extends ClassesRouter {
|
|||||||
async handleResetRequest(req) {
|
async handleResetRequest(req) {
|
||||||
this._throwOnBadEmailConfig(req);
|
this._throwOnBadEmailConfig(req);
|
||||||
|
|
||||||
const { email } = req.body;
|
let email = req.body.email;
|
||||||
if (!email) {
|
const token = req.body.token;
|
||||||
|
if (!email && !token) {
|
||||||
throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email');
|
throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email');
|
||||||
}
|
}
|
||||||
|
if (token) {
|
||||||
|
const results = await req.config.database.find('_User', {
|
||||||
|
_perishable_token: token,
|
||||||
|
_perishable_token_expires_at: { $lt: Parse._encode(new Date()) },
|
||||||
|
});
|
||||||
|
if (results && results[0] && results[0].email) {
|
||||||
|
email = results[0].email;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (typeof email !== 'string') {
|
if (typeof email !== 'string') {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_EMAIL_ADDRESS,
|
Parse.Error.INVALID_EMAIL_ADDRESS,
|
||||||
|
|||||||
Reference in New Issue
Block a user