Skip to content

Commit 6d894d6

Browse files
fix(NODE-3998): metadata duplication in handshake (#3615)
1 parent b038cbb commit 6d894d6

File tree

11 files changed

+223
-87
lines changed

11 files changed

+223
-87
lines changed

src/cmap/auth/auth_provider.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import type { Document } from '../../bson';
22
import { MongoRuntimeError } from '../../error';
3-
import type { ClientMetadataOptions } from '../../utils';
43
import type { HandshakeDocument } from '../connect';
54
import type { Connection, ConnectionOptions } from '../connection';
65
import type { MongoCredentials } from './mongo_credentials';
76

8-
/** @internal */
9-
export type AuthContextOptions = ConnectionOptions & ClientMetadataOptions;
10-
117
/**
128
* Context used during authentication
139
* @internal
@@ -20,7 +16,7 @@ export class AuthContext {
2016
/** If the context is for reauthentication. */
2117
reauthenticating = false;
2218
/** The options passed to the `connect` method */
23-
options: AuthContextOptions;
19+
options: ConnectionOptions;
2420

2521
/** A response from an initial auth attempt, only some mechanisms use this (e.g, SCRAM) */
2622
response?: Document;
@@ -30,7 +26,7 @@ export class AuthContext {
3026
constructor(
3127
connection: Connection,
3228
credentials: MongoCredentials | undefined,
33-
options: AuthContextOptions
29+
options: ConnectionOptions
3430
) {
3531
this.connection = connection;
3632
this.credentials = credentials;

src/cmap/connect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
MongoRuntimeError,
1818
needsRetryableWriteLabel
1919
} from '../error';
20-
import { Callback, ClientMetadata, HostAddress, makeClientMetadata, ns } from '../utils';
20+
import { Callback, ClientMetadata, HostAddress, ns } from '../utils';
2121
import { AuthContext, AuthProvider } from './auth/auth_provider';
2222
import { GSSAPI } from './auth/gssapi';
2323
import { MongoCR } from './auth/mongocr';
@@ -213,7 +213,7 @@ export async function prepareHandshakeDocument(
213213
const handshakeDoc: HandshakeDocument = {
214214
[serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
215215
helloOk: true,
216-
client: options.metadata || makeClientMetadata(options),
216+
client: options.metadata,
217217
compression: compressors
218218
};
219219

src/connection_string.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
MongoParseError
1616
} from './error';
1717
import {
18-
DriverInfo,
1918
MongoClient,
2019
MongoClientOptions,
2120
MongoOptions,
@@ -543,6 +542,8 @@ export function parseOptions(
543542
loggerClientOptions
544543
);
545544

545+
mongoOptions.metadata = makeClientMetadata(mongoOptions);
546+
546547
return mongoOptions;
547548
}
548549

@@ -644,10 +645,7 @@ interface OptionDescriptor {
644645

645646
export const OPTIONS = {
646647
appName: {
647-
target: 'metadata',
648-
transform({ options, values: [value] }): DriverInfo {
649-
return makeClientMetadata({ ...options.driverInfo, appName: String(value) });
650-
}
648+
type: 'string'
651649
},
652650
auth: {
653651
target: 'credentials',
@@ -798,15 +796,8 @@ export const OPTIONS = {
798796
type: 'boolean'
799797
},
800798
driverInfo: {
801-
target: 'metadata',
802-
default: makeClientMetadata(),
803-
transform({ options, values: [value] }) {
804-
if (!isRecord(value)) throw new MongoParseError('DriverInfo must be an object');
805-
return makeClientMetadata({
806-
driverInfo: value,
807-
appName: options.metadata?.application?.name
808-
});
809-
}
799+
default: {},
800+
type: 'record'
810801
},
811802
enableUtf8Validation: { type: 'boolean', default: true },
812803
family: {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export type {
197197
ResumeToken,
198198
UpdateDescription
199199
} from './change_stream';
200-
export type { AuthContext, AuthContextOptions } from './cmap/auth/auth_provider';
200+
export type { AuthContext } from './cmap/auth/auth_provider';
201201
export type {
202202
AuthMechanismProperties,
203203
MongoCredentials,

src/mongo_client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,7 @@ export interface MongoOptions
700700
>
701701
>,
702702
SupportedNodeConnectionOptions {
703+
appName?: string;
703704
hosts: HostAddress[];
704705
srvHost?: string;
705706
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 { ReadConcern } from './read_concern';
@@ -513,7 +513,10 @@ export function makeStateMachine(stateTable: StateTable): StateTransitionFunctio
513513
};
514514
}
515515

516-
/** @public */
516+
/**
517+
* @public
518+
* @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#hello-command
519+
*/
517520
export interface ClientMetadata {
518521
driver: {
519522
name: string;
@@ -526,7 +529,6 @@ export interface ClientMetadata {
526529
version: string;
527530
};
528531
platform: string;
529-
version?: string;
530532
application?: {
531533
name: string;
532534
};
@@ -545,44 +547,38 @@ export interface ClientMetadataOptions {
545547
// eslint-disable-next-line @typescript-eslint/no-var-requires
546548
const NODE_DRIVER_VERSION = require('../package.json').version;
547549

548-
export function makeClientMetadata(options?: ClientMetadataOptions): ClientMetadata {
549-
options = options ?? {};
550+
export function makeClientMetadata(
551+
options: Pick<MongoOptions, 'appName' | 'driverInfo'>
552+
): ClientMetadata {
553+
const name = options.driverInfo.name ? `nodejs|${options.driverInfo.name}` : 'nodejs';
554+
const version = options.driverInfo.version
555+
? `${NODE_DRIVER_VERSION}|${options.driverInfo.version}`
556+
: NODE_DRIVER_VERSION;
557+
const platform = options.driverInfo.platform
558+
? `Node.js ${process.version}, ${os.endianness()}|${options.driverInfo.platform}`
559+
: `Node.js ${process.version}, ${os.endianness()}`;
550560

551561
const metadata: ClientMetadata = {
552562
driver: {
553-
name: 'nodejs',
554-
version: NODE_DRIVER_VERSION
563+
name,
564+
version
555565
},
556566
os: {
557567
type: os.type(),
558568
name: process.platform,
559569
architecture: process.arch,
560570
version: os.release()
561571
},
562-
platform: `Node.js ${process.version}, ${os.endianness()} (unified)`
572+
platform
563573
};
564574

565-
// support optionally provided wrapping driver info
566-
if (options.driverInfo) {
567-
if (options.driverInfo.name) {
568-
metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
569-
}
570-
571-
if (options.driverInfo.version) {
572-
metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
573-
}
574-
575-
if (options.driverInfo.platform) {
576-
metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
577-
}
578-
}
579-
580575
if (options.appName) {
581576
// MongoDB requires the appName not exceed a byte length of 128
582-
const buffer = Buffer.from(options.appName);
583-
metadata.application = {
584-
name: buffer.byteLength > 128 ? buffer.slice(0, 128).toString('utf8') : options.appName
585-
};
577+
const name =
578+
Buffer.byteLength(options.appName, 'utf8') <= 128
579+
? options.appName
580+
: Buffer.from(options.appName, 'utf8').subarray(0, 128).toString('utf8');
581+
metadata.application = { name };
586582
}
587583

588584
return metadata;

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { expect } from 'chai';
33
import {
44
connect,
55
Connection,
6+
ConnectionOptions,
67
LEGACY_HELLO_COMMAND,
8+
makeClientMetadata,
79
MongoClient,
810
MongoServerError,
911
ns,
@@ -31,12 +33,13 @@ describe('Connection', function () {
3133
it('should execute a command against a server', {
3234
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } },
3335
test: function (done) {
34-
const connectOptions = Object.assign(
35-
{ connectionType: Connection },
36-
this.configuration.options
37-
);
36+
const connectOptions: Partial<ConnectionOptions> = {
37+
connectionType: Connection,
38+
...this.configuration.options,
39+
metadata: makeClientMetadata({ driverInfo: {} })
40+
};
3841

39-
connect(connectOptions, (err, conn) => {
42+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
4043
expect(err).to.not.exist;
4144
this.defer(_done => conn.destroy(_done));
4245

@@ -53,12 +56,14 @@ describe('Connection', function () {
5356
it('should emit command monitoring events', {
5457
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } },
5558
test: function (done) {
56-
const connectOptions = Object.assign(
57-
{ connectionType: Connection, monitorCommands: true },
58-
this.configuration.options
59-
);
60-
61-
connect(connectOptions, (err, conn) => {
59+
const connectOptions: Partial<ConnectionOptions> = {
60+
connectionType: Connection,
61+
monitorCommands: true,
62+
...this.configuration.options,
63+
metadata: makeClientMetadata({ driverInfo: {} })
64+
};
65+
66+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
6267
expect(err).to.not.exist;
6368
this.defer(_done => conn.destroy(_done));
6469

@@ -84,12 +89,13 @@ describe('Connection', function () {
8489
},
8590
test: function (done) {
8691
const namespace = ns(`${this.configuration.db}.$cmd`);
87-
const connectOptions = Object.assign(
88-
{ connectionType: Connection },
89-
this.configuration.options
90-
);
92+
const connectOptions: Partial<ConnectionOptions> = {
93+
connectionType: Connection,
94+
...this.configuration.options,
95+
metadata: makeClientMetadata({ driverInfo: {} })
96+
};
9197

92-
connect(connectOptions, (err, conn) => {
98+
connect(connectOptions as any as ConnectionOptions, (err, conn) => {
9399
expect(err).to.not.exist;
94100
this.defer(_done => conn.destroy(_done));
95101

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('../../mongodb');
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
@@ -370,11 +370,8 @@ async function runCmapTest(test: CmapTest, threadContext: ThreadContext) {
370370
delete poolOptions.backgroundThreadIntervalMS;
371371
}
372372

373-
let metadata;
374-
if (poolOptions.appName) {
375-
metadata = makeClientMetadata({ appName: poolOptions.appName });
376-
delete poolOptions.appName;
377-
}
373+
const metadata = makeClientMetadata({ appName: poolOptions.appName, driverInfo: {} });
374+
delete poolOptions.appName;
378375

379376
const operations = test.operations;
380377
const expectedError = test.error;

0 commit comments

Comments
 (0)