From e4e87652ca4ca656dec7213260ba89998d8fb8a9 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Thu, 31 Oct 2024 09:50:20 +0200 Subject: [PATCH 1/2] Reduce required permissions for replicating from a single database. --- .../src/replication/ChangeStream.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/modules/module-mongodb/src/replication/ChangeStream.ts b/modules/module-mongodb/src/replication/ChangeStream.ts index b952a9d4d..9e8a8acab 100644 --- a/modules/module-mongodb/src/replication/ChangeStream.ts +++ b/modules/module-mongodb/src/replication/ChangeStream.ts @@ -193,16 +193,21 @@ export class ChangeStream { } } - private getSourceNamespaceFilters() { + private getSourceNamespaceFilters(): { $match: any; multipleDatabases: boolean } { const sourceTables = this.sync_rules.getSourceTables(); let $inFilters: any[] = [{ db: this.defaultDb.databaseName, coll: '_powersync_checkpoints' }]; let $refilters: any[] = []; + let multipleDatabases = false; for (let tablePattern of sourceTables) { if (tablePattern.connectionTag != this.connections.connectionTag) { continue; } + if (tablePattern.schema != this.defaultDb.databaseName) { + multipleDatabases = true; + } + if (tablePattern.isWildcard) { $refilters.push({ db: tablePattern.schema, coll: new RegExp('^' + escapeRegExp(tablePattern.tablePrefix)) }); } else { @@ -213,9 +218,9 @@ export class ChangeStream { } } if ($refilters.length > 0) { - return { $or: [{ ns: { $in: $inFilters } }, ...$refilters] }; + return { $match: { $or: [{ ns: { $in: $inFilters } }, ...$refilters] }, multipleDatabases }; } - return { ns: { $in: $inFilters } }; + return { $match: { ns: { $in: $inFilters } }, multipleDatabases }; } static *getQueryData(results: Iterable): Generator { @@ -399,19 +404,29 @@ export class ChangeStream { // TODO: Use changeStreamSplitLargeEvent + const filters = this.getSourceNamespaceFilters(); + const pipeline: mongo.Document[] = [ { - $match: this.getSourceNamespaceFilters() + $match: filters.$match } ]; - const stream = this.client.watch(pipeline, { + const streamOptions: mongo.ChangeStreamOptions = { startAtOperationTime: startAfter, showExpandedEvents: true, useBigInt64: true, maxAwaitTimeMS: 200, fullDocument: 'updateLookup' - }); + }; + let stream: mongo.ChangeStream; + if (filters.multipleDatabases) { + // Requires readAnyDatabase@admin on Atlas + stream = this.client.watch(pipeline, streamOptions); + } else { + // Same general result, but requires less permissions than the above + stream = this.defaultDb.watch(pipeline, streamOptions); + } if (this.abort_signal.aborted) { stream.close(); From c6307d8c063f7728d727c7fe27b890277b3c1828 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Thu, 31 Oct 2024 10:13:56 +0200 Subject: [PATCH 2/2] Add changeset. --- .changeset/fifty-dogs-reply.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fifty-dogs-reply.md diff --git a/.changeset/fifty-dogs-reply.md b/.changeset/fifty-dogs-reply.md new file mode 100644 index 000000000..d19c4b701 --- /dev/null +++ b/.changeset/fifty-dogs-reply.md @@ -0,0 +1,5 @@ +--- +'@powersync/service-module-mongodb': minor +--- + +Reduce permissions required for replicating a single mongodb database