diff --git a/.eslintrc.json b/.eslintrc.json
index 8938b77..7dc4755 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -10,7 +10,7 @@
"es6": true
},
"parserOptions": {
- "ecmaVersion": 2019
+ "ecmaVersion": 2023
},
"plugins": [
"prettier"
diff --git a/README.md b/README.md
index 9421d30..6dc0cd5 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ NOTE: The test suite requires an active kerberos deployment.
## Functions
-checkPassword(username, password, service, [defaultRealm], [callback]) ⇒ Promise
+checkPassword(username, password, service, [defaultRealm]) ⇒ Promise.<null>
This function provides a simple way to verify that a user name and password
match those normally used for Kerberos authentication.
It does this by checking that the supplied user name and password can be
@@ -141,14 +141,14 @@ has the correct realms and KDCs listed.
only be used for testing. Do not use this in any production system - your
security could be compromised if you do.
-principalDetails(service, hostname, [callback]) ⇒ Promise
+principalDetails(service, hostname) ⇒ Promise
This function returns the service principal for the server given a service type and hostname.
Details are looked up via the /etc/keytab
file.
-initializeClient(service, [options], [callback]) ⇒ Promise
+initializeClient(service, [options]) ⇒ Promise.<KerberosClient>
Initializes a context for client-side authentication with the given service principal.
-initializeServer(service, [callback]) ⇒ Promise
+initializeServer(service) ⇒ Promise.<KerberosServer>
Initializes a context for server-side authentication with the given service principal.
@@ -168,52 +168,46 @@ security could be compromised if you do.
* [KerberosClient](#KerberosClient)
- * [.step(challenge, [callback])](#KerberosClient+step)
+ * [.step(challenge)](#KerberosClient+step)
- * [.wrap(challenge, [options], [callback])](#KerberosClient+wrap)
+ * [.wrap(challenge, [options])](#KerberosClient+wrap)
- * [.unwrap(challenge, [callback])](#KerberosClient+unwrap)
+ * [.unwrap(challenge)](#KerberosClient+unwrap)
-### *kerberosClient*.step(challenge, [callback])
+### *kerberosClient*.step(challenge)
| Param | Type | Description |
| --- | --- | --- |
| challenge | string
| A string containing the base64-encoded server data (which may be empty for the first step) |
-| [callback] | function
| |
Processes a single kerberos client-side step using the supplied server challenge.
-**Returns**: Promise
- returns Promise if no callback passed
-### *kerberosClient*.wrap(challenge, [options], [callback])
+### *kerberosClient*.wrap(challenge, [options])
| Param | Type | Description |
| --- | --- | --- |
| challenge | string
| The response returned after calling `unwrap` |
-| [options] | object
| Optional settings |
+| [options] | object
| Options |
| [options.user] | string
| The user to authorize |
| [options.protect] | boolean
| Indicates if the wrap should request message confidentiality |
-| [callback] | function
| |
Perform the client side kerberos wrap step.
-**Returns**: Promise
- returns Promise if no callback passed
-### *kerberosClient*.unwrap(challenge, [callback])
+### *kerberosClient*.unwrap(challenge)
| Param | Type | Description |
| --- | --- | --- |
| challenge | string
| A string containing the base64-encoded server data |
-| [callback] | function
| |
Perform the client side kerberos unwrap step
-**Returns**: Promise
- returns Promise if no callback passed
## KerberosServer
@@ -228,19 +222,17 @@ Perform the client side kerberos unwrap step
-### *kerberosServer*.step(challenge, [callback])
+### *kerberosServer*.step(challenge)
| Param | Type | Description |
| --- | --- | --- |
| challenge | string
| A string containing the base64-encoded client data |
-| [callback] | function
| |
Processes a single kerberos server-side step using the supplied client data.
-**Returns**: Promise
- returns Promise if no callback passed
-## checkPassword(username, password, service, [defaultRealm], [callback])
+## checkPassword(username, password, service, [defaultRealm])
| Param | Type | Description |
| --- | --- | --- |
@@ -248,7 +240,6 @@ Processes a single kerberos server-side step using the supplied client data.
| password | string
| The password for the user. |
| service | string
| The Kerberos service to check access for. |
| [defaultRealm] | string
| The default realm to use if one is not supplied in the user argument. |
-| [callback] | function
| |
This function provides a simple way to verify that a user name and password
match those normally used for Kerberos authentication.
@@ -266,25 +257,24 @@ IMPORTANT: This method is vulnerable to KDC spoofing attacks and it should
only be used for testing. Do not use this in any production system - your
security could be compromised if you do.
-**Returns**: Promise
- returns Promise if no callback passed
+**Returns**: Promise.<null>
- returns Promise that rejects if the password is invalid
-## principalDetails(service, hostname, [callback])
+## principalDetails(service, hostname)
| Param | Type | Description |
| --- | --- | --- |
| service | string
| The Kerberos service type for the server. |
| hostname | string
| The hostname of the server. |
-| [callback] | function
| |
This function returns the service principal for the server given a service type and hostname.
Details are looked up via the `/etc/keytab` file.
-**Returns**: Promise
- returns Promise if no callback passed
+**Returns**: Promise
- returns Promise
-## initializeClient(service, [options], [callback])
+## initializeClient(service, [options])
| Param | Type | Description |
| --- | --- | --- |
@@ -293,20 +283,18 @@ Details are looked up via the `/etc/keytab` file.
| [options.principal] | string
| Optional string containing the client principal in the form 'user@realm' (e.g. 'jdoe@example.com'). |
| [options.flags] | number
| Optional integer used to set GSS flags. (e.g. `GSS_C_DELEG_FLAG\|GSS_C_MUTUAL_FLAG\|GSS_C_SEQUENCE_FLAG` will allow for forwarding credentials to the remote host) |
| [options.mechOID] | number
| Optional GSS mech OID. Defaults to None (GSS_C_NO_OID). Other possible values are `GSS_MECH_OID_KRB5`, `GSS_MECH_OID_SPNEGO`. |
-| [callback] | function
| |
Initializes a context for client-side authentication with the given service principal.
-**Returns**: Promise
- returns Promise if no callback passed
+**Returns**: [Promise.<KerberosClient>
](#KerberosClient) - returns Promise
-## initializeServer(service, [callback])
+## initializeServer(service)
| Param | Type | Description |
| --- | --- | --- |
| service | string
| A string containing the service principal in the form 'type@fqdn' (e.g. 'imap@mail.apple.com'). |
-| [callback] | function
| |
Initializes a context for server-side authentication with the given service principal.
-**Returns**: Promise
- returns Promise if no callback passed
+**Returns**: [Promise.<KerberosServer>
](#KerberosServer) - returns Promise
diff --git a/lib/auth_processes/mongodb.js b/lib/auth_processes/mongodb.js
deleted file mode 100644
index 5682e11..0000000
--- a/lib/auth_processes/mongodb.js
+++ /dev/null
@@ -1,161 +0,0 @@
-'use strict';
-const dns = require('dns');
-const kerberos = require('../kerberos');
-
-/**
- * A class that was used for MongoDB kerberos authentication with legacy
- * MongoDB Node drivers (`mongodb<4.0`).
- *
- * Not intended for direct use.
- *
- * @kind class
- *
- * @deprecated This class will be removed in an upcoming major release.
- */
-class MongoAuthProcess {
- constructor(host, port, serviceName, options) {
- options = options || {};
- this.host = host;
- this.port = port;
-
- // Set up service name
- this.serviceName = serviceName || options.gssapiServiceName || 'mongodb';
-
- // Options
- this.canonicalizeHostName =
- typeof options.gssapiCanonicalizeHostName === 'boolean'
- ? options.gssapiCanonicalizeHostName
- : false;
-
- // Set up first transition
- this._transition = firstTransition(this);
-
- // Number of retries
- this.retries = 10;
- }
-
- init(username, password, callback) {
- const self = this;
- this.username = username;
- this.password = password;
-
- // Canonicialize host name if needed
- function performGssapiCanonicalizeHostName(canonicalizeHostName, host, callback) {
- if (!canonicalizeHostName) return callback();
-
- // Attempt to resolve the host name
- dns.resolveCname(host, (err, r) => {
- if (err) return callback(err);
-
- // Get the first resolve host id
- if (Array.isArray(r) && r.length > 0) {
- self.host = r[0];
- }
-
- callback();
- });
- }
-
- // Canonicialize host name if needed
- performGssapiCanonicalizeHostName(this.canonicalizeHostName, this.host, err => {
- if (err) return callback(err);
-
- const initOptions = {};
- if (password != null) {
- Object.assign(initOptions, { user: username, password });
- }
-
- const service =
- process.platform === 'win32'
- ? `${this.serviceName}/${this.host}`
- : `${this.serviceName}@${this.host}`;
-
- kerberos.initializeClient(service, initOptions, (err, client) => {
- if (err) return callback(err, null);
-
- self.client = client;
- callback(null, client);
- });
- });
- }
-
- transition(payload, callback) {
- if (this._transition == null) {
- return callback(new Error('Transition finished'));
- }
-
- this._transition(payload, callback);
- }
-}
-
-function firstTransition(auth) {
- return (payload, callback) => {
- auth.client.step('', (err, response) => {
- if (err) return callback(err);
-
- // Set up the next step
- auth._transition = secondTransition(auth);
-
- // Return the payload
- callback(null, response);
- });
- };
-}
-
-function secondTransition(auth) {
- return (payload, callback) => {
- auth.client.step(payload, (err, response) => {
- if (err && auth.retries === 0) return callback(err);
-
- // Attempt to re-establish a context
- if (err) {
- // Adjust the number of retries
- auth.retries = auth.retries - 1;
-
- // Call same step again
- return auth.transition(payload, callback);
- }
-
- // Set up the next step
- auth._transition = thirdTransition(auth);
-
- // Return the payload
- callback(null, response || '');
- });
- };
-}
-
-function thirdTransition(auth) {
- return (payload, callback) => {
- // GSS Client Unwrap
- auth.client.unwrap(payload, (err, response) => {
- if (err) return callback(err, false);
-
- // Wrap the response
- auth.client.wrap(response, { user: auth.username }, (err, wrapped) => {
- if (err) return callback(err, false);
-
- // Set up the next step
- auth._transition = fourthTransition(auth);
-
- // Return the payload
- callback(null, wrapped);
- });
- });
- };
-}
-
-function fourthTransition(auth) {
- return (payload, callback) => {
- // Set the transition to null
- auth._transition = null;
-
- // Callback with valid authentication
- callback(null, true);
- };
-}
-
-// Set the process
-module.exports = {
- MongoAuthProcess
-};
diff --git a/lib/index.js b/lib/index.js
index 2359362..d3b3fd2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -9,8 +9,3 @@ module.exports = kerberos;
module.exports.Kerberos = kerberos;
module.exports.version = require('../package.json').version;
-
-// Set up the auth processes
-module.exports.processes = {
- MongoAuthProcess: require('./auth_processes/mongodb').MongoAuthProcess
-};
diff --git a/lib/kerberos.js b/lib/kerberos.js
index f768180..c69105a 100644
--- a/lib/kerberos.js
+++ b/lib/kerberos.js
@@ -1,6 +1,7 @@
'use strict';
-const { loadBindings, defineOperation } = require('./util');
+const { promisify } = require('util');
+const { loadBindings } = require('./util');
const kerberos = loadBindings();
const KerberosClient = kerberos.KerberosClient;
@@ -31,52 +32,59 @@ const GSS_MECH_OID_SPNEGO = 6;
* @property {boolean} contextComplete Indicates that authentication has successfully completed or not
*/
+const promisifiedStep = promisify(KerberosClient.prototype.step);
/**
* Processes a single kerberos client-side step using the supplied server challenge.
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge A string containing the base64-encoded server data (which may be empty for the first step)
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise}
*/
-KerberosClient.prototype.step = defineOperation(KerberosClient.prototype.step, [
- { name: 'challenge', type: 'string' },
- { name: 'callback', type: 'function', required: false }
-]);
+KerberosClient.prototype.step = async function step(challenge) {
+ if (typeof challenge !== 'string') {
+ throw new TypeError('parameter `challenge` must be a string.');
+ }
+ return await promisifiedStep.call(this, challenge);
+};
+const promsifiedWrap = promisify(KerberosClient.prototype.wrap);
/**
* Perform the client side kerberos wrap step.
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge The response returned after calling `unwrap`
- * @param {object} [options] Optional settings
+ * @param {object} [options] Options
* @param {string} [options.user] The user to authorize
* @param {boolean} [options.protect] Indicates if the wrap should request message confidentiality
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise}
*/
-KerberosClient.prototype.wrap = defineOperation(KerberosClient.prototype.wrap, [
- { name: 'challenge', type: 'string' },
- { name: 'options', type: 'object' },
- { name: 'callback', type: 'function', required: false }
-]);
+KerberosClient.prototype.wrap = async function wrap(challenge, options = {}) {
+ if (typeof challenge !== 'string') {
+ throw new TypeError('parameter `challenge` must be a string.');
+ }
+ return await promsifiedWrap.call(this, challenge, options);
+};
+
+const promisifiedUnwrap = promisify(KerberosClient.prototype.unwrap);
/**
* Perform the client side kerberos unwrap step
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge A string containing the base64-encoded server data
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise}
*/
-KerberosClient.prototype.unwrap = defineOperation(KerberosClient.prototype.unwrap, [
- { name: 'challenge', type: 'string' },
- { name: 'callback', type: 'function', required: false }
-]);
+KerberosClient.prototype.unwrap = async function unwrap(challenge) {
+ if (typeof challenge !== 'string') {
+ throw new TypeError('parameter `challenge` must be a string.');
+ }
+ return await promisifiedUnwrap.call(this, challenge);
+};
+const promisifiedServerStep = promisify(KerberosServer.prototype.step);
/**
* @class KerberosServer
*
@@ -92,13 +100,16 @@ KerberosClient.prototype.unwrap = defineOperation(KerberosClient.prototype.unwra
* @kind function
* @memberof KerberosServer
* @param {string} challenge A string containing the base64-encoded client data
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise}
*/
-KerberosServer.prototype.step = defineOperation(KerberosServer.prototype.step, [
- { name: 'challenge', type: 'string' },
- { name: 'callback', type: 'function', required: false }
-]);
+KerberosServer.prototype.step = async function step(challenge) {
+ if (typeof challenge !== 'string') {
+ throw new TypeError('parameter `challenge` must be a string.');
+ }
+ return await promisifiedServerStep.call(this, challenge);
+};
+
+const promisifiedCheckPassword = promisify(kerberos.checkPassword);
/**
* This function provides a simple way to verify that a user name and password
@@ -122,17 +133,25 @@ KerberosServer.prototype.step = defineOperation(KerberosServer.prototype.step, [
* @param {string} password The password for the user.
* @param {string} service The Kerberos service to check access for.
* @param {string} [defaultRealm] The default realm to use if one is not supplied in the user argument.
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise} returns Promise that rejects if the password is invalid
*/
-const checkPassword = defineOperation(kerberos.checkPassword, [
- { name: 'username', type: 'string' },
- { name: 'password', type: 'string' },
- { name: 'service', type: 'string' },
- { name: 'defaultRealm', type: 'string', required: false },
- { name: 'callback', type: 'function', required: false }
-]);
-
+async function checkPassword(username, password, service, defaultRealm) {
+ if (typeof username !== 'string') {
+ throw new TypeError('parameter `username` must be a string.');
+ }
+ if (typeof password !== 'string') {
+ throw new TypeError('parameter `password` must be a string.');
+ }
+ if (typeof service !== 'string') {
+ throw new TypeError('parameter `service` must be a string.');
+ }
+ if (defaultRealm && typeof defaultRealm !== 'string') {
+ throw new TypeError('if specified, parameter `defaultRealm` must be a string.');
+ }
+ return await promisifiedCheckPassword.call(this, username, password, service, defaultRealm);
+}
+
+const promisifiedPrincipalDetails = promisify(kerberos.principalDetails);
/**
* This function returns the service principal for the server given a service type and hostname.
*
@@ -141,15 +160,19 @@ const checkPassword = defineOperation(kerberos.checkPassword, [
* @kind function
* @param {string} service The Kerberos service type for the server.
* @param {string} hostname The hostname of the server.
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise} returns Promise
*/
-const principalDetails = defineOperation(kerberos.principalDetails, [
- { name: 'service', type: 'string' },
- { name: 'hostname', type: 'string' },
- { name: 'callback', type: 'function', required: false }
-]);
-
+async function principalDetails(service, hostname) {
+ if (typeof service !== 'string') {
+ throw new TypeError('parameter `service` must be a string.');
+ }
+ if (typeof hostname !== 'string') {
+ throw new TypeError('parameter `hostname` must be a string.');
+ }
+ return await promisifiedPrincipalDetails.call(this, service, hostname);
+}
+
+const promisifiedInitializeClient = promisify(kerberos.initializeClient);
/**
* Initializes a context for client-side authentication with the given service principal.
*
@@ -159,27 +182,30 @@ const principalDetails = defineOperation(kerberos.principalDetails, [
* @param {string} [options.principal] Optional string containing the client principal in the form 'user@realm' (e.g. 'jdoe@example.com').
* @param {number} [options.flags] Optional integer used to set GSS flags. (e.g. `GSS_C_DELEG_FLAG\|GSS_C_MUTUAL_FLAG\|GSS_C_SEQUENCE_FLAG` will allow for forwarding credentials to the remote host)
* @param {number} [options.mechOID] Optional GSS mech OID. Defaults to None (GSS_C_NO_OID). Other possible values are `GSS_MECH_OID_KRB5`, `GSS_MECH_OID_SPNEGO`.
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise} returns Promise
*/
-const initializeClient = defineOperation(kerberos.initializeClient, [
- { name: 'service', type: 'string' },
- { name: 'options', type: 'object', default: { mechOID: GSS_C_NO_OID } },
- { name: 'callback', type: 'function', required: false }
-]);
+async function initializeClient(service, options = { mechOID: GSS_C_NO_OID }) {
+ if (typeof service !== 'string') {
+ throw new TypeError('parameter `service` must be a string.');
+ }
+ return await promisifiedInitializeClient.call(this, service, options);
+}
+
+const promisifiedInitializeServer = promisify(kerberos.initializeServer);
/**
* Initializes a context for server-side authentication with the given service principal.
*
* @kind function
* @param {string} service A string containing the service principal in the form 'type@fqdn' (e.g. 'imap@mail.apple.com').
- * @param {function} [callback]
- * @return {Promise} returns Promise if no callback passed
+ * @return {Promise} returns Promise
*/
-const initializeServer = defineOperation(kerberos.initializeServer, [
- { name: 'service', type: 'string' },
- { name: 'callback', type: 'function', required: false }
-]);
+async function initializeServer(service) {
+ if (typeof service !== 'string') {
+ throw new TypeError('parameter `service` must be a string.');
+ }
+ return await promisifiedInitializeServer.call(this, service);
+}
module.exports = {
initializeClient,
diff --git a/lib/util.js b/lib/util.js
index 7b34acd..83ff146 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -1,86 +1,5 @@
'use strict';
-function validateParameter(parameter, specs, specIndex) {
- const spec = specs[specIndex];
- if (parameter == null && spec.required === false) {
- return;
- }
-
- if (parameter == null) {
- throw new TypeError(`Required parameter \`${spec.name}\` missing`);
- }
-
- const paramType = typeof parameter;
- if (spec.type && paramType !== spec.type) {
- if (spec.required === false) {
- if (specs.slice(specIndex).some(def => def.type === paramType)) {
- return false;
- }
- }
-
- throw new TypeError(
- `Invalid type for parameter \`${spec.name}\`, expected \`${
- spec.type
- }\` but found \`${typeof parameter}\``
- );
- }
-
- return true;
-}
-
-function hasOwnProperty(object, property) {
- return Object.prototype.hasOwnProperty.call(object, property);
-}
-
-/**
- * Monkey-patches an existing method to support parameter validation, as well
- * as adding support for returning Promises if callbacks are not provided.
- *
- * @private
- * @param {function} fn the function to override
- * @param {Array} paramDefs the definitions of each parameter to the function
- */
-function defineOperation(fn, paramDefs) {
- return function () {
- const args = Array.prototype.slice.call(arguments);
- const params = [];
- for (let i = 0, argIdx = 0; i < paramDefs.length; ++i, ++argIdx) {
- const def = paramDefs[i];
- let arg = args[argIdx];
-
- if (hasOwnProperty(def, 'default') && arg == null) arg = def.default;
- if (def.type === 'object' && def.default != null) {
- arg = Object.assign({}, def.default, arg);
- }
-
- // special case to allow `options` to be optional
- if (def.name === 'options' && (typeof arg === 'function' || arg == null)) {
- arg = {};
- }
-
- if (validateParameter(arg, paramDefs, i)) {
- params.push(arg);
- } else {
- argIdx--;
- }
- }
-
- const callback = arguments[arguments.length - 1];
- if (typeof callback !== 'function') {
- return new Promise((resolve, reject) => {
- params.push((err, response) => {
- if (err) return reject(err);
- resolve(response);
- });
-
- fn.apply(this, params);
- });
- }
-
- fn.apply(this, params);
- };
-}
-
function loadBindings() {
try {
return require('../build/Release/kerberos.node');
@@ -96,4 +15,4 @@ function loadBindings() {
}
}
-module.exports = { defineOperation, validateParameter, loadBindings };
+module.exports = { loadBindings };
diff --git a/package-lock.json b/package-lock.json
index d66007e..69337e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,14 +25,12 @@
"eslint-plugin-prettier": "^5.5.4",
"jsdoc-to-markdown": "^9.1.2",
"mocha": "^11.7.1",
- "mongodb": "^6.18.0",
"node-gyp": "^10.1.0",
"prebuild": "^13.0.1",
- "prettier": "^3.6.2",
- "request": "^2.88.2"
+ "prettier": "^3.6.2"
},
"engines": {
- "node": ">=12.9.0"
+ "node": ">=20.19.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -375,16 +373,6 @@
"node": ">=v12.0.0"
}
},
- "node_modules/@mongodb-js/saslprep": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
- "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sparse-bitfield": "^3.0.3"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -543,21 +531,6 @@
"undici-types": "~7.10.0"
}
},
- "node_modules/@types/webidl-conversions": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
- "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
- "dev": true
- },
- "node_modules/@types/whatwg-url": {
- "version": "11.0.3",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz",
- "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==",
- "dev": true,
- "dependencies": {
- "@types/webidl-conversions": "*"
- }
- },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -935,16 +908,6 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
- "node_modules/bson": {
- "version": "6.10.4",
- "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
- "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=16.20.1"
- }
- },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -3459,13 +3422,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/memory-pager": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
- "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/memory-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/memory-stream/-/memory-stream-1.0.0.tgz",
@@ -3849,63 +3805,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
- "node_modules/mongodb": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
- "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@mongodb-js/saslprep": "^1.1.9",
- "bson": "^6.10.4",
- "mongodb-connection-string-url": "^3.0.0"
- },
- "engines": {
- "node": ">=16.20.1"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
- "gcp-metadata": "^5.2.0",
- "kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
- "socks": "^2.7.1"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "gcp-metadata": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- },
- "socks": {
- "optional": true
- }
- }
- },
- "node_modules/mongodb-connection-string-url": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz",
- "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==",
- "dev": true,
- "dependencies": {
- "@types/whatwg-url": "^11.0.2",
- "whatwg-url": "^13.0.0"
- }
- },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5428,16 +5327,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/sparse-bitfield": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "memory-pager": "^1.0.2"
- }
- },
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -5792,18 +5681,6 @@
"node": ">=0.8"
}
},
- "node_modules/tr46": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
- "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.3.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -5990,28 +5867,6 @@
"node": ">=12.17"
}
},
- "node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "dev": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
- "dev": true,
- "dependencies": {
- "tr46": "^4.1.1",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 9e2085d..6ab5950 100644
--- a/package.json
+++ b/package.json
@@ -42,11 +42,9 @@
"eslint-plugin-prettier": "^5.5.4",
"jsdoc-to-markdown": "^9.1.2",
"mocha": "^11.7.1",
- "mongodb": "^6.18.0",
"node-gyp": "^10.1.0",
"prebuild": "^13.0.1",
- "prettier": "^3.6.2",
- "request": "^2.88.2"
+ "prettier": "^3.6.2"
},
"overrides": {
"prebuild": {
diff --git a/test/defineOperation_tests.js b/test/defineOperation_tests.js
deleted file mode 100644
index 3db5ebb..0000000
--- a/test/defineOperation_tests.js
+++ /dev/null
@@ -1,53 +0,0 @@
-'use strict';
-
-const { loadBindings } = require('../lib/util');
-
-const kerberos = loadBindings();
-const defineOperation = require('../lib/util').defineOperation;
-const expect = require('chai').expect;
-
-const testMethod = defineOperation(kerberos._testMethod, [
- { name: 'string', type: 'string' },
- { name: 'shouldError', type: 'boolean', default: false },
- { name: 'optionalString', type: 'string', required: false },
- { name: 'callback', type: 'function', required: false }
-]);
-
-describe('defineOperation', () => {
- it('should validate parameters', function () {
- expect(() => testMethod(42)).to.throw(/Invalid type for parameter/);
- });
-
- it('should validate optional parameters, with valid parameters after', function () {
- expect(() => testMethod('llamas', false, true, () => {})).to.throw(
- /Invalid type for parameter `optionalString`/
- );
- });
-
- it('should support defaults', function (done) {
- expect(() => testMethod('testing')).to.not.throw();
- testMethod('testing', true, err => {
- expect(err).to.exist;
- done();
- });
- });
-
- it('should return a promise if no callback is provided', function () {
- const promise = testMethod('llamas', false);
- expect(promise).to.be.instanceOf(Promise);
- });
-
- it('should use a callback if provided', function (done) {
- testMethod('testing', false, 'optional', (err, result) => {
- expect(err).to.not.exist;
- expect(result).to.equal('optional');
-
- testMethod('testing', true, 'optional', (err, result) => {
- expect(err).to.exist;
- expect(result).to.not.exist;
-
- done();
- });
- });
- });
-});
diff --git a/test/kerberos_tests.js b/test/kerberos_tests.js
index 4be9de6..bdbe20d 100644
--- a/test/kerberos_tests.js
+++ b/test/kerberos_tests.js
@@ -1,6 +1,5 @@
'use strict';
const kerberos = require('../lib/index');
-const request = require('request');
const chai = require('chai');
const expect = chai.expect;
const os = require('os');
@@ -19,109 +18,82 @@ describe('Kerberos', function () {
if (os.type() === 'Windows_NT') this.skip();
});
- it('should lookup principal details on a server', function (done) {
+ it('should lookup principal details on a server', async function () {
const expected = `HTTP/${hostname}@${realm.toUpperCase()}`;
- kerberos.principalDetails('HTTP', hostname, (err, details) => {
- expect(err).to.not.exist;
- expect(details).to.equal(expected);
- done();
- });
+ const details = await kerberos.principalDetails('HTTP', hostname);
+ expect(details).to.equal(expected);
});
- it('should check a given password against a kerberos server', function (done) {
+ it('should check a given password against a kerberos server', async function () {
const service = `HTTP/${hostname}`;
- kerberos.checkPassword(username, password, service, realm.toUpperCase(), err => {
- expect(err).to.not.exist;
- kerberos.checkPassword(username, 'incorrect-password', service, realm.toUpperCase(), err => {
- expect(err).to.exist;
- done();
- });
- });
+ await kerberos.checkPassword(username, password, service, realm.toUpperCase());
+
+ const error = await kerberos
+ .checkPassword(username, 'incorrect-password', service, realm.toUpperCase())
+ .catch(e => e);
+ expect(error).to.be.instanceOf(Error);
});
- it('should authenticate against a kerberos server using GSSAPI', function (done) {
+ it('should authenticate against a kerberos server using GSSAPI', async function () {
const service = `HTTP@${hostname}`;
- kerberos.initializeClient(service, {}, (err, client) => {
- expect(err).to.not.exist;
-
- kerberos.initializeServer(service, (err, server) => {
- expect(err).to.not.exist;
- expect(client.contextComplete).to.be.false;
- expect(server.contextComplete).to.be.false;
-
- client.step('', (err, clientResponse) => {
- expect(err).to.not.exist;
- expect(client.contextComplete).to.be.false;
-
- server.step(clientResponse, (err, serverResponse) => {
- expect(err).to.not.exist;
- expect(client.contextComplete).to.be.false;
-
- client.step(serverResponse, err => {
- expect(err).to.not.exist;
- expect(client.contextComplete).to.be.true;
-
- const expectedUsername = `${username}@${realm.toUpperCase()}`;
- expect(server.username).to.equal(expectedUsername);
- expect(client.username).to.equal(expectedUsername);
- expect(server.targetName).to.not.exist;
- done();
- });
- });
- });
- });
- });
+ const client = await kerberos.initializeClient(service, {});
+ const server = await kerberos.initializeServer(service);
+
+ expect(client.contextComplete).to.be.false;
+ expect(server.contextComplete).to.be.false;
+
+ const clientResponse = await client.step('');
+ expect(client.contextComplete).to.be.false;
+
+ const serverResponse = await server.step(clientResponse);
+
+ expect(client.contextComplete).to.be.false;
+
+ await client.step(serverResponse);
+ expect(client.contextComplete).to.be.true;
+
+ const expectedUsername = `${username}@${realm.toUpperCase()}`;
+ expect(server.username).to.equal(expectedUsername);
+ expect(client.username).to.equal(expectedUsername);
+ expect(server.targetName).to.not.exist;
});
- it('should authenticate against a kerberos HTTP endpoint', function (done) {
+ it('should authenticate against a kerberos HTTP endpoint', async function () {
const service = `HTTP@${hostname}`;
const url = `http://${hostname}:${port}/`;
// send the initial request un-authenticated
- request.get(url, (err, response) => {
- expect(err).to.not.exist;
- expect(response).to.have.property('statusCode', 401);
-
- // validate the response supports the Negotiate protocol
- const authenticateHeader = response.headers['www-authenticate'];
- expect(authenticateHeader).to.exist;
- expect(authenticateHeader).to.equal('Negotiate');
-
- // generate the first Kerberos token
- const mechOID = kerberos.GSS_MECH_OID_KRB5;
- kerberos.initializeClient(service, { mechOID }, (err, client) => {
- expect(err).to.not.exist;
-
- client.step('', (err, kerberosToken) => {
- expect(err).to.not.exist;
-
- // attach the Kerberos token and resend back to the host
- request.get(
- { url, headers: { Authorization: `Negotiate ${kerberosToken}` } },
- (err, response) => {
- expect(err).to.not.exist;
- expect(response.statusCode).to.equal(200);
-
- // validate the headers exist and contain a www-authenticate message
- const authenticateHeader = response.headers['www-authenticate'];
- expect(authenticateHeader).to.exist;
- expect(authenticateHeader).to.startWith('Negotiate');
-
- // verify the return Kerberos token
- const tokenParts = authenticateHeader.split(' ');
- const serverKerberosToken = tokenParts[tokenParts.length - 1];
- client.step(serverKerberosToken, err => {
- expect(err).to.not.exist;
- expect(client.contextComplete).to.be.true;
- done();
- });
- }
- );
- });
- });
+ const initialResponse = await fetch(url);
+ expect(initialResponse.status).to.equal(401);
+
+ // validate the response supports the Negotiate protocol
+ const authenticateHeader = initialResponse.headers.get('www-authenticate');
+ expect(authenticateHeader).to.exist;
+ expect(authenticateHeader).to.equal('Negotiate');
+
+ // generate the first Kerberos token
+ const mechOID = kerberos.GSS_MECH_OID_KRB5;
+ const client = await kerberos.initializeClient(service, { mechOID });
+ const kerberosToken = await client.step('');
+
+ // attach the Kerberos token and resend back to the host
+ const authenticatedResponse = await fetch(url, {
+ headers: { Authorization: `Negotiate ${kerberosToken}` }
});
+ expect(authenticatedResponse.status).to.equal(200);
+
+ // validate the headers exist and contain a www-authenticate message
+ const responseAuthHeader = authenticatedResponse.headers.get('www-authenticate');
+ expect(responseAuthHeader).to.exist;
+ expect(responseAuthHeader).to.startWith('Negotiate');
+
+ // verify the return Kerberos token
+ const tokenParts = responseAuthHeader.split(' ');
+ const serverKerberosToken = tokenParts[tokenParts.length - 1];
+ await client.step(serverKerberosToken);
+ expect(client.contextComplete).to.be.true;
});
describe('Client.wrap()', function () {
@@ -169,4 +141,84 @@ describe('Kerberos', function () {
});
});
});
+
+ describe('parameter validation', function () {
+ test('initializeClient() throws if service is not a string', async function () {
+ expect(await kerberos.initializeClient().catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`service` must be a string/);
+ });
+
+ test('initializeServer() throws if service is not a string', async function () {
+ expect(await kerberos.initializeServer().catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`service` must be a string/);
+ });
+
+ test('principalDetails() throws if service is not a string', async function () {
+ expect(await kerberos.principalDetails(3, 'foo').catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`service` must be a string/);
+ });
+
+ test('principalDetails() throws if hostname is not a string', async function () {
+ expect(await kerberos.principalDetails('foo', 3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`hostname` must be a string/);
+ });
+
+ test('checkPassword() throws if username is not a string', async function () {
+ expect(await kerberos.checkPassword(3, 'password', 'service').catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`username` must be a string/);
+ });
+
+ test('checkPassword() throws if password is not a string', async function () {
+ expect(await kerberos.checkPassword('username', 3, 'service').catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`password` must be a string/);
+ });
+
+ test('checkPassword() throws if service is not a string', async function () {
+ expect(await kerberos.checkPassword('username', 'password', 3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`service` must be a string/);
+ });
+
+ test('KerberosServer.step() throws if challenge is not a string', async function () {
+ const service = `HTTP@${hostname}`;
+
+ const server = await kerberos.initializeServer(service);
+ expect(await server.step(3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`challenge` must be a string/);
+ });
+
+ describe('KerberosClient', function () {
+ let client;
+ beforeEach(async function () {
+ const service = `HTTP@${hostname}`;
+
+ client = await kerberos.initializeClient(service);
+ });
+
+ test('KerberosClient.unwrap() throws if challenge is not a string', async function () {
+ expect(await client.unwrap(3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`challenge` must be a string/);
+ });
+
+ test('KerberosClient.wrap() throws if challenge is not a string', async function () {
+ expect(await client.wrap(3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`challenge` must be a string/);
+ });
+
+ test('KerberosClient.step() throws if challenge is not a string', async function () {
+ expect(await client.step(3).catch(e => e))
+ .to.be.instanceOf(TypeError)
+ .to.match(/`challenge` must be a string/);
+ });
+ });
+ });
});
diff --git a/test/kerberos_win32_tests.js b/test/kerberos_win32_tests.js
deleted file mode 100644
index 550461d..0000000
--- a/test/kerberos_win32_tests.js
+++ /dev/null
@@ -1,191 +0,0 @@
-'use strict';
-const kerberos = require('../lib/index');
-const MongoClient = require('mongodb').MongoClient;
-const expect = require('chai').expect;
-
-const password = process.env.KERBEROS_PASSWORD;
-const realm = process.env.KERBEROS_REALM;
-const hostname = process.env.KERBEROS_HOSTNAME;
-const username = `${process.env.KERBEROS_USERNAME}@${realm}`;
-const service = `mongodb/${hostname}`;
-const port = process.env.KERBEROS_PORT || '27017';
-const upn = username;
-
-function authenticate(options, callback) {
- const db = options.db;
- const krbClient = options.krbClient;
- const challenge = options.challenge || '';
- const start = options.start || false;
- const conversationId = options.conversationId;
-
- let promise;
- if (callback == null || typeof callback !== 'function') {
- promise = new Promise((resolve, reject) => {
- callback = function (err, res) {
- if (err) return reject(err);
- resolve(res);
- };
- });
- }
-
- if (start) {
- krbClient.step('', (err, payload) => {
- expect(err).to.not.exist;
-
- db.command({ saslStart: 1, mechanism: 'GSSAPI', payload }, (err, dbResponse) => {
- expect(err).to.not.exist;
-
- authenticate(
- {
- db,
- krbClient,
- challenge: dbResponse.payload,
- conversationId: dbResponse.conversationId
- },
- callback
- );
- });
- });
-
- return promise;
- }
-
- krbClient.step(challenge, (err, payload) => {
- payload = payload || '';
-
- db.command({ saslContinue: 1, conversationId, payload }, (err, dbResponse) => {
- if (krbClient.contextComplete) {
- callback(null, { challenge: dbResponse.payload, conversationId });
- return;
- }
-
- if (err) return callback(err, null);
- authenticate({ db, krbClient, conversationId, challenge: payload }, callback);
- });
- });
-
- return promise;
-}
-
-const test = {};
-describe('Kerberos (win32)', function () {
- this.timeout(60000);
-
- beforeEach(function () {
- if (process.platform !== 'win32') {
- this.currentTest.skipReason = 'TODO(NODE-4021): Kerberos testing on windows';
- this.skip();
- }
- });
-
- beforeEach(function () {
- test.client = new MongoClient(`mongodb://${hostname}:${port}/`);
- });
-
- afterEach(function () {
- if (test.client) test.client.close().then(() => delete test.client);
- });
-
- it('should create a kerberos client', function () {
- // this is a very basic test used to pass appveyor and provide prebuild binaries
- return kerberos.initializeClient(service, { user: username, password }).then(krbClient => {
- expect(krbClient).to.exist;
- });
- });
-
- it('should work from windows', function (done) {
- test.client.connect((err, client) => {
- expect(err).to.not.exist;
-
- const db = client.db('$external');
-
- kerberos.initializeClient(service, { user: username, password }, (err, krbClient) => {
- expect(err).to.not.exist;
-
- authenticate({ db, krbClient, start: true }, (err, authResponse) => {
- expect(err).to.not.exist;
-
- krbClient.unwrap(authResponse.challenge, (err, unwrapped) => {
- expect(err).to.not.exist;
-
- // RFC-4752
- const challengeBytes = Buffer.from(unwrapped, 'base64');
- expect(challengeBytes).to.have.length(4);
-
- // Manually create an authorization message and encrypt it. This
- // is the "no security layer" message as detailed in RFC-4752,
- // section 3.1, final paragraph. This is also the message created
- // by calling authGSSClientWrap with the "user" option.
- // const UPN = Buffer.from(upn, 'utf8').toString('utf8');
- const msg = Buffer.from(`\x01\x00\x00\x00${upn}`).toString('base64');
- krbClient.wrap(msg, (err, custom) => {
- expect(err).to.not.exist;
- expect(custom).to.exist;
-
- // Wrap using unwrapped and user principal
- krbClient.wrap(unwrapped, { user: upn }, (err, wrapped) => {
- expect(err).to.not.exist;
- expect(wrapped).to.exist;
-
- db.command(
- {
- saslContinue: 1,
- conversationId: authResponse.conversationId,
- payload: wrapped
- },
- err => {
- expect(err).to.not.exist;
- expect(krbClient.username).to.exist;
- done();
- }
- );
- });
- });
- });
- });
- });
- });
- });
-
- it('should work from windows using promises', function () {
- return test.client.connect().then(client => {
- const db = client.db('$external');
-
- return kerberos.initializeClient(service, { user: username, password }).then(krbClient => {
- return authenticate({ db, krbClient, start: true }).then(authResponse => {
- return krbClient.unwrap(authResponse.challenge).then(unwrapped => {
- // RFC-4752
- const challengeBytes = Buffer.from(unwrapped, 'base64');
- expect(challengeBytes).to.have.length(4);
-
- // Manually create an authorization message and encrypt it. This
- // is the "no security layer" message as detailed in RFC-4752,
- // section 3.1, final paragraph. This is also the message created
- // by calling authGSSClientWrap with the "user" option.
- // const UPN = Buffer.from(upn, 'utf8').toString('utf8');
- const msg = Buffer.from(`\x01\x00\x00\x00${upn}`).toString('base64');
- return krbClient
- .wrap(msg)
- .then(custom => {
- expect(custom).to.exist;
-
- // Wrap using unwrapped and user principal
- return krbClient.wrap(unwrapped, { user: upn });
- })
- .then(wrapped => {
- expect(wrapped).to.exist;
- return db.command({
- saslContinue: 1,
- conversationId: authResponse.conversationId,
- payload: wrapped
- });
- })
- .then(() => {
- expect(krbClient.username).to.exist;
- });
- });
- });
- });
- });
- });
-});