fix: Indexes _email_verify_token for email verification and _perishable_token password reset are not created automatically (#9893)

This commit is contained in:
Manuel
2025-11-01 13:52:23 +01:00
committed by GitHub
parent 00f8d4cda9
commit 62dd3c565a
6 changed files with 192 additions and 1 deletions

View File

@@ -413,6 +413,8 @@ describe('DatabaseController', function () {
case_insensitive_username: { username: 1 },
case_insensitive_email: { email: 1 },
email_1: { email: 1 },
_email_verify_token: { _email_verify_token: 1 },
_perishable_token: { _perishable_token: 1 },
});
}
);
@@ -437,9 +439,153 @@ describe('DatabaseController', function () {
_id_: { _id: 1 },
username_1: { username: 1 },
email_1: { email: 1 },
_email_verify_token: { _email_verify_token: 1 },
_perishable_token: { _perishable_token: 1 },
});
}
);
it_only_db('mongo')(
'should use _email_verify_token index in email verification',
async () => {
const TestUtils = require('../lib/TestUtils');
let emailVerificationLink;
const emailSentPromise = TestUtils.resolvingPromise();
const emailAdapter = {
sendVerificationEmail: options => {
emailVerificationLink = options.link;
emailSentPromise.resolve();
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
databaseURI: 'mongodb://localhost:27017/testEmailVerifyTokenIndexStats',
databaseAdapter: undefined,
appName: 'test',
verifyUserEmails: true,
emailAdapter: emailAdapter,
publicServerURL: 'http://localhost:8378/1',
});
// Create a user to trigger email verification
const user = new Parse.User();
user.setUsername('statsuser');
user.setPassword('password');
user.set('email', 'stats@example.com');
await user.signUp();
await emailSentPromise;
// Get index stats before the query
const config = Config.get(Parse.applicationId);
const collection = await config.database.adapter._adaptiveCollection('_User');
const statsBefore = await collection._mongoCollection.aggregate([
{ $indexStats: {} },
]).toArray();
const emailVerifyIndexBefore = statsBefore.find(
stat => stat.name === '_email_verify_token'
);
const accessesBefore = emailVerifyIndexBefore?.accesses?.ops || 0;
// Perform email verification (this should use the index)
const request = require('../lib/request');
await request({
url: emailVerificationLink,
followRedirects: false,
});
// Get index stats after the query
const statsAfter = await collection._mongoCollection.aggregate([
{ $indexStats: {} },
]).toArray();
const emailVerifyIndexAfter = statsAfter.find(
stat => stat.name === '_email_verify_token'
);
const accessesAfter = emailVerifyIndexAfter?.accesses?.ops || 0;
// Verify the index was actually used
expect(accessesAfter).toBeGreaterThan(accessesBefore);
expect(emailVerifyIndexAfter).toBeDefined();
// Verify email verification succeeded
await user.fetch();
expect(user.get('emailVerified')).toBe(true);
}
);
it_only_db('mongo')(
'should use _perishable_token index in password reset',
async () => {
const TestUtils = require('../lib/TestUtils');
let passwordResetLink;
const emailSentPromise = TestUtils.resolvingPromise();
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
passwordResetLink = options.link;
emailSentPromise.resolve();
},
sendMail: () => {},
};
await reconfigureServer({
databaseURI: 'mongodb://localhost:27017/testPerishableTokenIndexStats',
databaseAdapter: undefined,
appName: 'test',
emailAdapter: emailAdapter,
publicServerURL: 'http://localhost:8378/1',
});
// Create a user
const user = new Parse.User();
user.setUsername('statsuser2');
user.setPassword('oldpassword');
user.set('email', 'stats2@example.com');
await user.signUp();
// Request password reset
await Parse.User.requestPasswordReset('stats2@example.com');
await emailSentPromise;
const url = new URL(passwordResetLink);
const token = url.searchParams.get('token');
// Get index stats before the query
const config = Config.get(Parse.applicationId);
const collection = await config.database.adapter._adaptiveCollection('_User');
const statsBefore = await collection._mongoCollection.aggregate([
{ $indexStats: {} },
]).toArray();
const perishableTokenIndexBefore = statsBefore.find(
stat => stat.name === '_perishable_token'
);
const accessesBefore = perishableTokenIndexBefore?.accesses?.ops || 0;
// Perform password reset (this should use the index)
const request = require('../lib/request');
await request({
method: 'POST',
url: 'http://localhost:8378/1/apps/test/request_password_reset',
body: { new_password: 'newpassword', token, username: 'statsuser2' },
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
followRedirects: false,
});
// Get index stats after the query
const statsAfter = await collection._mongoCollection.aggregate([
{ $indexStats: {} },
]).toArray();
const perishableTokenIndexAfter = statsAfter.find(
stat => stat.name === '_perishable_token'
);
const accessesAfter = perishableTokenIndexAfter?.accesses?.ops || 0;
// Verify the index was actually used
expect(accessesAfter).toBeGreaterThan(accessesBefore);
expect(perishableTokenIndexAfter).toBeDefined();
}
);
});
describe('convertEmailToLowercase', () => {

View File

@@ -25,6 +25,7 @@ module.exports = [
it_id: "readonly",
fit_id: "readonly",
it_only_db: "readonly",
fit_only_db: "readonly",
it_only_mongodb_version: "readonly",
it_only_postgres_version: "readonly",
it_only_node_version: "readonly",

View File

@@ -515,6 +515,17 @@ global.it_only_db = db => {
}
};
global.fit_only_db = db => {
if (
process.env.PARSE_SERVER_TEST_DB === db ||
(!process.env.PARSE_SERVER_TEST_DB && db == 'mongo')
) {
return fit;
} else {
return xit;
}
};
global.it_only_mongodb_version = version => {
if (!semver.validRange(version)) {
throw new Error('Invalid version range');