Skip to content

Commit 74a3015

Browse files
comments
1 parent 2b81a7f commit 74a3015

File tree

6 files changed

+312
-351
lines changed

6 files changed

+312
-351
lines changed
File renamed without changes.

.prettierrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"singleQuote": true,
3+
"tabWidth": 2,
4+
"printWidth": 100,
5+
"arrowParens": "avoid",
6+
"trailingComma": "none"
7+
}

src/index.ts

Lines changed: 47 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { URL, URLSearchParams } from "whatwg-url";
2+
import { URL, URLSearchParams } from 'whatwg-url';
33
import {
44
redactValidConnectionString,
55
redactConnectionString,
6-
ConnectionStringRedactionOptions,
7-
} from "./redact";
6+
ConnectionStringRedactionOptions
7+
} from './redact';
88
export { redactConnectionString, ConnectionStringRedactionOptions };
99

10-
const DUMMY_HOSTNAME = "__this_is_a_placeholder__";
10+
const DUMMY_HOSTNAME = '__this_is_a_placeholder__';
1111

1212
function connectionStringHasValidScheme(connectionString: string) {
13-
return (
14-
connectionString.startsWith("mongodb://") ||
15-
connectionString.startsWith("mongodb+srv://")
16-
);
13+
return connectionString.startsWith('mongodb://') || connectionString.startsWith('mongodb+srv://');
1714
}
1815

1916
// Adapted from the Node.js driver code:
@@ -50,9 +47,7 @@ class CaseInsensitiveMap<K extends string = string> extends Map<K, string> {
5047
}
5148
}
5249

