From 1d45f5f8cc5adda1f29e855992816fbf4a630250 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 8 Jul 2026 02:53:16 +1200 Subject: [PATCH 1/2] feat(client): support custom authSource for SCRAM authentication SCRAM authentication was hardcoded to run against the admin database, which fails when the MongoDB user was created in the application database itself (db.createUser() run inside the target database). Add an authSource option to Auth and a matching constructor parameter to Client so callers can authenticate against the database that actually holds the user. Defaults to admin, preserving existing behaviour. Co-Authored-By: Claude Fable 5 --- src/Auth.php | 6 +++-- src/Client.php | 9 ++++++-- tests/AuthTest.php | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 tests/AuthTest.php diff --git a/src/Auth.php b/src/Auth.php index fecc25b..3a86e66 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -9,6 +9,7 @@ class Auth private string $authcid; private string $secret; private string $authzid; + private string $authSource; private string $gs2Header; private ?string $cnonce = null; private string $firstMessageBare; @@ -23,6 +24,7 @@ public function __construct(array $options) $this->authcid = $options['authcid'] ?? ''; $this->secret = $options['secret'] ?? ''; $this->authzid = $options['authzid'] ?? ''; + $this->authSource = $options['authSource'] ?? 'admin'; $this->cnonce = base64_encode(random_bytes(32)); } @@ -39,7 +41,7 @@ public function start(): array "autoAuthorize" => 1, "options" => ["skipEmptyExchange" => true], ], - 'admin' + $this->authSource ]; } @@ -61,7 +63,7 @@ public function continue($data): array "conversationId" => $cid, "payload" => $payload, ], - 'admin' + $this->authSource ]; } diff --git a/src/Client.php b/src/Client.php index d54e9a6..b7680f3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -134,6 +134,9 @@ class Client * @param string $user * @param string $password * @param Boolean $useCoroutine + * @param string|null $authSource Database to authenticate against; defaults to 'admin'. + * Set this when the user was created in a database other than admin (e.g. the + * application database itself). * @throws \Exception */ public function __construct( @@ -144,7 +147,8 @@ public function __construct( string $password, bool $useCoroutine = false, bool $tls = false, - array $tlsOptions = [] + array $tlsOptions = [], + ?string $authSource = null ) { if (empty($database)) { throw new \InvalidArgumentException('Database name cannot be empty'); @@ -210,7 +214,8 @@ public function __construct( $this->auth = new Auth([ 'authcid' => $user, - 'secret' => Auth::encodeCredentials($user, $password) + 'secret' => Auth::encodeCredentials($user, $password), + 'authSource' => $authSource ?? 'admin', ]); } diff --git a/tests/AuthTest.php b/tests/AuthTest.php new file mode 100644 index 0000000..1a95af2 --- /dev/null +++ b/tests/AuthTest.php @@ -0,0 +1,56 @@ + 'root', + 'secret' => Auth::encodeCredentials('root', 'example'), + ]); + + [$payload, $database] = $auth->start(); + + $this->assertSame(1, $payload['saslStart']); + $this->assertSame('admin', $database); + } + + public function testStartUsesProvidedAuthSource(): void + { + $auth = new Auth([ + 'authcid' => 'appuser', + 'secret' => Auth::encodeCredentials('appuser', 'example'), + 'authSource' => 'appdb', + ]); + + [$payload, $database] = $auth->start(); + + $this->assertSame(1, $payload['saslStart']); + $this->assertSame('appdb', $database); + } + + public function testContinueUsesProvidedAuthSource(): void + { + $auth = new Auth([ + 'authcid' => 'appuser', + 'secret' => Auth::encodeCredentials('appuser', 'example'), + 'authSource' => 'appdb', + ]); + + $auth->start(); + + $data = new \stdClass(); + $data->conversationId = 1; + $data->payload = new \MongoDB\BSON\Binary('r=' . $auth->getCnonce() . 'server,s=' . base64_encode('salt') . ',i=4096', 0); + + [$payload, $database] = $auth->continue($data); + + $this->assertSame(1, $payload['saslContinue']); + $this->assertSame('appdb', $database); + } +} From 28ed5cd4e9fa15b57d342d812e55a9424d2712bb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 8 Jul 2026 15:57:45 +1200 Subject: [PATCH 2/2] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/Client.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Client.php b/src/Client.php index b7680f3..f3f05e4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -134,6 +134,8 @@ class Client * @param string $user * @param string $password * @param Boolean $useCoroutine + * @param bool $tls + * @param array $tlsOptions * @param string|null $authSource Database to authenticate against; defaults to 'admin'. * Set this when the user was created in a database other than admin (e.g. the * application database itself).