Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
be72896
Use `[email protected]`.
mattcollier Dec 6, 2023
74ce575
Remove unsupported `connectOptions`.
dlongley Mar 4, 2025
b6d6acc
Add test for promoteBuffers.
mattcollier Dec 8, 2023
af50c20
Add assertion for `insertOne` response.
mattcollier Dec 11, 2023
a1abddc
Change user to username.
mattcollier Dec 11, 2023
6c2e57a
Remove bug fix for unused driver version 3.6.4.
dlongley Mar 5, 2025
dfb4f4c
Remove duplicate `promoteBuffers` option.
dlongley Mar 5, 2025
07e27c7
Remove unnecessary `writeOptions` use.
dlongley Mar 5, 2025
4c82716
Set minimum server version to `>=5`.
dlongley Mar 5, 2025
25afd96
Remove use of `_usesRoles` helper.
mattcollier Dec 12, 2023
6e36c08
Refactor code in test.
mattcollier Dec 13, 2023
01f805a
Update readme.
mattcollier Dec 13, 2023
7fd22bc
Refactor `openDatabase()`.
dlongley Mar 5, 2025
f9d2b46
Update copyright header.
dlongley Mar 5, 2025
3eb8fc8
Update BedrockErrors.
dlongley Mar 5, 2025
cf85e25
Improve error checks and helpers.
dlongley Mar 5, 2025
2ca5beb
Include `MongoServerError` in class of database errors.
dlongley Mar 5, 2025
cadcc2c
Add `isDuplicateError()` test.
dlongley Mar 5, 2025
58cb6a5
Use latest mongodb driver version.
dlongley Mar 5, 2025
25a9953
Update linting.
dlongley Mar 5, 2025
2499d8e
Add copyright header back to linting config files.
dlongley Mar 5, 2025
1be6a4f
Use `structuredClone()` instead of `klona`; support node>=20.
dlongley Mar 5, 2025
fc1f2ce
Update github actions.
dlongley Mar 5, 2025
67e42cd
Reorder config options.
dlongley Mar 5, 2025
3c869b2
Update README.
dlongley Mar 5, 2025
1c10876
Remove unused `jsdoc-to-markdown` dev dependency.
dlongley Mar 6, 2025
e13469a
Add note about error name changes.
dlongley Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright 2012 - 2024 Digital Bazaar, Inc.
* Copyright 2012 - 2025 Digital Bazaar, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,16 +15,18 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BigBlueHat I recall the license tooling added these blank lines? Do they need to be preserved?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, in fact, the licensing tool should not be inserting them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it was a choice, similar to the extra spaces in the copyright date range. I was wondering if some part of that tooling would be grumpy if blank lines are gone.

