Skip to content

Commit b790142

Browse files
fix(NODE-5161): metadata duplication in handshake (#3628)
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
1 parent 49fa638 commit b790142

File tree

10 files changed

+225
-84
lines changed

10 files changed

+225
-84
lines changed

src/cmap/auth/auth_provider.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ import type { MongoCredentials } from './mongo_credentials';
77

88
export type AuthContextOptions = ConnectionOptions & ClientMetadataOptions;
99

10-
/** Context used during authentication */
10+
/**
11+
* Context used during authentication
12+
* @internal
13+
*/
1114
export class AuthContext {
1215
/** The connection to authenticate */
1316
connection: Connection;
1417
/** The credentials to use for authentication */
1518
credentials?: MongoCredentials;
1619
/** The options passed to the `connect` method */
17-
options: AuthContextOptions;
20+
options: ConnectionOptions;
1821

1922
/** A response from an initial auth attempt, only some mechanisms use this (e.g, SCRAM) */
2023
response?: Document;
@@ -24,7 +27,7 @@ export class AuthContext {
2427
constructor(
2528
connection: Connection,
2629
credentials: MongoCredentials | undefined,
27-
options: AuthContextOptions
30+
options: ConnectionOptions
2831
) {
2932
this.connection = connection;
3033
this.credentials = credentials;

src/cmap/connect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
MongoServerError,
1919
needsRetryableWriteLabel
2020
} from '../error';
21-
import { Callback, ClientMetadata, HostAddress, makeClientMetadata, ns } from '../utils';
21+
import { Callback, ClientMetadata, HostAddress, ns } from '../utils';
2222
import { AuthContext, AuthProvider } from './auth/auth_provider';
2323
import { GSSAPI } from './auth/gssapi';
2424
import { MongoCR } from './auth/mongocr';
@@ -233,7 +233,7 @@ export function prepareHandshakeDocument(
233233
const handshakeDoc: HandshakeDocument = {
234234
[serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: true,
235235
helloOk: true,
236-
client: options.metadata || makeClientMetadata(options),
236+
client: options.metadata,
237237
compression: compressors
238238
};
239239

src/connection_string.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
} from './error';
1717
import { Logger as LegacyLogger, LoggerLevel as LegacyLoggerLevel } from './logger';
1818
import {
19-
DriverInfo,
2019
MongoClient,
2120
MongoClientOptions,
2221
MongoOptions,
@@ -534,6 +533,8 @@ export function parseOptions(
534533
loggerClientOptions
535534
);
536535

536+
mongoOptions.metadata = makeClientMetadata(mongoOptions);
537+
537538
return mongoOptions;
538539
}
539540

@@ -635,10 +636,7 @@ interface OptionDescriptor {
635636

636637
export const OPTIONS = {
637638
appName: {
638-
target: 'metadata',
639-
transform({ options, values: [value] }): DriverInfo {
640-
return makeClientMetadata({ ...options.driverInfo, appName: String(value) });
641-
}
639+
type: 'string'
642640
},
643641
auth: {
644642
target: 'credentials',
@@ -784,15 +782,8 @@ export const OPTIONS = {
784782
type: 'boolean'
785783
},
786784
driverInfo: {
787-
target: 'metadata',
788-
default: makeClientMetadata(),
789-
transform({ options, values: [value] }) {
790-
if (!isRecord(value)) throw new MongoParseError('DriverInfo must be an object');
791-
return makeClientMetadata({
792-
driverInfo: value,
793-
appName: options.metadata?.application?.name
794-
});
795-
}
785+
default: {},
786+
type: 'record'
796787
},
797788
enableUtf8Validation: { type: 'boolean', default: true },
798789
family: {

src/mongo_client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ export interface MongoOptions
770770
>
771771
>,
772772
SupportedNodeConnectionOptions {
773+
appName?: string;
773774
hosts: HostAddress[];
774775
srvHost?: string;
775776
credentials?: MongoCredentials;

src/utils.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
MongoRuntimeError
2121
} from './error';
2222
import type { Explain } from './explain';
23-
import type { MongoClient } from './mongo_client';
23+
import type { MongoClient, MongoOptions } from './mongo_client';
2424
import type { CommandOperationOptions, OperationParent } from './operations/command';
2525
import type { Hint, OperationOptions } from './operations/operation';
2626
import { PromiseProvider } from './promise_provider';
@@ -657,7 +657,10 @@ export function makeStateMachine(stateTable: StateTable): StateTransitionFunctio
657657
};
658658
}
659659

660-
/** @public */
660+
/**
661+
* @public
662+
* @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#hello-command
663+
*/
661664
export interface ClientMetadata {
662665
driver: {
663666
name: string;
@@ -670,7 +673,6 @@ export interface ClientMetadata {
670673
version: string;
671674
};
672675
platform: string;
673-
version?: string;
674676
application?: {
675677
name: string;
676678
};
@@ -689,44 +691,38 @@ export interface ClientMetadataOptions {
689691
// eslint-disable-next-line @typescript-eslint/no-var-requires
690692
const NODE_DRIVER_VERSION = require('../package.json').version;
691693

692-
export function makeClientMetadata(options?: ClientMetadataOptions): ClientMetadata {
693-
options = options ?? {};
694+
export function makeClientMetadata(
695+
options: Pick<MongoOptions, 'appName' | 'driverInfo'>
696+
): ClientMetadata {
697+
const name = options.driverInfo.name ? `nodejs|${options.driverInfo.name}` : 'nodejs';
698+
const version = options.driverInfo.version
699+
? `${NODE_DRIVER_VERSION}|${options.driverInfo.version}`
700+
: NODE_DRIVER_VERSION;
701+
const platform = options.driverInfo.platform
702+
? `Node.js ${process.version}, ${os.endianness()}|${options.driverInfo.platform}`
703+
: `Node.js ${process.version}, ${os.endianness()}`;
694704

695705
const metadata: ClientMetadata = {
696706
driver: {
697-
name: 'nodejs',
698-
version: NODE_DRIVER_VERSION
707+
name,
708+
version
699709
},
700710
os: {
701711
type: os.type(),
702712
name: process.platform,
703713
architecture: process.arch,
704714
version: os.release()
705715
},
706-
platform: `Node.js ${process.version}, ${os.endianness()} (unified)`
716+
platform
707717
};
708718

709-
// support optionally provided wrapping driver info
710-
if (options.driverInfo) {
711-
if (options.driverInfo.name) {
712-
metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
713-
}
714-
715-
if (options.driverInfo.version) {
716-
metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
717-
}
718-
719-
if (options.driverInfo.platform) {
720-
metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
721-
}
722-
}
723-
724719
if (options.appName) {
725720
// MongoDB requires the appName not exceed a byte length of 128
726-
const buffer = Buffer.from(options.appName);
727-
metadata.application = {
728-
name: buffer.byteLength > 128 ? buffer.slice(0, 128).toString('utf8') : options.appName
729-
};
721+
const name =
722+
Buffer.byteLength(options.appName, 'utf8') <= 128
723+
? options.appName
724+
: Buffer.from(options.appName, 'utf8').subarray(0, 128).toString('utf8');
725+
metadata.application = { name };
730726
}
731727

732728
return metadata;

test/integration/connection-monitoring-and-pooling/connection.test.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { connect } from '../../../src/cmap/connect';
55
import { Connection } from '../../../src/cmap/connection';
66
import { LEGACY_HELLO_COMMAND } from '../../../src/constants';
77
import { Topology } from '../../../src/sdam/topology';
8-
import { ns } from '../../../src/utils';
8+
import { makeClientMetadata, ns } from '../../../src/utils';
99
import { skipBrokenAuthTestBeforeEachHook } from '../../tools/runner/hooks/configuration';
1010
import { assert as test, setupDatabase } from '../shared';
1111

@@ -27,12 +27,13 @@ describe('Connection', function () {
2727
it('should execute a command against a server', {
2828
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } },
2929
test: function (done) {
30-
const connectOptions = Object.assign(
31-
{ connectionType: Connection },
32-
this.configuration.options
33-
);
30+
const connectOptions: Partial<ConnectionOptions> = {
31+
connectionType: Connection,
32+
...this.configuration.options,
33+
metadata: makeClientMetadata({ driverInfo: {} })
34+
};
3435

35-
connect(connectOptions, (err, conn) => {
36+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
3637
expect(err).to.not.exist;
3738
this.defer(_done => conn.destroy(_done));
3839

@@ -49,12 +50,14 @@ describe('Connection', function () {
4950
it('should emit command monitoring events', {
5051
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } },
5152
test: function (done) {
52-
const connectOptions = Object.assign(
53-
{ connectionType: Connection, monitorCommands: true },
54-
this.configuration.options
55-
);
56-
57-
connect(connectOptions, (err, conn) => {
53+
const connectOptions: Partial<ConnectionOptions> = {
54+
connectionType: Connection,
55+
monitorCommands: true,
56+
...this.configuration.options,
57+
metadata: makeClientMetadata({ driverInfo: {} })
58+
};
59+
60+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
5861
expect(err).to.not.exist;
5962
this.defer(_done => conn.destroy(_done));
6063

@@ -80,12 +83,13 @@ describe('Connection', function () {
8083
},
8184
test: function (done) {
8285
const namespace = ns(`${this.configuration.db}.$cmd`);
83-
const connectOptions = Object.assign(
84-
{ connectionType: Connection },
85-
this.configuration.options
86-
);
86+
const connectOptions: Partial<ConnectionOptions> = {
87+
connectionType: Connection,
88+
...this.configuration.options,
89+
metadata: makeClientMetadata({ driverInfo: {} })
90+
};
8791

88-
connect(connectOptions, (err, conn) => {
92+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
8993
expect(err).to.not.exist;
9094
this.defer(_done => conn.destroy(_done));
9195

test/integration/node-specific/topology.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
'use strict';
22
const { expect } = require('chai');
3+
const { makeClientMetadata } = require('../../../src/utils');
34

45
describe('Topology', function () {
56
it('should correctly track states of a topology', {
67
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } }, // apiVersion not supported by newTopology()
78
test: function (done) {
8-
const topology = this.configuration.newTopology();
9+
const topology = this.configuration.newTopology({
10+
metadata: makeClientMetadata({ driverInfo: {} })
11+
});
912

1013
const states = [];
1114
topology.on('stateChanged', (_, newState) => states.push(newState));

test/tools/cmap_spec_runner.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,8 @@ async function runCmapTest(test: CmapTest, threadContext: ThreadContext) {
363363
delete poolOptions.backgroundThreadIntervalMS;
364364
}
365365

366-
let metadata;
367-
if (poolOptions.appName) {
368-
metadata = makeClientMetadata({ appName: poolOptions.appName });
369-
delete poolOptions.appName;
370-
}
366+
const metadata = makeClientMetadata({ appName: poolOptions.appName, driverInfo: {} });
367+
delete poolOptions.appName;
371368

372369
const operations = test.operations;
373370
const expectedError = test.error;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy