Skip to content

Commit 7f97c2a

Browse files
test(NODE-5621): unskip and fix the unicode auth prose spec tests (#3965)
Co-authored-by: Durran Jordan <durran@gmail.com>
1 parent f506b6a commit 7f97c2a

File tree

2 files changed

+210
-47
lines changed

2 files changed

+210
-47
lines changed

test/integration/auth/auth.prose.test.ts

Lines changed: 177 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ describe('Authentication Spec Prose Tests', function () {
305305
);
306306
});
307307

308-
// todo(NODE-5621): fix the issue with unicode characters.
309308
describe('Step 4', function () {
310309
/**
311310
* Step 4
@@ -340,9 +339,15 @@ describe('Authentication Spec Prose Tests', function () {
340339
];
341340

342341
beforeEach(async function () {
343-
utilClient = this.configuration.newClient();
342+
utilClient = this.configuration.newClient(this.configuration.url());
344343
const db = utilClient.db('admin');
345344

345+
try {
346+
await Promise.all(users.map(user => db.removeUser(user.username)));
347+
} catch (err) {
348+
/** We ensure that users are deleted. No action needed. */
349+
}
350+
346351
const createUserCommands = users.map(user => ({
347352
createUser: user.username,
348353
pwd: user.password,
@@ -354,33 +359,189 @@ describe('Authentication Spec Prose Tests', function () {
354359
});
355360

356361
afterEach(async function () {
357-
const db = utilClient.db('admin');
358-
await Promise.all(users.map(user => db.removeUser(user.username)));
359362
await utilClient?.close();
363+
await client?.close();
360364
});
361365

362-
for (const { username, password } of [
363-
{ username: 'IX', password: 'IX' },
364-
{ username: 'IX', password: 'I\u00ADX' },
365-
{ username: '\u2168', password: 'IV' },
366-
{ username: '\u2168', password: 'I\u00ADV' }
367-
]) {
368-
it.skip(
369-
`logs in with username "${username}" and password "${password}"`,
366+
context('auth credentials in options', () => {
367+
it('logs in with non-normalized username and password', metadata, async function () {
368+
const options = {
369+
auth: { username: 'IX', password: 'IX' },
370+
authSource: 'admin',
371+
authMechanism: 'SCRAM-SHA-256'
372+
};
373+
374+
client = this.configuration.newClient({}, options);
375+
const stats = await client.db('admin').stats();
376+
expect(stats).to.exist;
377+
});
378+
379+
it(
380+
'logs in with non-normalized username and normalized password',
370381
metadata,
371382
async function () {
372383
const options = {
373-
auth: { username, password },
384+
auth: { username: 'IX', password: 'I\u00ADX' },
374385
authSource: 'admin',
375386
authMechanism: 'SCRAM-SHA-256'
376387
};
377388

378-
client = this.configuration.newClient(options);
389+
client = this.configuration.newClient({}, options);
379390
const stats = await client.db('admin').stats();
380391
expect(stats).to.exist;
381392
}
382-
).skipReason = 'todo(NODE-5621): fix the issue with unicode characters.';
383-
}
393+
);
394+
395+
it(
396+
'logs in with normalized username and non-normalized password',
397+
metadata,
398+
async function () {
399+
const options = {
400+
auth: { username: '\u2168', password: 'IV' },
401+
authSource: 'admin',
402+
authMechanism: 'SCRAM-SHA-256'
403+
};
404+
405+
client = this.configuration.newClient({}, options);
406+
const stats = await client.db('admin').stats();
407+
expect(stats).to.exist;
408+
}
409+
);
410+
411+
it('logs in with normalized username and normalized password', metadata, async function () {
412+
const options = {
413+
auth: { username: '\u2168', password: 'I\u00ADV' },
414+
authSource: 'admin',
415+
authMechanism: 'SCRAM-SHA-256'
416+
};
417+
418+
client = this.configuration.newClient({}, options);
419+
const stats = await client.db('admin').stats();
420+
expect(stats).to.exist;
421+
});
422+
});
423+
424+
context('auth credentials in url', () => {
425+
context('encoded', () => {
426+
it('logs in with not encoded username and password', metadata, async function () {
427+
const options = {
428+
authSource: 'admin',
429+
authMechanism: 'SCRAM-SHA-256'
430+
};
431+
client = this.configuration.newClient(
432+
this.configuration.url({ username: 'IX', password: 'IX' }),
433+
options
434+
);
435+
const stats = await client.db('admin').stats();
436+
expect(stats).to.exist;
437+
});
438+
439+
it('logs in with not encoded username and encoded password', metadata, async function () {
440+
const options = {
441+
authSource: 'admin',
442+
authMechanism: 'SCRAM-SHA-256'
443+
};
444+
client = this.configuration.newClient(
445+
this.configuration.url({ username: 'IX', password: 'I%C2%ADX' }),
446+
options
447+
);
448+
const stats = await client.db('admin').stats();
449+
expect(stats).to.exist;
450+
});
451+
452+
it('logs in with encoded username and not encoded password', metadata, async function () {
453+
const options = {
454+
authSource: 'admin',
455+
authMechanism: 'SCRAM-SHA-256'
456+
};
457+
client = this.configuration.newClient(
458+
this.configuration.url({ username: '%E2%85%A8', password: 'IV' }),
459+
options
460+
);
461+
const stats = await client.db('admin').stats();
462+
expect(stats).to.exist;
463+
});
464+
465+
it('logs in with encoded username and encoded password', metadata, async function () {
466+
const options = {
467+
authSource: 'admin',
468+
authMechanism: 'SCRAM-SHA-256'
469+
};
470+
client = this.configuration.newClient(
471+
this.configuration.url({ username: '%E2%85%A8', password: 'I%C2%ADV' }),
472+
options
473+
);
474+
const stats = await client.db('admin').stats();
475+
expect(stats).to.exist;
476+
});
477+
});
478+
479+
context('normalized', () => {
480+
it('logs in with non-normalized username and password', metadata, async function () {
481+
const options = {
482+
authSource: 'admin',
483+
authMechanism: 'SCRAM-SHA-256'
484+
};
485+
client = this.configuration.newClient(
486+
this.configuration.url({ username: 'IX', password: 'IX' }),
487+
options
488+
);
489+
const stats = await client.db('admin').stats();
490+
expect(stats).to.exist;
491+
});
492+
493+
it(
494+
'logs in with non-normalized username and normalized password',
495+
metadata,
496+
async function () {
497+
const options = {
498+
authSource: 'admin',
499+
authMechanism: 'SCRAM-SHA-256'
500+
};
501+
client = this.configuration.newClient(
502+
this.configuration.url({ username: 'IX', password: 'I\u00ADX' }),
503+
options
504+
);
505+
const stats = await client.db('admin').stats();
506+
expect(stats).to.exist;
507+
}
508+
);
509+
510+
it(
511+
'logs in with normalized username and non-normalized password',
512+
metadata,
513+
async function () {
514+
const options = {
515+
authSource: 'admin',
516+
authMechanism: 'SCRAM-SHA-256'
517+
};
518+
client = this.configuration.newClient(
519+
this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }),
520+
options
521+
);
522+
const stats = await client.db('admin').stats();
523+
expect(stats).to.exist;
524+
}
525+
);
526+
527+
it(
528+
'logs in with normalized username and normalized password',
529+
metadata,
530+
async function () {
531+
const options = {
532+
authSource: 'admin',
533+
authMechanism: 'SCRAM-SHA-256'
534+
};
535+
client = this.configuration.newClient(
536+
this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }),
537+
options
538+
);
539+
const stats = await client.db('admin').stats();
540+
expect(stats).to.exist;
541+
}
542+
);
543+
});
544+
});
384545
});
385546
});
386547
});

