Skip to content

Commit cb9ee9e

Browse files
authored
feat: Add MongoOption builder logic (#2623)
Parse options from uri and object using a unified registry creating frozen MongoOptions interface object. NODE-2699 NODE-2698
1 parent f6d9b81 commit cb9ee9e

29 files changed

+1434
-292
lines changed

src/cmap/auth/defaultAuthProviders.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ import { MongoDBAWS } from './mongodb_aws';
77
import type { AuthProvider } from './auth_provider';
88

99
/** @public */
10-
export enum AuthMechanism {
11-
MONGODB_AWS = 'MONGODB-AWS',
12-
MONGODB_CR = 'MONGODB-CR',
13-
MONGODB_DEFAULT = 'DEFAULT',
14-
MONGODB_GSSAPI = 'GSSAPI',
15-
MONGODB_PLAIN = 'PLAIN',
16-
MONGODB_SCRAM_SHA1 = 'SCRAM-SHA-1',
17-
MONGODB_SCRAM_SHA256 = 'SCRAM-SHA-256',
18-
MONGODB_X509 = 'MONGODB-X509'
19-
}
10+
export const AuthMechanism = {
11+
MONGODB_AWS: 'MONGODB-AWS',
12+
MONGODB_CR: 'MONGODB-CR',
13+
MONGODB_DEFAULT: 'DEFAULT',
14+
MONGODB_GSSAPI: 'GSSAPI',
15+
MONGODB_PLAIN: 'PLAIN',
16+
MONGODB_SCRAM_SHA1: 'SCRAM-SHA-1',
17+
MONGODB_SCRAM_SHA256: 'SCRAM-SHA-256',
18+
MONGODB_X509: 'MONGODB-X509'
19+
} as const;
20+
21+
/** @public */
22+
export type AuthMechanismId = typeof AuthMechanism[keyof typeof AuthMechanism];
2023

2124
export const AUTH_PROVIDERS = {
2225
[AuthMechanism.MONGODB_AWS]: new MongoDBAWS(),

src/cmap/auth/mongo_credentials.ts

+55-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// Resolves the default auth mechanism according to
22

33
import type { Document } from '../../bson';
4-
import { AuthMechanism } from './defaultAuthProviders';
4+
import { AuthMechanismId, AuthMechanism } from './defaultAuthProviders';
55

66
// https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
7-
function getDefaultAuthMechanism(ismaster?: Document): AuthMechanism {
7+
function getDefaultAuthMechanism(ismaster?: Document): AuthMechanismId {
88
if (ismaster) {
99
// If ismaster contains saslSupportedMechs, use scram-sha-256
1010
// if it is available, else scram-sha-1
1111
if (Array.isArray(ismaster.saslSupportedMechs)) {
12-
return ismaster.saslSupportedMechs.indexOf('SCRAM-SHA-256') >= 0
12+
return ismaster.saslSupportedMechs.includes(AuthMechanism.MONGODB_SCRAM_SHA256)
1313
? AuthMechanism.MONGODB_SCRAM_SHA256
1414
: AuthMechanism.MONGODB_SCRAM_SHA1;
1515
}
@@ -30,7 +30,7 @@ export interface MongoCredentialsOptions {
3030
password: string;
3131
source: string;
3232
db?: string;
33-
mechanism?: AuthMechanism;
33+
mechanism?: AuthMechanismId;
3434
mechanismProperties: Document;
3535
}
3636

@@ -46,7 +46,7 @@ export class MongoCredentials {
4646
/** The database that the user should authenticate against */
4747
readonly source: string;
4848
/** The method used to authenticate */
49-
readonly mechanism: AuthMechanism;
49+
readonly mechanism: AuthMechanismId;
5050
/** Special properties used by some types of auth mechanisms */
5151
readonly mechanismProperties: Document;
5252

@@ -108,4 +108,54 @@ export class MongoCredentials {
108108

109109
return this;
110110
}
111+
112+
validate(): void {
113+
if (
114+
(this.mechanism === AuthMechanism.MONGODB_GSSAPI ||
115+
this.mechanism === AuthMechanism.MONGODB_CR ||
116+
this.mechanism === AuthMechanism.MONGODB_PLAIN ||
117+
this.mechanism === AuthMechanism.MONGODB_SCRAM_SHA1 ||
118+
this.mechanism === AuthMechanism.MONGODB_SCRAM_SHA256) &&
119+
!this.username
120+
) {
121+
throw new TypeError(`Username required for mechanism '${this.mechanism}'`);
122+
}
123+
124+
if (
125+
this.mechanism === AuthMechanism.MONGODB_GSSAPI ||
126+
this.mechanism === AuthMechanism.MONGODB_AWS ||
127+
this.mechanism === AuthMechanism.MONGODB_X509
128+
) {
129+
if (this.source != null && this.source !== '$external') {
130+
throw new TypeError(
131+
`Invalid source '${this.source}' for mechanism '${this.mechanism}' specified.`
132+
);
133+
}
134+
}
135+
136+
if (this.mechanism === AuthMechanism.MONGODB_PLAIN && this.source == null) {
137+
throw new TypeError('PLAIN Authentication Mechanism needs an auth source');
138+
}
139+
140+
if (this.mechanism === AuthMechanism.MONGODB_X509 && this.password != null) {
141+
if (this.password === '') {
142+
Reflect.set(this, 'password', undefined);
143+
return;
144+
}
145+
throw new TypeError(`Password not allowed for mechanism MONGODB-X509`);
146+
}
147+
}
148+
149+
static merge(
150+
creds: MongoCredentials,
151+
options: Partial<MongoCredentialsOptions>
152+
): MongoCredentials {
153+
return new MongoCredentials({
154+
username: options.username ?? creds.username,
155+
password: options.password ?? creds.password,
156+
mechanism: options.mechanism ?? creds.mechanism,
157+
mechanismProperties: options.mechanismProperties ?? creds.mechanismProperties,
158+
source: options.source ?? creds.source ?? options.db
159+
});
160+
}
111161
}

src/cmap/auth/scram.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { MongoCredentials } from './mongo_credentials';
77
import type { HandshakeDocument } from '../connect';
88

99
import { saslprep } from '../../deps';
10+
import { AuthMechanism } from './defaultAuthProviders';
1011

1112
type CryptoMethod = 'sha1' | 'sha256';
1213

@@ -83,7 +84,8 @@ function makeFirstMessage(
8384
nonce: Buffer
8485
) {
8586
const username = cleanUsername(credentials.username);
86-
const mechanism = cryptoMethod === 'sha1' ? 'SCRAM-SHA-1' : 'SCRAM-SHA-256';
87+
const mechanism =
88+
cryptoMethod === 'sha1' ? AuthMechanism.MONGODB_SCRAM_SHA1 : AuthMechanism.MONGODB_SCRAM_SHA256;
8789

8890
// NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
8991
// Since the username is not sasl-prep-d, we need to do this here.

src/cmap/connection.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import type { Server } from '../sdam/server';
2727
import type { MongoCredentials } from './auth/mongo_credentials';
2828
import type { CommandOptions } from './wire_protocol/command';
2929
import type { GetMoreOptions } from './wire_protocol/get_more';
30-
import type { InsertOptions, UpdateOptions, RemoveOptions } from './wire_protocol/index';
3130
import type { Stream } from './connect';
3231
import type { LoggerOptions } from '../logger';
3332
import type { QueryOptions } from './wire_protocol/query';
33+
import type { WriteCommandOptions } from './wire_protocol/write_command';
3434

3535
const kStream = Symbol('stream');
3636
const kQueue = Symbol('queue');
@@ -280,17 +280,17 @@ export class Connection extends EventEmitter {
280280
}
281281

282282
/** @internal */
283-
insert(ns: string, ops: Document[], options: InsertOptions, callback: Callback): void {
283+
insert(ns: string, ops: Document[], options: WriteCommandOptions, callback: Callback): void {
284284
wp.insert(makeServerTrampoline(this), ns, ops, options, callback);
285285
}
286286

287287
/** @internal */
288-
update(ns: string, ops: Document[], options: UpdateOptions, callback: Callback): void {
288+
update(ns: string, ops: Document[], options: WriteCommandOptions, callback: Callback): void {
289289
wp.update(makeServerTrampoline(this), ns, ops, options, callback);
290290
}
291291

292292
/** @internal */
293-
remove(ns: string, ops: Document[], options: RemoveOptions, callback: Callback): void {
293+
remove(ns: string, ops: Document[], options: WriteCommandOptions, callback: Callback): void {
294294
wp.remove(makeServerTrampoline(this), ns, ops, options, callback);
295295
}
296296
}

src/cmap/wire_protocol/index.ts

+3-12
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,31 @@ import type { Callback } from '../../utils';
1111

1212
export { writeCommand };
1313

14-
/** @internal */
15-
export type InsertOptions = WriteCommandOptions;
16-
1714
export function insert(
1815
server: Server,
1916
ns: string,
2017
ops: Document[],
21-
options: InsertOptions,
18+
options: WriteCommandOptions,
2219
callback: Callback
2320
): void {
2421
writeCommand(server, 'insert', 'documents', ns, ops, options, callback);
2522
}
2623

27-
/** @internal */
28-
export type UpdateOptions = WriteCommandOptions;
29-
3024
export function update(
3125
server: Server,
3226
ns: string,
3327
ops: Document[],
34-
options: UpdateOptions,
28+
options: WriteCommandOptions,
3529
callback: Callback
3630
): void {
3731
writeCommand(server, 'update', 'updates', ns, ops, options, callback);
3832
}
3933

40-
/** @internal */
41-
export type RemoveOptions = WriteCommandOptions;
42-
4334
export function remove(
4435
server: Server,
4536
ns: string,
4637
ops: Document[],
47-
options: RemoveOptions,
38+
options: WriteCommandOptions,
4839
callback: Callback
4940
): void {
5041
writeCommand(server, 'delete', 'deletes', ns, ops, options, callback);

0 commit comments

Comments
 (0)