module.exports = {
root: true,
parserOptions: {
// this is required for dynamic import()
ecmaVersion: 2020
},
env: {
node: true
},
extends: ['digitalbazaar', 'digitalbazaar/jsdoc'],
ignorePatterns: ['node_modules/']
extends: [
'digitalbazaar',
'digitalbazaar/jsdoc',
'digitalbazaar/module'
],
ignorePatterns: ['node_modules/'],
rules: {
'unicorn/prefer-node-protocol': 'error'
}
};
15 changes: 8 additions & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ jobs:
timeout-minutes: 10
services:
mongodb:
image: mongo:5
image: mongo:6
ports:
- 27017:27017
strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -50,15 +50,15 @@ jobs:
timeout-minutes: 10
services:
mongodb:
image: mongo:5
image: mongo:6
ports:
- 27017:27017
env:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -84,12 +84,12 @@ jobs:
timeout-minutes: 10
services:
mongodb:
image: mongo:5
image: mongo:6
ports:
- 27017:27017
strategy:
matrix:
node-version: [20.x]
node-version: [22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -105,7 +105,8 @@ jobs:
cd test
npm run coverage-ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: ./test/coverage/lcov.info
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# bedrock-mongodb ChangeLog

## 11.0.0 - 2025-mm-dd

### Changed
- **BREAKING**: Use mongodb driver 6.x.
- **BREAKING**: Update error names to match bedrock best practice.
- `InvalidKey` error for a bad param type has been changed to `TypeError`.
- `DatabaseError` error is now `VersionError` for an invalid version,
`DataError` for invalid data, and `OperationError` for some other
error with a database operation. This is unrelated to MongoDB errors
that are detected using the `isDatabaseError()` helper, which is
unchanged and now more clearly decoupled from `DatabaseError`, which
is no longer used.

### Removed
- **BREAKING**: Remove export of previously deprecated `writeOptions`.

## 10.2.0 - 2024-02-28

### Changed
Expand Down
43 changes: 19 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,17 @@ import * as database from '@bedrock/mongodb';
// custom configuration
bedrock.config.mongodb.name = 'my_project_dev'; // default: bedrock_dev
bedrock.config.mongodb.host = 'localhost'; // default: localhost
bedrock.config.mongodb.protocol = 'mongodb'; // default: mongodb
bedrock.config.mongodb.protocol = 'mongodb'; // default: mongodb
bedrock.config.mongodb.port = 27017; // default: 27017
bedrock.config.mongodb.username = 'my_project'; // default: bedrock
bedrock.config.mongodb.password = 'password'; // default: password

// the mongodb database 'my_project_dev' and the 'my_project' user will
// be created on start up following a prompt for the admin user credentials

// alternatively, use `mongodb` URL format:
// alternatively, use `mongodb` URL format (preferred for deployments):
bedrock.config.mongodb.url = 'mongodb://localhost:27017/my_project_dev';

// enable local collection if a local database is available
// the local database has similar options to primary database
// see lib/config.js for details
// bedrock.config.mongodb.local.enable = true; // default: false

// open some collections once the database is ready
bedrock.events.on('bedrock-mongodb.ready', async function() {
await database.openCollections(['collection1', 'collection2']);
Expand All @@ -58,15 +53,20 @@ bedrock.start();
For documentation on database configuration, see [config.js](./lib/config.js).

### Connecting and Authenticating
MongoDB's documentation offers tons of great examples on how to authenticate
using a myriad number of connection strings.
MongoDB's documentation provides examples on how to authenticate using a myriad
number of connection strings.

[Mongo Node 3.5 Driver connect docs](http://mongodb.github.io/node-mongodb-native/3.5/tutorials/connect/)
[Mongo Node Driver connect docs](https://www.mongodb.com/docs/drivers/node/current/fundamentals/connection/connect/)

[Mongo Node 3.5 Driver atlas docs](https://docs.mongodb.com/drivers/node#connect-to-mongodb-atlas)
You can connect using a url by setting:
```js
config.mongodb.url = 'mongodb://myDBReader:D1fficultP%[email protected]:27017/?authSource=admin';
```

You can also connect to access-enabled mongo servers using some small changes to the
`config.mongodb.connectOptions`:
`config.mongodb.connectOptions`, however, the `config.mongodb.url` option is highly
preferable and should cover most use cases:

```js
import {config} from '@bedrock/core';

Expand All @@ -86,26 +86,21 @@ connectOptions.authSource = 'my_provider_auth_db';
```
MongoDB provides [excellent docs on their connection strings](https://docs.mongodb.com/manual/reference/connection-string/)

You can connect using a url by setting:
```js
config.mongodb.url = 'mongodb://myDBReader:D1fficultP%[email protected]:27017/?authSource=admin';
```

## Requirements

* Linux or Mac OS X (also works on Windows with some coaxing)
* node.js >= 14.x
* npm >= 6.x
* mongodb ~= 4.x
* node.js >= 20.x
* npm >= 10.x
* mongodb >= 6.x
* libkrb5-dev >= 1.x.x

## Setup

1. Ensure an admin user is set up on mongodb. To do so, follow the instructions
at [mongodb.org](http://docs.mongodb.org/manual/tutorial/add-user-administrator/)
for your version of MongoDB. Version 4.2.x is currently supported.
for your version of MongoDB. Version 6.x is currently supported.
2. [optional] Tweak your project's configuration settings; see
[Configuration](#configuration) or [Quick Examples](#quickexamples).
[Configuration](#configuration) or [Quick Examples](#quick-examples).

## API

Expand All @@ -122,7 +117,7 @@ an error occurs, the returned promise rejects. If no error occurs, then once
the promise resolves, the `collections` object will have keys that match the
collection names and values that are instances of
[mongodb-native][]
[Collection](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html).
[Collection](https://mongodb.github.io/node-mongodb-native/6.14/classes/Collection.html).

### createGridFSBucket(options)

Expand Down Expand Up @@ -165,4 +160,4 @@ Commercial licensing and support are available by contacting
[Digital Bazaar](https://digitalbazaar.com/) <[email protected]>.

[bedrock]: https://github.com/digitalbazaar/bedrock
[mongodb-native]: http://mongodb.github.io/node-mongodb-native/3.5/
[mongodb-native]: https://www.mongodb.com/docs/drivers/node/current/
68 changes: 28 additions & 40 deletions lib/authn.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright 2012 - 2024 Digital Bazaar, Inc.
* Copyright 2012 - 2025 Digital Bazaar, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,57 +15,53 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

import * as bedrock from '@bedrock/core';
import * as urls from './urls.js';
import {logger} from './logger.js';
import {MDBE_AUTHZ_FAILED} from './exceptions.js';
import mongo from 'mongodb';
import semver from 'semver';
import {klona} from 'klona';

const {MongoClient} = mongo;
const {util: {BedrockError}} = bedrock;

export async function openDatabase(options) {
const config = bedrock.config.mongodb;
// copy the config stuff related to connecting

const opts = {
database: config.name,
authentication: {...config.authentication},
// authSource should be set in connectOptions
connectOptions: {...config.connectOptions},
writeOptions: {...config.writeOptions},
...options
};

// if a `url` was not specified, create one from the `config`
if(!opts.url) {
if(config.url) {
opts.url = config.url;
} else {
// `config.url` not specified; create `opts.url` from the `config` and
// set other necessary options from the config
opts.url = urls.create(config);
opts.database = config.name;
opts.authentication = {...config.authentication};
}

// do unauthenticated connection to mongo server to check
// server compatibility and authn requirements
const {admin} = await _getUnauthenticatedDb({config: klona(config)});
const {admin} = await _getUnauthenticatedDb({
config: structuredClone(config)
});
const serverInfo = await admin.serverInfo(null);
_checkServerVersion({serverInfo, config});

// check if server supports roles; if not, can't authenticate
if(!_usesRoles(serverInfo)) {
const stringVersion = serverInfo.versionArray.join('.');
throw new BedrockError(
`MongoDB server version "${stringVersion}" is unsupported.`,
'NotSupportedError');
}
// makes an unauthenticated call to the server
// to see if auth is required
const authRequired = await _isAuthnRequired({config, admin});
// do auth check and add additional params if `config.url` is not set
if(!config.url) {
// makes an unauthenticated call to the server to see if auth is required
const authRequired = await _isAuthnRequired({config, admin});

// if authRequired create an auth object for Mongo;
// otherwise `auth` will be passed as `null` and success will rely on other
// config options such as the url for the server
if(authRequired) {
_addAuthOptions({options: opts, config});
// if `authRequired`, create an auth object for Mongo; otherwise, `auth`
// will be passed as `null` and success will rely on other config options
if(authRequired) {
_addAuthOptions({options: opts, config});
}
}

// connect to database and get server info
Expand Down Expand Up @@ -98,19 +94,10 @@ async function _connect(options) {
const client = await MongoClient.connect(options.url, connectOptions);
const db = client.db();
const ping = await db.admin().ping();
logger.debug(
'database connection succeeded: db=' + db.databaseName +
' username=' + connectOptions?.auth?.user, {ping});
logger.debug('database connection succeeded: db=' + db.databaseName, {ping});
return {client, db};
}

function _usesRoles(serverInfo) {
// >= Mongo 2.6 uses user roles
return (
(serverInfo.versionArray[0] == 2 && serverInfo.versionArray[1] >= 6) ||
(serverInfo.versionArray[0] > 2));
}

/**
* Determines if authn is required.
*
Expand Down Expand Up @@ -143,7 +130,7 @@ async function _isAuthnRequired({config, admin}) {

function _addAuthOptions({options, config}) {
options.connectOptions.auth = {
user: config.username,
username: config.username,
password: config.password
};
// authSource is the database to authenticate against
Expand Down Expand Up @@ -188,12 +175,13 @@ function _checkServerVersion({serverInfo, config}) {
});
if(config.requirements.serverVersion &&
!semver.satisfies(version, config.requirements.serverVersion)) {
throw new BedrockError(
'Unsupported database version.',
'DatabaseError', {
throw new BedrockError('Unsupported database version.', {
name: 'VersionError',
details: {
url: urls.sanitize(config.url),
version,
required: config.requirements.serverVersion
});
}
});
}
}
10 changes: 3 additions & 7 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright 2012 - 2024 Digital Bazaar, Inc.
* Copyright 2012 - 2025 Digital Bazaar, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

import {config} from '@bedrock/core';

config.mongodb = {};
Expand Down Expand Up @@ -54,12 +53,9 @@ config.mongodb.authentication = {
};
// this is used when making connections to the database
config.mongodb.connectOptions = {
useUnifiedTopology: true,
serverSelectionTimeoutMS: 30000,
autoReconnect: false,
useNewUrlParser: true,
// promotes binary BSON values to native Node.js buffers
promoteBuffers: true,
serverSelectionTimeoutMS: 30000
// it is recommended to set either ssl or tls to true in production
// ssl: true
// tls: true
Expand All @@ -79,7 +75,7 @@ config.mongodb.writeOptions = {

config.mongodb.requirements = {};
// server version requirement with server-style string
config.mongodb.requirements.serverVersion = '>=4.2';
config.mongodb.requirements.serverVersion = '>=5';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs and tests are using v6. Is there a reason to have this lower?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is software out there still using 5 for which this works -- we can bump it further in the future to avoid additional blockers on getting this update through.


// this is used by _createUser to add a user as an admin to a collection
// config.mongodb.collection = 'admin-collection';
Loading