@mongodb-js/oidc-plugin
Advanced tools
Comparing version 0.1.0-alpha.1 to 0.1.0-alpha.2
@@ -102,4 +102,26 @@ /// <reference types="node" /> | ||
redirectServerRequestHandler?: RedirectServerRequestHandler; | ||
/** | ||
* A serialized representation of a previous plugin instance's state | ||
* as returned by `.serialize()`. | ||
* | ||
* This option should only be passed if it comes from a trusted source, | ||
* since it contains access tokens that will be sent to MongoDB servers. | ||
*/ | ||
serializedState?: string; | ||
/** | ||
* If set to true, creating the plugin will throw an exception when | ||
* `serializedState` is provided but cannot be deserialized. | ||
* If set to false, invalid serialized state will result in a log | ||
* message being emitted but otherwise be ignored. | ||
*/ | ||
throwOnIncompatibleSerializedState?: boolean; | ||
} | ||
/** @public */ | ||
export interface MongoDBOIDCPluginMongoClientOptions { | ||
readonly authMechanismProperties: { | ||
readonly REQUEST_TOKEN_CALLBACK: OIDCRequestFunction; | ||
readonly REFRESH_TOKEN_CALLBACK: OIDCRefreshFunction; | ||
}; | ||
} | ||
/** @public */ | ||
export interface MongoDBOIDCPlugin { | ||
@@ -112,9 +134,6 @@ /** | ||
* MongoClient driver options. | ||
* | ||
* @public | ||
*/ | ||
readonly mongoClientOptions: { | ||
readonly authMechanismProperties: { | ||
readonly REQUEST_TOKEN_CALLBACK: OIDCRequestFunction; | ||
readonly REFRESH_TOKEN_CALLBACK: OIDCRefreshFunction; | ||
}; | ||
}; | ||
readonly mongoClientOptions: MongoDBOIDCPluginMongoClientOptions; | ||
/** | ||
@@ -124,2 +143,11 @@ * The logger instance passed in the options, or a default one otherwise. | ||
readonly logger: TypedEventEmitter<MongoDBOIDCLogEventsMap>; | ||
/** | ||
* Create a serialized representation of this plugin's state. The result | ||
* can be stored and be later passed to new plugin instances to make | ||
* that instance behave as a resumed version of this instance. | ||
* | ||
* Be aware that this string contains OIDC tokens in plaintext! Do not | ||
* store it without appropriate security mechanisms in place. | ||
*/ | ||
serialize(): Promise<string>; | ||
} | ||
@@ -133,5 +161,10 @@ /** @internal */ | ||
* This plugin instance can be passed to multiple MongoClient instances. | ||
* It caches credentials based on cluster ID and username. If no username is | ||
* provided when connecting to the MongoDB instance, the cache will be shared | ||
* across all MongoClients that use this plugin instance. | ||
* It caches credentials based on cluster OIDC metadata. | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when the | ||
* MongoDB deployments they are connecting to do not share a trust relationship | ||
* since an untrusted server may be able to advertise malicious OIDC metadata | ||
* (this restriction may be lifted in a future version of this library). | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when they | ||
* are being used with different usernames (user principals), in the connection | ||
* string or in the MongoClient options. | ||
* | ||
@@ -138,0 +171,0 @@ * @public |
@@ -12,5 +12,10 @@ "use strict"; | ||
* This plugin instance can be passed to multiple MongoClient instances. | ||
* It caches credentials based on cluster ID and username. If no username is | ||
* provided when connecting to the MongoDB instance, the cache will be shared | ||
* across all MongoClients that use this plugin instance. | ||
* It caches credentials based on cluster OIDC metadata. | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when the | ||
* MongoDB deployments they are connecting to do not share a trust relationship | ||
* since an untrusted server may be able to advertise malicious OIDC metadata | ||
* (this restriction may be lifted in a future version of this library). | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when they | ||
* are being used with different usernames (user principals), in the connection | ||
* string or in the MongoClient options. | ||
* | ||
@@ -24,2 +29,3 @@ * @public | ||
logger: plugin.logger, | ||
serialize: plugin.serialize.bind(plugin), | ||
}; | ||
@@ -26,0 +32,0 @@ exports.publicPluginToInternalPluginMap_DoNotUseOutsideOfTests.set(publicPlugin, plugin); |
export { createMongoDBOIDCPlugin } from './api'; | ||
export type { MongoDBOIDCPlugin, MongoDBOIDCPluginOptions, AuthFlowType, DeviceFlowInformation, OpenBrowserOptions, OpenBrowserReturnType, RedirectServerRequestHandler, RedirectServerRequestInfo, } from './api'; | ||
export type { TypedEventEmitter, OIDCRefreshFunction, OIDCRequestFunction, OIDCMechanismServerStep1, OIDCRequestTokenResult, OIDCAbortSignal, MongoDBOIDCError, MongoDBOIDCLogEventsMap, } from './types'; | ||
export type { MongoDBOIDCPlugin, MongoDBOIDCPluginOptions, AuthFlowType, DeviceFlowInformation, OpenBrowserOptions, OpenBrowserReturnType, RedirectServerRequestHandler, RedirectServerRequestInfo, MongoDBOIDCPluginMongoClientOptions, } from './api'; | ||
export type { TypedEventEmitter, OIDCCallbackContext, OIDCRefreshFunction, OIDCRequestFunction, IdPServerInfo, IdPServerResponse, OIDCAbortSignal, MongoDBOIDCError, MongoDBOIDCLogEventsMap, } from './types'; | ||
export { hookLoggerToMongoLogWriter, MongoLogWriter } from './log-hook'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -102,4 +102,9 @@ "use strict"; | ||
}); | ||
emitter.on('mongodb-oidc-plugin:deserialization-failed', (ev) => { | ||
log.error('OIDC-PLUGIN', mongoLogId(1002000020), `${contextPrefix}-oidc`, 'State deserialization failed', { | ||
...ev, | ||
}); | ||
}); | ||
} | ||
exports.hookLoggerToMongoLogWriter = hookLoggerToMongoLogWriter; | ||
//# sourceMappingURL=log-hook.js.map |
@@ -1,3 +0,3 @@ | ||
import type { MongoDBOIDCLogEventsMap, OIDCAbortSignal, OIDCMechanismServerStep1, OIDCRequestTokenResult, TypedEventEmitter } from './types'; | ||
import type { TokenSet } from 'openid-client'; | ||
import type { MongoDBOIDCLogEventsMap, OIDCCallbackContext, IdPServerInfo, IdPServerResponse, TypedEventEmitter } from './types'; | ||
import { TokenSet } from 'openid-client'; | ||
import type { MongoDBOIDCPlugin, MongoDBOIDCPluginOptions } from './api'; | ||
@@ -10,13 +10,16 @@ /** @internal Exported for testing only */ | ||
readonly logger: TypedEventEmitter<MongoDBOIDCLogEventsMap>; | ||
private readonly mapUserToAuthState; | ||
private readonly mapIdpToAuthState; | ||
readonly mongoClientOptions: MongoDBOIDCPlugin['mongoClientOptions']; | ||
private readonly timers; | ||
constructor(options: Readonly<MongoDBOIDCPluginOptions>); | ||
private _deserialize; | ||
private _serialize; | ||
serialize(): Promise<string>; | ||
private isFlowAllowed; | ||
private getAuthState; | ||
private getRedirectURI; | ||
private getInitialOIDCIssuerAndClientParams; | ||
private getOIDCClient; | ||
private openBrowser; | ||
private notifyDeviceFlow; | ||
private storeTokenSet; | ||
private updateStateWithTokenSet; | ||
private verifyValidUrl; | ||
@@ -26,5 +29,4 @@ private authorizationCodeFlow; | ||
private initiateAuthAttempt; | ||
requestToken(principalName: string | undefined, serverMetadata: OIDCMechanismServerStep1, driverAbortSignal?: OIDCAbortSignal | number): Promise<OIDCRequestTokenResult>; | ||
refreshToken(principalName: string | undefined, serverMetadata: OIDCMechanismServerStep1, previousResult: OIDCRequestTokenResult, driverAbortSignal?: OIDCAbortSignal | number): Promise<OIDCRequestTokenResult>; | ||
requestToken(serverMetadata: IdPServerInfo, context: OIDCCallbackContext): Promise<IdPServerResponse>; | ||
} | ||
//# sourceMappingURL=plugin.d.ts.map |
@@ -25,5 +25,2 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -34,4 +31,4 @@ exports.MongoDBOIDCPluginImpl = exports.automaticRefreshTimeoutMS = void 0; | ||
const openid_client_1 = require("openid-client"); | ||
const openid_client_2 = require("openid-client"); | ||
const rfc_8252_http_server_1 = require("./rfc-8252-http-server"); | ||
const open_1 = __importDefault(require("open")); | ||
const util_2 = require("util"); | ||
@@ -60,3 +57,5 @@ const crypto_1 = require("crypto"); | ||
return async ({ url }) => { | ||
const child = await (0, open_1.default)(url); | ||
// 'open' 9.x+ is ESM-only. | ||
const open = (await Promise.resolve().then(() => __importStar(require('open')))).default; | ||
const child = await open(url); | ||
child.unref(); | ||
@@ -95,3 +94,3 @@ return child; | ||
constructor(options) { | ||
this.mapUserToAuthState = new Map(); | ||
this.mapIdpToAuthState = new Map(); | ||
this.options = options; | ||
@@ -102,7 +101,74 @@ this.logger = options.logger ?? new events_1.EventEmitter(); | ||
REQUEST_TOKEN_CALLBACK: this.requestToken.bind(this), | ||
REFRESH_TOKEN_CALLBACK: this.refreshToken.bind(this), | ||
REFRESH_TOKEN_CALLBACK: this.requestToken.bind(this), | ||
}, | ||
}; | ||
this.timers = { setTimeout, clearTimeout }; | ||
if (options.serializedState) { | ||
this._deserialize(options.serializedState); | ||
} | ||
} | ||
_deserialize(serialized) { | ||
try { | ||
let original; | ||
try { | ||
original = JSON.parse(Buffer.from(serialized, 'base64').toString('utf8')); | ||
} | ||
catch (err) { | ||
throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized: ${err.message}`); | ||
} | ||
if (original.oidcPluginStateVersion !== 0) { | ||
throw new types_1.MongoDBOIDCError(`Stored OIDC data could not be deserialized because of a version mismatch (got ${JSON.stringify(original.oidcPluginStateVersion)}, expected 0)`); | ||
} | ||
for (const [key, serializedState] of original.state) { | ||
const state = { | ||
serverOIDCMetadata: { ...serializedState.serverOIDCMetadata }, | ||
currentAuthAttempt: null, | ||
currentTokenSet: null, | ||
lastIdTokenClaims: serializedState.lastIdTokenClaims | ||
? { ...serializedState.lastIdTokenClaims } | ||
: undefined, | ||
}; | ||
this.updateStateWithTokenSet(state, new openid_client_1.TokenSet(serializedState.currentTokenSet.set)); | ||
this.mapIdpToAuthState.set(key, state); | ||
} | ||
} | ||
catch (err) { | ||
this.logger.emit('mongodb-oidc-plugin:deserialization-failed', { | ||
error: err.message, | ||
}); | ||
// It's not necessary to throw by default here since failure to | ||
// deserialize previous state means that, at worst, users will have | ||
// to re-authenticate. | ||
if (this.options.throwOnIncompatibleSerializedState) | ||
throw err; | ||
} | ||
} | ||
// Separate method so we can re-use the inferred return type in _deserialize() | ||
_serialize() { | ||
return { | ||
oidcPluginStateVersion: 0, | ||
state: [...this.mapIdpToAuthState] | ||
.filter(([, state]) => !!state.currentTokenSet) | ||
.map(([key, state]) => { | ||
return [ | ||
key, | ||
{ | ||
serverOIDCMetadata: { ...state.serverOIDCMetadata }, | ||
currentTokenSet: { | ||
set: { ...state.currentTokenSet?.set }, | ||
}, | ||
lastIdTokenClaims: state.lastIdTokenClaims | ||
? { ...state.lastIdTokenClaims } | ||
: undefined, | ||
}, | ||
]; | ||
}), | ||
}; | ||
} | ||
serialize() { | ||
// Wrap the result using JS-to-JSON-to-UTF8-to-Base64. We could probably | ||
// omit the base64 encoding, but this makes it clearer that it's an opaque | ||
// value that's not intended to be inspected or modified. | ||
return Promise.resolve(Buffer.from(JSON.stringify(this._serialize()), 'utf8').toString('base64')); | ||
} | ||
// Is this flow supported and allowed? | ||
@@ -120,12 +186,12 @@ isFlowAllowed(flow) { | ||
// or create a new one if none exists. | ||
getAuthState(serverMetadata, principalName) { | ||
getAuthState(serverMetadata) { | ||
if (!serverMetadata.clientId) { | ||
throw new types_1.MongoDBOIDCError('No clientId passed in server OIDC metadata object'); | ||
} | ||
principalName ?? (principalName = null); | ||
const key = JSON.stringify({ | ||
clientId: serverMetadata.clientId, | ||
principalName, | ||
// If any part of the server metadata changes, we should probably use | ||
// a new cache entry. | ||
...(0, util_1.normalizeObject)(serverMetadata), | ||
}); | ||
const existing = this.mapUserToAuthState.get(key); | ||
const existing = this.mapIdpToAuthState.get(key); | ||
if (existing) | ||
@@ -138,3 +204,3 @@ return existing; | ||
}; | ||
this.mapUserToAuthState.set(key, newState); | ||
this.mapIdpToAuthState.set(key, newState); | ||
return newState; | ||
@@ -148,29 +214,30 @@ } | ||
} | ||
getInitialOIDCIssuerAndClientParams(serverMetadata) { | ||
async getOIDCClient(state) { | ||
const serverMetadata = state.serverOIDCMetadata; | ||
const scope = [ | ||
...new Set([ | ||
'openid', | ||
'offline_access', | ||
...(serverMetadata.requestScopes ?? []), | ||
]), | ||
].join(' '); | ||
if (state.client) { | ||
return { | ||
scope, | ||
issuer: state.client.issuer, | ||
client: state.client, | ||
}; | ||
} | ||
const issuer = await openid_client_2.Issuer.discover(serverMetadata.issuer); | ||
const client = new issuer.Client({ | ||
client_id: serverMetadata.clientId, | ||
redirect_uris: [this.getRedirectURI()], | ||
response_types: ['code'], | ||
token_endpoint_auth_method: 'none', | ||
}); | ||
state.client = client; | ||
return { | ||
scope: [ | ||
...new Set([ | ||
'openid', | ||
'offline_access', | ||
...(serverMetadata.requestScopes ?? []), | ||
]), | ||
].join(' '), | ||
issuerParams: { | ||
authorization_endpoint: serverMetadata.authorizationEndpoint, | ||
token_endpoint: serverMetadata.tokenEndpoint, | ||
device_authorization_endpoint: serverMetadata.deviceAuthorizationEndpoint, | ||
// Required by the TS definitions, but we just don't have this data | ||
// at this point. We re-define it later where necessary. | ||
issuer: '', | ||
}, | ||
clientParams: { | ||
client_id: serverMetadata.clientId, | ||
client_secret: serverMetadata.clientSecret, | ||
redirect_uris: [this.getRedirectURI()], | ||
response_types: ['code'], | ||
// At least Okta requires this: | ||
token_endpoint_auth_method: serverMetadata.clientSecret | ||
? 'client_secret_post' | ||
: 'none', | ||
}, | ||
scope, | ||
issuer, | ||
client, | ||
}; | ||
@@ -186,3 +253,3 @@ } | ||
// We should never really get to this point | ||
throw new Error('Cannot open browser if `openBrowser` is false'); | ||
throw new types_1.MongoDBOIDCError('Cannot open browser if `openBrowser` is false'); | ||
} | ||
@@ -206,3 +273,3 @@ if (typeof this.options.openBrowser === 'function') { | ||
} | ||
throw new Error('Unknown format for `openBrowser`'); | ||
throw new types_1.MongoDBOIDCError('Unknown format for `openBrowser`'); | ||
} | ||
@@ -212,3 +279,3 @@ async notifyDeviceFlow(deviceFlowInformation) { | ||
// Should never happen. | ||
throw new Error('notifyDeviceFlow() requested but not provided'); | ||
throw new types_1.MongoDBOIDCError('notifyDeviceFlow() requested but not provided'); | ||
} | ||
@@ -218,3 +285,26 @@ this.logger.emit('mongodb-oidc-plugin:notify-device-flow'); | ||
} | ||
storeTokenSet(state, tokenSet, client) { | ||
updateStateWithTokenSet(state, tokenSet) { | ||
// We intend to be able to pass plugin instances to multiple MongoClient | ||
// instances that are connecting to the same MongoDB endpoint. | ||
// We need to prevent a scenario in which a requestToken callback is called | ||
// for client A, the token expires before it is requested again by client A, | ||
// then the plugin is passed to client B which requests a token, and we | ||
// receive mismatching tokens for different users or different audiences. | ||
const idTokenClaims = tokenSet.claims(); | ||
if (state.lastIdTokenClaims) { | ||
for (const claim of ['aud', 'sub']) { | ||
const normalize = (value) => { | ||
return JSON.stringify(Array.isArray(value) ? [...value].sort() : value); | ||
}; | ||
const knownClaim = normalize(state.lastIdTokenClaims[claim]); | ||
const newClaim = normalize(idTokenClaims[claim]); | ||
if (knownClaim !== newClaim) { | ||
throw new types_1.MongoDBOIDCError(`Unexpected '${claim}' field in id token: Expected ${knownClaim}, saw ${newClaim}`); | ||
} | ||
} | ||
} | ||
state.lastIdTokenClaims = { | ||
aud: idTokenClaims.aud, | ||
sub: idTokenClaims.sub, | ||
}; | ||
const timerDuration = automaticRefreshTimeoutMS(tokenSet); | ||
@@ -235,2 +325,3 @@ let timer = timerDuration | ||
this.logger.emit('mongodb-oidc-plugin:refresh-started'); | ||
const { client } = await this.getOIDCClient(state); | ||
const refreshedTokens = await client.refresh(tokenSet); | ||
@@ -240,3 +331,3 @@ // Check again to avoid race conditions. | ||
this.logger.emit('mongodb-oidc-plugin:refresh-succeeded'); | ||
this.storeTokenSet(state, refreshedTokens, client); | ||
this.updateStateWithTokenSet(state, refreshedTokens); | ||
return true; | ||
@@ -256,2 +347,3 @@ } | ||
}; | ||
this.logger.emit('mongodb-oidc-plugin:state-updated'); | ||
} | ||
@@ -275,8 +367,6 @@ verifyValidUrl(serverOIDCMetadata, key) { | ||
async authorizationCodeFlow(state, signal) { | ||
this.verifyValidUrl(state.serverOIDCMetadata, 'authorizationEndpoint'); | ||
const { scope, issuerParams, clientParams } = this.getInitialOIDCIssuerAndClientParams(state.serverOIDCMetadata); | ||
const codeVerifier = openid_client_1.generators.codeVerifier(); | ||
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier); | ||
let issuer = new openid_client_1.Issuer(issuerParams); | ||
let client = new issuer.Client(clientParams); | ||
this.verifyValidUrl(state.serverOIDCMetadata, 'issuer'); | ||
const { scope, client } = await this.getOIDCClient(state); | ||
const codeVerifier = openid_client_2.generators.codeVerifier(); | ||
const codeChallenge = openid_client_2.generators.codeChallenge(codeVerifier); | ||
const oidcStateParam = (await (0, util_2.promisify)(crypto_1.randomBytes)(16)).toString('hex'); | ||
@@ -348,22 +438,2 @@ const server = new rfc_8252_http_server_1.RFC8252HTTPServer({ | ||
const params = client.callbackParams(paramsUrl); | ||
// The oidc-client library requires `issuer` to be set here; we did not | ||
// set it when we assembled the original `issuerParams` (because it was | ||
// not available as information), but we should have received it from the | ||
// callback parameters and can set it here. | ||
issuerParams.issuer = String(params.iss); | ||
issuer = new openid_client_1.Issuer(issuerParams); | ||
client = new issuer.Client(clientParams); | ||
client.validateIdToken = () => { | ||
// Do not attempt to validate the ID token. The client library would | ||
// do this by default, but we do not have access to the necessary | ||
// JWKS here. This is in direct conflict with the OIDC spec (!!) | ||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation: | ||
// | ||
// > Clients MUST validate the ID Token in the Token Response in the following manner: [...] | ||
// | ||
// We accept this because for the purposes of this plugin, we do not | ||
// interact with the identity token, nor do we actually consume the | ||
// access token. We leave it to the the server to validate that the token is | ||
// valid, refers to the right identity, and comes from the right source. | ||
}; | ||
const tokenSet = await client.callback(this.getRedirectURI(), params, { | ||
@@ -373,16 +443,11 @@ code_verifier: codeVerifier, | ||
}); | ||
this.storeTokenSet(state, tokenSet, client); | ||
this.updateStateWithTokenSet(state, tokenSet); | ||
} | ||
async deviceAuthorizationFlow(state, signal) { | ||
this.verifyValidUrl(state.serverOIDCMetadata, 'deviceAuthorizationEndpoint'); | ||
const { scope, issuerParams, clientParams } = this.getInitialOIDCIssuerAndClientParams(state.serverOIDCMetadata); | ||
const issuer = new openid_client_1.Issuer(issuerParams); | ||
const client = new issuer.Client(clientParams); | ||
client.validateIdToken = () => { | ||
/* see above */ | ||
}; | ||
this.verifyValidUrl(state.serverOIDCMetadata, 'issuer'); | ||
const { scope, client } = await this.getOIDCClient(state); | ||
await (0, util_1.withAbortCheck)(signal, async ({ signalCheck, signalPromise }) => { | ||
const deviceFlowHandle = await Promise.race([ | ||
client.deviceAuthorization({ | ||
client_id: clientParams.client_id, | ||
client_id: client.metadata.client_id, | ||
scope, | ||
@@ -398,3 +463,3 @@ }), | ||
const tokenSet = await deviceFlowHandle.poll({ signal }); | ||
this.storeTokenSet(state, tokenSet, client); | ||
this.updateStateWithTokenSet(state, tokenSet); | ||
}); | ||
@@ -505,4 +570,7 @@ } | ||
} | ||
async requestToken(principalName, serverMetadata, driverAbortSignal) { | ||
const state = this.getAuthState(serverMetadata, principalName); | ||
async requestToken(serverMetadata, context) { | ||
if (context.version !== 0) { | ||
throw new types_1.MongoDBOIDCError(`OIDC MongoDB driver protocol mismatch: unknown version ${context.version}`); | ||
} | ||
const state = this.getAuthState(serverMetadata); | ||
if (state.currentAuthAttempt) { | ||
@@ -515,5 +583,6 @@ return await state.currentAuthAttempt; | ||
// a timeout in milliseconds as well. | ||
if (typeof driverAbortSignal === 'number') { | ||
driverAbortSignal = (0, util_1.timeoutSignal)(driverAbortSignal); | ||
} | ||
const driverAbortSignal = context.timeoutContext ?? | ||
(context.timeoutSeconds | ||
? (0, util_1.timeoutSignal)(context.timeoutSeconds * 1000) | ||
: undefined); | ||
const newAuthAttempt = this.initiateAuthAttempt(state, driverAbortSignal); | ||
@@ -527,9 +596,4 @@ state.currentAuthAttempt = newAuthAttempt; | ||
} | ||
async refreshToken(principalName, serverMetadata, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
previousResult, driverAbortSignal) { | ||
return await this.requestToken(principalName, serverMetadata, driverAbortSignal); | ||
} | ||
} | ||
exports.MongoDBOIDCPluginImpl = MongoDBOIDCPluginImpl; | ||
//# sourceMappingURL=plugin.js.map |
/** @public */ | ||
export interface MongoDBOIDCLogEventsMap { | ||
'mongodb-oidc-plugin:deserialization-failed': (event: { | ||
error: string; | ||
}) => void; | ||
'mongodb-oidc-plugin:state-updated': () => void; | ||
'mongodb-oidc-plugin:local-redirect-accessed': (event: { | ||
@@ -68,18 +72,15 @@ id: string; | ||
/** | ||
* A copy of the Node.js driver's `OIDCMechanismServerStep1` | ||
* A copy of the Node.js driver's `IdPServerInfo` | ||
* @public | ||
*/ | ||
export interface OIDCMechanismServerStep1 { | ||
authorizationEndpoint?: string; | ||
tokenEndpoint?: string; | ||
deviceAuthorizationEndpoint?: string; | ||
export interface IdPServerInfo { | ||
issuer: string; | ||
clientId: string; | ||
clientSecret?: string; | ||
requestScopes?: string[]; | ||
} | ||
/** | ||
* A copy of the Node.js driver's `OIDCRequestTokenResult` | ||
* A copy of the Node.js driver's `IdPServerResponse` | ||
* @public | ||
*/ | ||
export interface OIDCRequestTokenResult { | ||
export interface IdPServerResponse { | ||
accessToken: string; | ||
@@ -90,6 +91,16 @@ expiresInSeconds?: number; | ||
/** | ||
* A copy of the Node.js driver's `OIDCCallbackContext` | ||
* @public | ||
*/ | ||
export interface OIDCCallbackContext { | ||
refreshToken?: string; | ||
timeoutSeconds?: number; | ||
timeoutContext?: OIDCAbortSignal; | ||
version: number; | ||
} | ||
/** | ||
* A copy of the Node.js driver's `OIDCRequestFunction` | ||
* @public | ||
*/ | ||
export type OIDCRequestFunction = (principalName: string | undefined, idl: OIDCMechanismServerStep1, abortSignal?: OIDCAbortSignal | number) => Promise<OIDCRequestTokenResult>; | ||
export type OIDCRequestFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>; | ||
/** | ||
@@ -99,3 +110,3 @@ * A copy of the Node.js driver's `OIDCRefreshFunction` | ||
*/ | ||
export type OIDCRefreshFunction = (principalName: string | undefined, idl: OIDCMechanismServerStep1, result: OIDCRequestTokenResult, abortSignal?: OIDCAbortSignal | number) => Promise<OIDCRequestTokenResult>; | ||
export type OIDCRefreshFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>; | ||
/** @public */ | ||
@@ -102,0 +113,0 @@ export type OIDCAbortSignal = { |
@@ -22,3 +22,4 @@ /// <reference types="node" /> | ||
export declare function withLock<T extends (...args: any[]) => Promise<any>>(fn: T): (...args: Parameters<T>) => ReturnType<T>; | ||
export declare function normalizeObject<T extends object>(obj: T): T; | ||
export {}; | ||
//# sourceMappingURL=util.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.withLock = exports.timeoutSignal = exports.AbortSignal = exports.AbortController = exports.errorString = exports.withAbortCheck = exports.throwIfAborted = void 0; | ||
exports.normalizeObject = exports.withLock = exports.timeoutSignal = exports.AbortSignal = exports.AbortController = exports.errorString = exports.withAbortCheck = exports.throwIfAborted = void 0; | ||
class AbortError extends Error { | ||
@@ -71,2 +71,7 @@ constructor() { | ||
exports.withLock = withLock; | ||
// Normalize JS objects by sorting keys so that {a:1,b:2} and {b:2,a:1} are equivalent. | ||
function normalizeObject(obj) { | ||
return Object.fromEntries(Object.entries(obj).sort()); | ||
} | ||
exports.normalizeObject = normalizeObject; | ||
//# sourceMappingURL=util.js.map |
104
index.d.ts
@@ -14,5 +14,10 @@ /// <reference types="node" /> | ||
* This plugin instance can be passed to multiple MongoClient instances. | ||
* It caches credentials based on cluster ID and username. If no username is | ||
* provided when connecting to the MongoDB instance, the cache will be shared | ||
* across all MongoClients that use this plugin instance. | ||
* It caches credentials based on cluster OIDC metadata. | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when the | ||
* MongoDB deployments they are connecting to do not share a trust relationship | ||
* since an untrusted server may be able to advertise malicious OIDC metadata | ||
* (this restriction may be lifted in a future version of this library). | ||
* Do *not* pass the plugin instance to multiple MongoClient instances when they | ||
* are being used with different usernames (user principals), in the connection | ||
* string or in the MongoClient options. | ||
* | ||
@@ -43,2 +48,22 @@ * @public | ||
/** | ||
* A copy of the Node.js driver's `IdPServerInfo` | ||
* @public | ||
*/ | ||
export declare interface IdPServerInfo { | ||
issuer: string; | ||
clientId: string; | ||
requestScopes?: string[]; | ||
} | ||
/** | ||
* A copy of the Node.js driver's `IdPServerResponse` | ||
* @public | ||
*/ | ||
export declare interface IdPServerResponse { | ||
accessToken: string; | ||
expiresInSeconds?: number; | ||
refreshToken?: string; | ||
} | ||
/** @public */ | ||
@@ -55,2 +80,6 @@ export declare class MongoDBOIDCError extends Error { | ||
export declare interface MongoDBOIDCLogEventsMap { | ||
'mongodb-oidc-plugin:deserialization-failed': (event: { | ||
error: string; | ||
}) => void; | ||
'mongodb-oidc-plugin:state-updated': () => void; | ||
'mongodb-oidc-plugin:local-redirect-accessed': (event: { | ||
@@ -123,9 +152,6 @@ id: string; | ||
* MongoClient driver options. | ||
* | ||
* @public | ||
*/ | ||
readonly mongoClientOptions: { | ||
readonly authMechanismProperties: { | ||
readonly REQUEST_TOKEN_CALLBACK: OIDCRequestFunction; | ||
readonly REFRESH_TOKEN_CALLBACK: OIDCRefreshFunction; | ||
}; | ||
}; | ||
readonly mongoClientOptions: MongoDBOIDCPluginMongoClientOptions; | ||
/** | ||
@@ -135,5 +161,22 @@ * The logger instance passed in the options, or a default one otherwise. | ||
readonly logger: TypedEventEmitter<MongoDBOIDCLogEventsMap>; | ||
/** | ||
* Create a serialized representation of this plugin's state. The result | ||
* can be stored and be later passed to new plugin instances to make | ||
* that instance behave as a resumed version of this instance. | ||
* | ||
* Be aware that this string contains OIDC tokens in plaintext! Do not | ||
* store it without appropriate security mechanisms in place. | ||
*/ | ||
serialize(): Promise<string>; | ||
} | ||
/** @public */ | ||
export declare interface MongoDBOIDCPluginMongoClientOptions { | ||
readonly authMechanismProperties: { | ||
readonly REQUEST_TOKEN_CALLBACK: OIDCRequestFunction; | ||
readonly REFRESH_TOKEN_CALLBACK: OIDCRefreshFunction; | ||
}; | ||
} | ||
/** @public */ | ||
export declare interface MongoDBOIDCPluginOptions { | ||
@@ -205,2 +248,17 @@ /** | ||
redirectServerRequestHandler?: RedirectServerRequestHandler; | ||
/** | ||
* A serialized representation of a previous plugin instance's state | ||
* as returned by `.serialize()`. | ||
* | ||
* This option should only be passed if it comes from a trusted source, | ||
* since it contains access tokens that will be sent to MongoDB servers. | ||
*/ | ||
serializedState?: string; | ||
/** | ||
* If set to true, creating the plugin will throw an exception when | ||
* `serializedState` is provided but cannot be deserialized. | ||
* If set to false, invalid serialized state will result in a log | ||
* message being emitted but otherwise be ignored. | ||
*/ | ||
throwOnIncompatibleSerializedState?: boolean; | ||
} | ||
@@ -227,12 +285,10 @@ | ||
/** | ||
* A copy of the Node.js driver's `OIDCMechanismServerStep1` | ||
* A copy of the Node.js driver's `OIDCCallbackContext` | ||
* @public | ||
*/ | ||
export declare interface OIDCMechanismServerStep1 { | ||
authorizationEndpoint?: string; | ||
tokenEndpoint?: string; | ||
deviceAuthorizationEndpoint?: string; | ||
clientId: string; | ||
clientSecret?: string; | ||
requestScopes?: string[]; | ||
export declare interface OIDCCallbackContext { | ||
refreshToken?: string; | ||
timeoutSeconds?: number; | ||
timeoutContext?: OIDCAbortSignal; | ||
version: number; | ||
} | ||
@@ -244,3 +300,3 @@ | ||
*/ | ||
export declare type OIDCRefreshFunction = (principalName: string | undefined, idl: OIDCMechanismServerStep1, result: OIDCRequestTokenResult, abortSignal?: OIDCAbortSignal | number) => Promise<OIDCRequestTokenResult>; | ||
export declare type OIDCRefreshFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>; | ||
@@ -251,14 +307,4 @@ /** | ||
*/ | ||
export declare type OIDCRequestFunction = (principalName: string | undefined, idl: OIDCMechanismServerStep1, abortSignal?: OIDCAbortSignal | number) => Promise<OIDCRequestTokenResult>; | ||
export declare type OIDCRequestFunction = (info: IdPServerInfo, context: OIDCCallbackContext) => Promise<IdPServerResponse>; | ||
/** | ||
* A copy of the Node.js driver's `OIDCRequestTokenResult` | ||
* @public | ||
*/ | ||
export declare interface OIDCRequestTokenResult { | ||
accessToken: string; | ||
expiresInSeconds?: number; | ||
refreshToken?: string; | ||
} | ||
/** @public */ | ||
@@ -265,0 +311,0 @@ export declare interface OpenBrowserOptions { |
@@ -16,3 +16,3 @@ { | ||
"homepage": "https://github.com/mongodb-js/oidc-plugin", | ||
"version": "0.1.0-alpha.1", | ||
"version": "0.1.0-alpha.2", | ||
"repository": { | ||
@@ -38,6 +38,5 @@ "type": "git", | ||
"scripts": { | ||
"bootstrap": "npm run compile", | ||
"prepublishOnly": "npm run compile", | ||
"compile": "tsc -p tsconfig.json && api-extractor run && rimraf 'dist/**/*.d.ts*' && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", | ||
"typecheck": "tsc --noEmit", | ||
"typecheck": "tsc -p tsconfig-lint.json --noEmit", | ||
"eslint": "eslint", | ||
@@ -59,5 +58,7 @@ "prettier": "prettier", | ||
"@mongodb-js/mocha-config-compass": "^0.10.0", | ||
"@mongodb-js/oidc-mock-provider": "^0.3.0", | ||
"@mongodb-js/prettier-config-compass": "^0.5.0", | ||
"@mongodb-js/tsconfig-compass": "^0.6.0", | ||
"@mongodb-js/tsconfig-compass": "^1.0.1", | ||
"@types/chai": "^4.2.21", | ||
"@types/download": "^8.0.2", | ||
"@types/express": "^4.17.17", | ||
@@ -68,2 +69,3 @@ "@types/mocha": "^9.0.0", | ||
"@types/sinon-chai": "^3.2.5", | ||
"@types/tar": "^6.1.4", | ||
"@typescript-eslint/eslint-plugin": "^5.52.0", | ||
@@ -73,5 +75,6 @@ "@typescript-eslint/parser": "^5.54.1", | ||
"depcheck": "^1.4.1", | ||
"download": "^8.0.0", | ||
"electron": "^23.1.2", | ||
"electron-mocha": "^11.0.2", | ||
"eslint": "^7.25.0", | ||
"eslint": "^8.39.0", | ||
"eslint-config-prettier": "^8.7.0", | ||
@@ -81,2 +84,4 @@ "eslint-plugin-mocha": "^10.1.0", | ||
"mocha": "^10.2.0", | ||
"mongodb": "github:addaleax/node-mongodb-native#NODE-5191-gitdep", | ||
"mongodb-download-url": "^1.3.0", | ||
"mongodb-log-writer": "^1.1.5", | ||
@@ -87,4 +92,5 @@ "node-fetch": "^3.3.1", | ||
"prettier": "2.3.2", | ||
"rimraf": "^4.4.0", | ||
"sinon": "^9.2.3", | ||
"rimraf": "^5.0.0", | ||
"sinon": "^15.0.3", | ||
"tar": "^6.1.13", | ||
"typescript": "^4.9.5", | ||
@@ -97,5 +103,5 @@ "webdriverio": "^8.5.9", | ||
"express": "^4.18.2", | ||
"open": "^8.4.1", | ||
"open": "^9.1.0", | ||
"openid-client": "^5.4.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
147274
2043
38
+ Addedbig-integer@1.6.52(transitive)
+ Addedbplist-parser@0.2.0(transitive)
+ Addedbundle-name@3.0.0(transitive)
+ Addedcross-spawn@7.0.6(transitive)
+ Addeddefault-browser@4.0.0(transitive)
+ Addeddefault-browser-id@3.0.0(transitive)
+ Addeddefine-lazy-prop@3.0.0(transitive)
+ Addedexeca@5.1.17.2.0(transitive)
+ Addedget-stream@6.0.1(transitive)
+ Addedhuman-signals@2.1.04.3.1(transitive)
+ Addedis-docker@3.0.0(transitive)
+ Addedis-inside-container@1.0.0(transitive)
+ Addedis-stream@2.0.13.0.0(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedmerge-stream@2.0.0(transitive)
+ Addedmimic-fn@2.1.04.0.0(transitive)
+ Addednpm-run-path@4.0.15.3.0(transitive)
+ Addedonetime@5.1.26.0.0(transitive)
+ Addedopen@9.1.0(transitive)
+ Addedpath-key@3.1.14.0.0(transitive)
+ Addedrun-applescript@5.0.0(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedstrip-final-newline@2.0.03.0.0(transitive)
+ Addedtitleize@3.0.0(transitive)
+ Addeduntildify@4.0.0(transitive)
+ Addedwhich@2.0.2(transitive)
- Removeddefine-lazy-prop@2.0.0(transitive)
- Removedopen@8.4.2(transitive)
Updatedopen@^9.1.0