test/tools/runner/config.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -158,88 +158,90 @@ export class TestConfiguration {
158158
return uri.indexOf('MONGODB-OIDC') > -1 && uri.indexOf('PROVIDER_NAME:azure') > -1;
159159
}
160160

161-
newClient(dbOptions?: string | Record<string, any>, serverOptions?: Record<string, any>) {
161+
newClient(urlOrQueryOptions?: string | Record<string, any>, serverOptions?: Record<string, any>) {
162162
serverOptions = Object.assign({}, getEnvironmentalOptions(), serverOptions);
163163

164-
// support MongoClient constructor form (url, options) for `newClient`
165-
if (typeof dbOptions === 'string') {
166-
return new MongoClient(dbOptions, serverOptions);
164+
// Support MongoClient constructor form (url, options) for `newClient`.
165+
if (typeof urlOrQueryOptions === 'string') {
166+
if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) {
167+
throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`);
168+
}
169+
170+
return new MongoClient(urlOrQueryOptions, serverOptions);
167171
}
168172

169-
dbOptions = dbOptions || {};
170-
// Fall back
171-
let dbHost = (serverOptions && serverOptions.host) || this.options.host;
172-
const dbPort = (serverOptions && serverOptions.port) || this.options.port;
173+
const queryOptions = urlOrQueryOptions || {};
174+
175+
// Fall back.
176+
let dbHost = serverOptions.host || this.options.host;
173177
if (dbHost.indexOf('.sock') !== -1) {
174178
dbHost = qs.escape(dbHost);
175179
}
180+
const dbPort = serverOptions.port || this.options.port;
176181

177-
if (this.options.authMechanism) {
178-
Object.assign(dbOptions, {
182+
if (this.options.authMechanism && !serverOptions.authMechanism) {
183+
Object.assign(queryOptions, {
179184
authMechanism: this.options.authMechanism
180185
});
181186
}
182187

183-
if (this.options.authMechanismProperties) {
184-
Object.assign(dbOptions, {
188+
if (this.options.authMechanismProperties && !serverOptions.authMechanismProperties) {
189+
Object.assign(queryOptions, {
185190
authMechanismProperties: convertToConnStringMap(this.options.authMechanismProperties)
186191
});
187192
}
188193

189-
if (this.options.replicaSet) {
190-
Object.assign(dbOptions, { replicaSet: this.options.replicaSet });
194+
if (this.options.replicaSet && !serverOptions.replicaSet) {
195+
Object.assign(queryOptions, { replicaSet: this.options.replicaSet });
191196
}
192197

193198
if (this.options.proxyURIParams) {
194199
for (const [name, value] of Object.entries(this.options.proxyURIParams)) {
195200
if (value) {
196-
dbOptions[name] = value;
201+
queryOptions[name] = value;
197202
}
198203
}
199204
}
200205

201-
// Flatten any options nested under `writeConcern` before we make the connection string
202-
if (dbOptions.writeConcern) {
203-
Object.assign(dbOptions, dbOptions.writeConcern);
204-
delete dbOptions.writeConcern;
206+
// Flatten any options nested under `writeConcern` before we make the connection string.
207+
if (queryOptions.writeConcern && !serverOptions.writeConcern) {
208+
Object.assign(queryOptions, queryOptions.writeConcern);
209+
delete queryOptions.writeConcern;
205210
}
206211

207212
if (this.topologyType === TopologyType.LoadBalanced && !this.isServerless) {
208-
dbOptions.loadBalanced = true;
213+
queryOptions.loadBalanced = true;
209214
}
210215

211216
const urlOptions: url.UrlObject = {
212217
protocol: this.isServerless ? 'mongodb+srv' : 'mongodb',
213218
slashes: true,
214219
hostname: dbHost,
215220
port: this.isServerless ? null : dbPort,
216-
query: dbOptions,
221+
query: queryOptions,
217222
pathname: '/'
218223
};
219224

220-
if (this.options.auth) {
225+
if (this.options.auth && !serverOptions.auth) {
221226
const { username, password } = this.options.auth;
222227
if (username) {
223228
urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
224229
}
225230
}
226231

227-
if (dbOptions.auth) {
228-
const { username, password } = dbOptions.auth;
232+
if (queryOptions.auth) {
233+
const { username, password } = queryOptions.auth;
229234
if (username) {
230235
urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
231236
}
232237
}
233238

234239
if (typeof urlOptions.query === 'object') {
235-
// Auth goes at the top of the uri, not in the searchParams
236-
delete urlOptions.query.auth;
240+
// Auth goes at the top of the uri, not in the searchParams.
241+
delete urlOptions.query?.auth;
237242
}
238243

239244
const connectionString = url.format(urlOptions);
240-
if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) {
241-
throw new Error(`Cannot use options to specify host/port, must be in ${connectionString}`);
242-
}
243245

244246
return new MongoClient(connectionString, serverOptions);
245247
}
@@ -277,8 +279,8 @@ export class TestConfiguration {
277279

278280
url.pathname = `/${options.db}`;
279281

280-
const username = this.options.username || (this.options.auth && this.options.auth.username);
281-
const password = this.options.password || (this.options.auth && this.options.auth.password);
282+
const username = options.username || this.options.auth?.username;
283+
const password = options.password || this.options.auth?.password;
282284

283285
if (username) {
284286
url.username = username;

0 commit comments

Comments
 (0)