Skip to content
13 changes: 12 additions & 1 deletion src/log-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,15 @@ export function hookLoggerToMongoLogWriter(

emitter.on(
'mongodb-oidc-plugin:auth-succeeded',
({ tokenType, refreshToken, expiresAt, passIdTokenAsAccessToken }) => {
({
tokenType,
refreshToken,
expiresAt,
passIdTokenAsAccessToken,
forceRefreshOrReauth,
willRetryWithForceRefreshOrReauth,
tokenSetId,
}) => {
log.info(
'OIDC-PLUGIN',
mongoLogId(1_002_000_017),
Expand All @@ -252,6 +260,9 @@ export function hookLoggerToMongoLogWriter(
refreshToken,
expiresAt,
passIdTokenAsAccessToken,
forceRefreshOrReauth,
willRetryWithForceRefreshOrReauth,
tokenSetId,
}
);
}
Expand Down
146 changes: 139 additions & 7 deletions src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ function requestToken(
plugin: MongoDBOIDCPlugin,
oidcParams: IdPServerInfo,
abortSignal?: OIDCAbortSignal,
username?: string
username?: string,
refreshToken?: string
): ReturnType<OIDCCallbackFunction> {
return plugin.mongoClientOptions.authMechanismProperties.OIDC_HUMAN_CALLBACK({
timeoutContext: abortSignal,
version: 1,
idpInfo: oidcParams,
username,
refreshToken,
});
}

Expand Down Expand Up @@ -390,6 +392,7 @@ describe('OIDC plugin (local OIDC provider)', function () {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
expect(Object.keys(serializedData.state[0][1]).sort()).to.deep.equal([
'currentTokenSet',
'discardingTokenSets',
'lastIdTokenClaims',
'serverOIDCMetadata',
]);
Expand Down Expand Up @@ -905,24 +908,66 @@ describe('OIDC plugin (local OIDC provider)', function () {

describe('automaticRefreshTimeoutMS', function () {
it('returns the correct automatic refresh timeout', function () {
const now = () => Date.now() / 1000;
expect(automaticRefreshTimeoutMS({})).to.equal(undefined);
expect(automaticRefreshTimeoutMS({ expires_in: 10000 })).to.equal(
expect(automaticRefreshTimeoutMS({ expires_at: now() + 10000 })).to.equal(
undefined
);
expect(
automaticRefreshTimeoutMS({ refresh_token: 'asdf', expires_in: 10000 })
automaticRefreshTimeoutMS({
refresh_token: 'asdf',
expires_at: now() + 10000,
})
).to.equal(9700000);
expect(
automaticRefreshTimeoutMS({ refresh_token: 'asdf', expires_in: 100 })
automaticRefreshTimeoutMS({
refresh_token: 'asdf',
expires_at: now() + 100,
})
).to.equal(50000);
expect(
automaticRefreshTimeoutMS({ refresh_token: 'asdf', expires_in: 10 })
automaticRefreshTimeoutMS(
{
refresh_token: 'asdf',
expires_at: now() + 100,
id_token: '...',
claims() {
return { exp: now() + 500 };
},
},
true
)
).to.equal(250000);
expect(
automaticRefreshTimeoutMS(
{
refresh_token: 'asdf',
expires_at: now() + 100,
id_token: '...',
claims() {
return { exp: now() + 500 };
},
},
false
)
).to.equal(50000);
expect(
automaticRefreshTimeoutMS({
refresh_token: 'asdf',
expires_at: now() + 10,
})
).to.equal(undefined);
expect(
automaticRefreshTimeoutMS({ refresh_token: 'asdf', expires_in: 0 })
automaticRefreshTimeoutMS({
refresh_token: 'asdf',
expires_at: now() + 0,
})
).to.equal(undefined);
expect(
automaticRefreshTimeoutMS({ refresh_token: 'asdf', expires_in: -10 })
automaticRefreshTimeoutMS({
refresh_token: 'asdf',
expires_at: now() + -10,
})
).to.equal(undefined);
});
});
Expand Down Expand Up @@ -1235,6 +1280,93 @@ describe('OIDC plugin (mock OIDC provider)', function () {
});
});

context('when drivers re-request tokens early', function () {
let plugin: MongoDBOIDCPlugin;

beforeEach(function () {
plugin = createMongoDBOIDCPlugin({
openBrowserTimeout: 60_000,
openBrowser: fetchBrowser,
allowedFlows: ['auth-code'],
redirectURI: 'http://localhost:0/callback',
});
});

it('will return a different token even if the existing one is not yet expired', async function () {
const result1 = await requestToken(plugin, {
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
});
const result2 = await requestToken(plugin, {
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
});
const result3 = await requestToken(
plugin,
{
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
},
undefined,
undefined,
result2.refreshToken
);
const result4 = await requestToken(
plugin,
{
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
},
undefined,
undefined,
result2.refreshToken
);
expect(result1).to.deep.equal(result2);
expect(result2.accessToken).not.to.equal(result3.accessToken);
expect(result2.refreshToken).not.to.equal(result3.refreshToken);
expect(result3).to.deep.equal(result4);
});

it('will return only one new token per expired token even when called in parallel', async function () {
const result1 = await requestToken(plugin, {
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
});
const [result2, result3] = await Promise.all([
requestToken(
plugin,
{
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
},
undefined,
undefined,
result1.refreshToken
),
requestToken(
plugin,
{
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
},
undefined,
undefined,
result1.refreshToken
),
]);
expect(result1.accessToken).not.to.equal(result2.accessToken);
expect(result1.refreshToken).not.to.equal(result2.refreshToken);
expect(result2).to.deep.equal(result3);
});
});

context('HTTP request tracking', function () {
it('will log all outgoing HTTP requests', async function () {
const pluginHttpRequests: string[] = [];
Expand Down
Loading
Loading