fix: GridFSBucketAdapter throws when using some Parse Server specific options in MongoDB database options (#9915)

This commit is contained in:
Manuel
2025-11-08 18:41:45 +01:00
committed by GitHub
parent 502a512028
commit d3d4003570
5 changed files with 53 additions and 28 deletions

View File

@@ -24,10 +24,20 @@ describe_only_db('mongo')('GridFSBucket', () => {
const databaseURI = 'mongodb://localhost:27017/parse'; const databaseURI = 'mongodb://localhost:27017/parse';
const gfsAdapter = new GridFSBucketAdapter(databaseURI, { const gfsAdapter = new GridFSBucketAdapter(databaseURI, {
retryWrites: true, retryWrites: true,
// these are not supported by the mongo client // Parse Server-specific options that should be filtered out before passing to MongoDB client
allowPublicExplain: true,
enableSchemaHooks: true, enableSchemaHooks: true,
schemaCacheTtl: 5000, schemaCacheTtl: 5000,
maxTimeMS: 30000, maxTimeMS: 30000,
disableIndexFieldValidation: true,
logClientEvents: [{ name: 'commandStarted' }],
createIndexUserUsername: true,
createIndexUserUsernameCaseInsensitive: true,
createIndexUserEmail: true,
createIndexUserEmailCaseInsensitive: true,
createIndexUserEmailVerifyToken: true,
createIndexUserPasswordResetToken: true,
createIndexRoleName: true,
}); });
const db = await gfsAdapter._connect(); const db = await gfsAdapter._connect();

View File

@@ -9,7 +9,7 @@
// @flow-disable-next // @flow-disable-next
import { MongoClient, GridFSBucket, Db } from 'mongodb'; import { MongoClient, GridFSBucket, Db } from 'mongodb';
import { FilesAdapter, validateFilename } from './FilesAdapter'; import { FilesAdapter, validateFilename } from './FilesAdapter';
import defaults from '../../defaults'; import defaults, { ParseServerDatabaseOptions } from '../../defaults';
const crypto = require('crypto'); const crypto = require('crypto');
export class GridFSBucketAdapter extends FilesAdapter { export class GridFSBucketAdapter extends FilesAdapter {
@@ -34,10 +34,10 @@ export class GridFSBucketAdapter extends FilesAdapter {
.digest('base64') .digest('base64')
.substring(0, 32) .substring(0, 32)
: null; : null;
const defaultMongoOptions = { const defaultMongoOptions = {};
};
const _mongoOptions = Object.assign(defaultMongoOptions, mongoOptions); const _mongoOptions = Object.assign(defaultMongoOptions, mongoOptions);
for (const key of ['enableSchemaHooks', 'schemaCacheTtl', 'maxTimeMS', 'disableIndexFieldValidation']) { // Remove Parse Server-specific options that should not be passed to MongoDB client
for (const key of ParseServerDatabaseOptions) {
delete _mongoOptions[key]; delete _mongoOptions[key];
} }
this._mongoOptions = _mongoOptions; this._mongoOptions = _mongoOptions;

View File

@@ -16,7 +16,7 @@ import {
import Parse from 'parse/node'; import Parse from 'parse/node';
// @flow-disable-next // @flow-disable-next
import _ from 'lodash'; import _ from 'lodash';
import defaults from '../../../defaults'; import defaults, { ParseServerDatabaseOptions } from '../../../defaults';
import logger from '../../../logger'; import logger from '../../../logger';
import Utils from '../../../Utils'; import Utils from '../../../Utils';
@@ -147,7 +147,6 @@ export class MongoStorageAdapter implements StorageAdapter {
constructor({ uri = defaults.DefaultMongoURI, collectionPrefix = '', mongoOptions = {} }: any) { constructor({ uri = defaults.DefaultMongoURI, collectionPrefix = '', mongoOptions = {} }: any) {
this._uri = uri; this._uri = uri;
this._collectionPrefix = collectionPrefix; this._collectionPrefix = collectionPrefix;
this._mongoOptions = { ...mongoOptions };
this._onchange = () => {}; this._onchange = () => {};
// MaxTimeMS is not a global MongoDB client option, it is applied per operation. // MaxTimeMS is not a global MongoDB client option, it is applied per operation.
@@ -158,24 +157,12 @@ export class MongoStorageAdapter implements StorageAdapter {
this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation; this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation;
this._logClientEvents = mongoOptions.logClientEvents; this._logClientEvents = mongoOptions.logClientEvents;
// Remove Parse Server-specific options that should not be passed to MongoDB client // Create a copy of mongoOptions and remove Parse Server-specific options that should not
// Note: We only delete from this._mongoOptions, not from the original mongoOptions object, // be passed to MongoDB client. Note: We only delete from this._mongoOptions, not from the
// because other components (like DatabaseController) need access to these options // original mongoOptions object, because other components (like DatabaseController) need
for (const key of [ // access to these options.
'allowPublicExplain', this._mongoOptions = { ...mongoOptions };
'enableSchemaHooks', for (const key of ParseServerDatabaseOptions) {
'schemaCacheTtl',
'maxTimeMS',
'disableIndexFieldValidation',
'logClientEvents',
'createIndexUserUsername',
'createIndexUserUsernameCaseInsensitive',
'createIndexUserEmail',
'createIndexUserEmailCaseInsensitive',
'createIndexUserEmailVerifyToken',
'createIndexUserPasswordResetToken',
'createIndexRoleName',
]) {
delete this._mongoOptions[key]; delete this._mongoOptions[key];
} }
} }

View File

@@ -33,3 +33,21 @@ const computedDefaults = {
export default Object.assign({}, DefinitionDefaults, computedDefaults); export default Object.assign({}, DefinitionDefaults, computedDefaults);
export const DefaultMongoURI = DefinitionDefaults.databaseURI; export const DefaultMongoURI = DefinitionDefaults.databaseURI;
// Parse Server-specific database options that should be filtered out
// before passing to MongoDB client
export const ParseServerDatabaseOptions = [
'allowPublicExplain',
'createIndexRoleName',
'createIndexUserEmail',
'createIndexUserEmailCaseInsensitive',
'createIndexUserEmailVerifyToken',
'createIndexUserPasswordResetToken',
'createIndexUserUsername',
'createIndexUserUsernameCaseInsensitive',
'disableIndexFieldValidation',
'enableSchemaHooks',
'logClientEvents',
'maxTimeMS',
'schemaCacheTtl',
];

View File

@@ -228,9 +228,21 @@ export interface FileUploadOptions {
} }
export interface DatabaseOptions { export interface DatabaseOptions {
// Parse Server custom options // Parse Server custom options
allowPublicExplain?: boolean;
createIndexRoleName?: boolean;
createIndexUserEmail?: boolean;
createIndexUserEmailCaseInsensitive?: boolean;
createIndexUserEmailVerifyToken?: boolean;
createIndexUserPasswordResetToken?: boolean;
createIndexUserUsername?: boolean;
createIndexUserUsernameCaseInsensitive?: boolean;
disableIndexFieldValidation?: boolean;
enableSchemaHooks?: boolean; enableSchemaHooks?: boolean;
logClientEvents?: any[];
// maxTimeMS is a MongoDB option but Parse Server applies it per-operation, not as a global client option
maxTimeMS?: number;
schemaCacheTtl?: number; schemaCacheTtl?: number;
// MongoDB driver options // MongoDB driver options
appName?: string; appName?: string;
authMechanism?: string; authMechanism?: string;
@@ -238,7 +250,6 @@ export interface DatabaseOptions {
authSource?: string; authSource?: string;
autoSelectFamily?: boolean; autoSelectFamily?: boolean;
autoSelectFamilyAttemptTimeout?: number; autoSelectFamilyAttemptTimeout?: number;
allowPublicExplain?: boolean;
compressors?: string[] | string; compressors?: string[] | string;
connectTimeoutMS?: number; connectTimeoutMS?: number;
directConnection?: boolean; directConnection?: boolean;
@@ -250,7 +261,6 @@ export interface DatabaseOptions {
maxIdleTimeMS?: number; maxIdleTimeMS?: number;
maxPoolSize?: number; maxPoolSize?: number;
maxStalenessSeconds?: number; maxStalenessSeconds?: number;
maxTimeMS?: number;
minPoolSize?: number; minPoolSize?: number;
proxyHost?: string; proxyHost?: string;
proxyPassword?: string; proxyPassword?: string;