53-
function caseInsenstiveURLSearchParams<K extends string = string>(
54-
Ctor: typeof URLSearchParams,
55-
) {
50+
function caseInsenstiveURLSearchParams<K extends string = string>(Ctor: typeof URLSearchParams) {
5651
return class CaseInsenstiveURLSearchParams extends Ctor {
5752
append(name: K, value: any): void {
5853
return super.append(this._normalizeKey(name), value);
@@ -114,7 +109,7 @@ abstract class URLWithoutHost extends URL {
114109

115110
class MongoParseError extends Error {
116111
get name(): string {
117-
return "MongoParseError";
112+
return 'MongoParseError';
118113
}
119114
}
120115

@@ -134,7 +129,7 @@ export class ConnectionString extends URLWithoutHost {
134129
const { looseValidation } = options;
135130
if (!looseValidation && !connectionStringHasValidScheme(uri)) {
136131
throw new MongoParseError(
137-
'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"',
132+
'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"'
138133
);
139134
}
140135

@@ -147,84 +142,72 @@ export class ConnectionString extends URLWithoutHost {
147142

148143
if (!looseValidation) {
149144
if (!protocol || !hosts) {
150-
throw new MongoParseError(
151-
`Protocol and host list are required in "${uri}"`,
152-
);
145+
throw new MongoParseError(`Protocol and host list are required in "${uri}"`);
153146
}
154147

155148
try {
156-
decodeURIComponent(username ?? "");
157-
decodeURIComponent(password ?? "");
149+
decodeURIComponent(username ?? '');
150+
decodeURIComponent(password ?? '');
158151
} catch (err) {
159152
throw new MongoParseError((err as Error).message);
160153
}
161154

162155
// characters not permitted in username nor password Set([':', '/', '?', '#', '[', ']', '@'])
163156
const illegalCharacters = /[:/?#[\]@]/gi;
164157
if (username?.match(illegalCharacters)) {
165-
throw new MongoParseError(
166-
`Username contains unescaped characters ${username}`,
167-
);
158+
throw new MongoParseError(`Username contains unescaped characters ${username}`);
168159
}
169160
if (!username || !password) {
170-
const uriWithoutProtocol = uri.replace(`${protocol}://`, "");
171-
if (
172-
uriWithoutProtocol.startsWith("@") ||
173-
uriWithoutProtocol.startsWith(":")
174-
) {
175-
throw new MongoParseError("URI contained empty userinfo section");
161+
const uriWithoutProtocol = uri.replace(`${protocol}://`, '');
162+
if (uriWithoutProtocol.startsWith('@') || uriWithoutProtocol.startsWith(':')) {
163+
throw new MongoParseError('URI contained empty userinfo section');
176164
}
177165
}
178166

179167
if (password?.match(illegalCharacters)) {
180-
throw new MongoParseError("Password contains unescaped characters");
168+
throw new MongoParseError('Password contains unescaped characters');
181169
}
182170
}
183171

184-
let authString = "";
185-
if (typeof username === "string") authString += username;
186-
if (typeof password === "string") authString += `:${password}`;
187-
if (authString) authString += "@";
172+
let authString = '';
173+
if (typeof username === 'string') authString += username;
174+
if (typeof password === 'string') authString += `:${password}`;
175+
if (authString) authString += '@';
188176

189177
try {
190-
super(
191-
`${protocol.toLowerCase()}://${authString}${DUMMY_HOSTNAME}${rest}`,
192-
);
178+
super(`${protocol.toLowerCase()}://${authString}${DUMMY_HOSTNAME}${rest}`);
193179
} catch (err: any) {
194180
if (looseValidation) {
195181
// Call the constructor again, this time with loose validation off,
196182
// for a better error message
197183
// eslint-disable-next-line no-new
198184
new ConnectionString(uri, {
199185
...options,
200-
looseValidation: false,
186+
looseValidation: false
201187
});
202188
}
203-
if (typeof err.message === "string") {
189+
if (typeof err.message === 'string') {
204190
err.message = err.message.replace(DUMMY_HOSTNAME, hosts);
205191
}
206192
throw err;
207193
}
208-
this._hosts = hosts.split(",");
194+
this._hosts = hosts.split(',');
209195

210196
if (!looseValidation) {
211197
if (this.isSRV && this.hosts.length !== 1) {
212-
throw new MongoParseError(
213-
"mongodb+srv URI cannot have multiple service names",
214-
);
198+
throw new MongoParseError('mongodb+srv URI cannot have multiple service names');
215199
}
216-
if (this.isSRV && this.hosts.some((host) => host.includes(":"))) {
217-
throw new MongoParseError("mongodb+srv URI cannot have port number");
200+
if (this.isSRV && this.hosts.some(host => host.includes(':'))) {
201+
throw new MongoParseError('mongodb+srv URI cannot have port number');
218202
}
219203
}
220204

221205
if (!this.pathname) {
222-
this.pathname = "/";
206+
this.pathname = '/';
223207
}
224208
Object.setPrototypeOf(
225209
this.searchParams,
226-
caseInsenstiveURLSearchParams(this.searchParams.constructor as any)
227-
.prototype,
210+
caseInsenstiveURLSearchParams(this.searchParams.constructor as any).prototype
228211
);
229212
}
230213

@@ -235,29 +218,29 @@ export class ConnectionString extends URLWithoutHost {
235218
return DUMMY_HOSTNAME as never;
236219
}
237220
set host(_ignored: never) {
238-
throw new Error("No single host for connection string");
221+
throw new Error('No single host for connection string');
239222
}
240223
get hostname(): never {
241224
return DUMMY_HOSTNAME as never;
242225
}
243226
set hostname(_ignored: never) {
244-
throw new Error("No single host for connection string");
227+
throw new Error('No single host for connection string');
245228
}
246229
get port(): never {
247-
return "" as never;
230+
return '' as never;
248231
}
249232
set port(_ignored: never) {
250-
throw new Error("No single host for connection string");
233+
throw new Error('No single host for connection string');
251234
}
252235
get href(): string {
253236
return this.toString();
254237
}
255238
set href(_ignored: string) {
256-
throw new Error("Cannot set href for connection strings");
239+
throw new Error('Cannot set href for connection strings');
257240
}
258241

259242
get isSRV(): boolean {
260-
return this.protocol.includes("srv");
243+
return this.protocol.includes('srv');
261244
}
262245

263246
get hosts(): string[] {
@@ -269,12 +252,12 @@ export class ConnectionString extends URLWithoutHost {
269252
}
270253

271254
toString(): string {
272-
return super.toString().replace(DUMMY_HOSTNAME, this.hosts.join(","));
255+
return super.toString().replace(DUMMY_HOSTNAME, this.hosts.join(','));
273256
}
274257

275258
clone(): ConnectionString {
276259
return new ConnectionString(this.toString(), {
277-
looseValidation: true,
260+
looseValidation: true
278261
});
279262
}
280263

@@ -284,12 +267,11 @@ export class ConnectionString extends URLWithoutHost {
284267

285268
typedSearchParams<T extends Record<string, any>>() {
286269
const _sametype =
287-
(false as true) &&
288-
new (caseInsenstiveURLSearchParams<keyof T & string>(URLSearchParams))();
270+
(false as true) && new (caseInsenstiveURLSearchParams<keyof T & string>(URLSearchParams))();
289271
return this.searchParams as unknown as typeof _sametype;
290272
}
291273

292-
[Symbol.for("nodejs.util.inspect.custom")](): any {
274+
[Symbol.for('nodejs.util.inspect.custom')](): any {
293275
const {
294276
href,
295277
origin,
@@ -300,7 +282,7 @@ export class ConnectionString extends URLWithoutHost {
300282
pathname,
301283
search,
302284
searchParams,
303-
hash,
285+
hash
304286
} = this;
305287
return {
306288
href,
@@ -312,7 +294,7 @@ export class ConnectionString extends URLWithoutHost {
312294
pathname,
313295
search,
314296
searchParams,
315-
hash,
297+
hash
316298
};
317299
}
318300
}
@@ -322,27 +304,24 @@ export class ConnectionString extends URLWithoutHost {
322304
* readPreferenceTags connection string parameters.
323305
*/
324306
export class CommaAndColonSeparatedRecord<
325-
K extends Record<string, unknown> = Record<string, unknown>,
307+
K extends Record<string, unknown> = Record<string, unknown>
326308
> extends CaseInsensitiveMap<keyof K & string> {
327309
constructor(from?: string | null) {
328310
super();
329-
for (const entry of (from ?? "").split(",")) {
311+
for (const entry of (from ?? '').split(',')) {
330312
if (!entry) continue;
331-
const colonIndex = entry.indexOf(":");
313+
const colonIndex = entry.indexOf(':');
332314
// Use .set() to properly account for case insensitivity
333315
if (colonIndex === -1) {
334-
this.set(entry as keyof K & string, "");
316+
this.set(entry as keyof K & string, '');
335317
} else {
336-
this.set(
337-
entry.slice(0, colonIndex) as keyof K & string,
338-
entry.slice(colonIndex + 1),
339-
);
318+
this.set(entry.slice(0, colonIndex) as keyof K & string, entry.slice(colonIndex + 1));
340319
}
341320
}
342321
}
343322

344323
toString(): string {
345-
return [...this].map((entry) => entry.join(":")).join(",");
324+
return [...this].map(entry => entry.join(':')).join(',');
346325
}
347326
}
348327

src/redact.ts

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ConnectionString, { CommaAndColonSeparatedRecord } from "./index";
1+
import ConnectionString, { CommaAndColonSeparatedRecord } from './index';
22

33
export interface ConnectionStringRedactionOptions {
44
redactUsernames?: boolean;
@@ -7,44 +7,42 @@ export interface ConnectionStringRedactionOptions {
77

88
export function redactValidConnectionString(
99
inputUrl: Readonly<ConnectionString>,
10-
options?: ConnectionStringRedactionOptions,
10+
options?: ConnectionStringRedactionOptions
1111
): ConnectionString {
1212
const url = inputUrl.clone();
13-
const replacementString = options?.replacementString ?? "_credentials_";
13+
const replacementString = options?.replacementString ?? '_credentials_';
1414
const redactUsernames = options?.redactUsernames ?? true;
1515

1616
if ((url.username || url.password) && redactUsernames) {
1717
url.username = replacementString;
18-
url.password = "";
18+
url.password = '';
1919
} else if (url.password) {
2020
url.password = replacementString;
2121
}
22-
if (url.searchParams.has("authMechanismProperties")) {
23-
const props = new CommaAndColonSeparatedRecord(
24-
url.searchParams.get("authMechanismProperties"),
25-
);
26-
if (props.get("AWS_SESSION_TOKEN")) {
27-
props.set("AWS_SESSION_TOKEN", replacementString);
28-
url.searchParams.set("authMechanismProperties", props.toString());
22+
if (url.searchParams.has('authMechanismProperties')) {
23+
const props = new CommaAndColonSeparatedRecord(url.searchParams.get('authMechanismProperties'));
24+
if (props.get('AWS_SESSION_TOKEN')) {
25+
props.set('AWS_SESSION_TOKEN', replacementString);
26+
url.searchParams.set('authMechanismProperties', props.toString());
2927
}
3028
}
31-
if (url.searchParams.has("tlsCertificateKeyFilePassword")) {
32-
url.searchParams.set("tlsCertificateKeyFilePassword", replacementString);
29+
if (url.searchParams.has('tlsCertificateKeyFilePassword')) {
30+
url.searchParams.set('tlsCertificateKeyFilePassword', replacementString);
3331
}
34-
if (url.searchParams.has("proxyUsername") && redactUsernames) {
35-
url.searchParams.set("proxyUsername", replacementString);
32+
if (url.searchParams.has('proxyUsername') && redactUsernames) {
33+
url.searchParams.set('proxyUsername', replacementString);
3634
}
37-
if (url.searchParams.has("proxyPassword")) {
38-
url.searchParams.set("proxyPassword", replacementString);
35+
if (url.searchParams.has('proxyPassword')) {
36+
url.searchParams.set('proxyPassword', replacementString);
3937
}
4038
return url;
4139
}
4240

4341
export function redactConnectionString(
4442
uri: string,
45-
options?: ConnectionStringRedactionOptions,
43+
options?: ConnectionStringRedactionOptions
4644
): string {
47-
const replacementString = options?.replacementString ?? "<credentials>";
45+
const replacementString = options?.replacementString ?? '<credentials>';
4846
const redactUsernames = options?.redactUsernames ?? true;
4947

5048
let parsed: ConnectionString | undefined;
@@ -56,7 +54,7 @@ export function redactConnectionString(
5654
if (parsed) {
5755
// If we can parse the connection string, use the more precise
5856
// redaction logic.
59-
options = { ...options, replacementString: "___credentials___" };
57+
options = { ...options, replacementString: '___credentials___' };
6058
return parsed
6159
.redact(options)
6260
.toString()
@@ -68,22 +66,15 @@ export function redactConnectionString(
6866
const R = replacementString; // alias for conciseness
6967
const replacements: ((uri: string) => string)[] = [
7068
// Username and password
71-
(uri) =>
72-
uri.replace(
73-
redactUsernames ? /(\/\/)(.*)(@)/g : /(\/\/[^@]*:)(.*)(@)/g,
74-
`$1${R}$3`,
75-
),
69+
uri => uri.replace(redactUsernames ? /(\/\/)(.*)(@)/g : /(\/\/[^@]*:)(.*)(@)/g, `$1${R}$3`),
7670
// AWS IAM Session Token as part of query parameter
77-
(uri) => uri.replace(/(AWS_SESSION_TOKEN(:|%3A))([^,&]+)/gi, `$1${R}`),
71+
uri => uri.replace(/(AWS_SESSION_TOKEN(:|%3A))([^,&]+)/gi, `$1${R}`),
7872
// tlsCertificateKeyFilePassword query parameter
79-
(uri) => uri.replace(/(tlsCertificateKeyFilePassword=)([^&]+)/gi, `$1${R}`),
73+
uri => uri.replace(/(tlsCertificateKeyFilePassword=)([^&]+)/gi, `$1${R}`),
8074
// proxyUsername query parameter
81-
(uri) =>
82-
redactUsernames
83-
? uri.replace(/(proxyUsername=)([^&]+)/gi, `$1${R}`)
84-
: uri,
75+
uri => (redactUsernames ? uri.replace(/(proxyUsername=)([^&]+)/gi, `$1${R}`) : uri),
8576
// proxyPassword query parameter
86-
(uri) => uri.replace(/(proxyPassword=)([^&]+)/gi, `$1${R}`),
77+
uri => uri.replace(/(proxyPassword=)([^&]+)/gi, `$1${R}`)
8778
];
8879
for (const replacer of replacements) {
8980
uri = replacer(uri);

0 commit comments

Comments
 (0)