Skip to content

Commit

Permalink
feat!(devtools-connect): update oidc MONGOSH-1790 (#347)
Browse files Browse the repository at this point in the history
oidc update
  • Loading branch information
mabaasit authored Jun 6, 2024
1 parent 30e0f00 commit bd7ed21
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 105 deletions.
58 changes: 10 additions & 48 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/devtools-connect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
"system-ca": "^1.0.2"
},
"peerDependencies": {
"@mongodb-js/oidc-plugin": "^0.4.0",
"@mongodb-js/oidc-plugin": "^1.0.0",
"mongodb": "^5.8.1 || ^6.0.0",
"mongodb-log-writer": "^1.4.2"
},
"devDependencies": {
"@mongodb-js/oidc-plugin": "^0.4.0",
"@mongodb-js/oidc-plugin": "^1.0.0",
"@mongodb-js/saslprep": "^1.1.7",
"@types/lodash.merge": "^4.6.7",
"@types/mocha": "^9.0.0",
Expand Down
52 changes: 23 additions & 29 deletions packages/devtools-connect/src/ipc-rpc-state-share.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,71 +102,65 @@ describe('IPC RPC state sharing', function () {

describe('StateShareServer/StateShareClient', function () {
let fakeState: DevtoolsConnectionState;
let request: sinon.SinonStub;
let refresh: sinon.SinonStub;
let callback: sinon.SinonStub;
let server: StateShareServer;
let client: StateShareClient;

beforeEach(function () {
request = sinon.stub();
refresh = sinon.stub();
callback = sinon.stub();
fakeState = {
productName: 'MongoDB Something',
oidcPlugin: {
mongoClientOptions: {
authMechanismProperties: {
REFRESH_TOKEN_CALLBACK: refresh,
REQUEST_TOKEN_CALLBACK: request,
OIDC_HUMAN_CALLBACK: callback,
},
},
},
} as unknown as DevtoolsConnectionState;
request.resolves({ accessToken: 'req-accesstoken' });
refresh.resolves({ accessToken: 'ref-accesstoken' });
});

afterEach(async function () {
await server.close();
});

it('can be used to share OIDC state', async function () {
callback.resolves({ accessToken: 'req-accesstoken' });
server = await StateShareServer.create(fakeState);
client = new StateShareClient(server.handle);
const result =
await client.oidcPlugin.mongoClientOptions.authMechanismProperties.REQUEST_TOKEN_CALLBACK(
await client.oidcPlugin.mongoClientOptions.authMechanismProperties.OIDC_HUMAN_CALLBACK(
{
issuer: 'http://localhost/',
clientId: 'clientId',
},
{
version: 0,
idpInfo: {
issuer: 'http://localhost/',
clientId: 'clientId',
},
version: 1,
}
);
expect(result).to.deep.equal({ accessToken: 'req-accesstoken' });
});

it('supports timeoutContext', async function () {
refresh.callsFake(
(_idpInfo: unknown, ctx: { timeoutContext: AbortSignal }) => {
return new Promise((resolve, reject) =>
ctx.timeoutContext.addEventListener('abort', () =>
reject(new Error('aborted'))
)
);
}
);
callback.callsFake((params: { timeoutContext: AbortSignal }) => {
return new Promise((resolve, reject) =>
params.timeoutContext.addEventListener('abort', () =>
reject(new Error('aborted'))
)
);
});
server = await StateShareServer.create(fakeState);
client = new StateShareClient(server.handle);

const abortController = new AbortController();
const result =
client.oidcPlugin.mongoClientOptions.authMechanismProperties.REFRESH_TOKEN_CALLBACK(
{
issuer: 'http://localhost/',
clientId: 'clientId',
},
client.oidcPlugin.mongoClientOptions.authMechanismProperties.OIDC_HUMAN_CALLBACK(
{
version: 0,
idpInfo: {
issuer: 'http://localhost/',
clientId: 'clientId',
},
version: 1,
timeoutContext: abortController.signal,
}
);
Expand Down
40 changes: 14 additions & 26 deletions packages/devtools-connect/src/ipc-rpc-state-share.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type {
MongoDBOIDCPlugin,
IdPServerInfo,
IdPServerResponse,
OIDCCallbackContext,
OIDCCallbackParams,
} from '@mongodb-js/oidc-plugin';
import type { DevtoolsConnectionState } from './connect';
import { createServer as createHTTPServer, request } from 'http';
Expand Down Expand Up @@ -225,26 +224,21 @@ export class StateShareServer extends RpcServer {

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
protected async handleRpc(content: any): Promise<Record<string, unknown>> {
const oidcCallbacks =
this.state.oidcPlugin.mongoClientOptions.authMechanismProperties;
let oidcMethod: keyof typeof oidcCallbacks;
switch (content.method) {
case 'oidc:_abort':
this.abortContexts.get(content.timeoutContextId)?.abort();
return {};
case 'oidc:REQUEST_TOKEN_CALLBACK':
oidcMethod = 'REQUEST_TOKEN_CALLBACK';
// fallthrough
case 'oidc:REFRESH_TOKEN_CALLBACK': {
oidcMethod ??= 'REFRESH_TOKEN_CALLBACK';

case 'oidc:OIDC_HUMAN_CALLBACK': {
const abortController = new AbortController();
this.abortContexts.set(content.timeoutContextId, abortController);
try {
const result = await oidcCallbacks[oidcMethod](content.info, {
...content.context,
timeoutContext: abortController.signal,
});
const result =
await this.state.oidcPlugin.mongoClientOptions.authMechanismProperties.OIDC_HUMAN_CALLBACK(
{
...content.callbackParams,
timeoutContext: abortController.signal,
}
);
return { result };
} finally {
this.abortContexts.delete(content.timeoutContextId);
Expand Down Expand Up @@ -275,13 +269,9 @@ export class StateShareClient extends RpcClient {
logger: new EventEmitter(),
mongoClientOptions: {
authMechanismProperties: {
REQUEST_TOKEN_CALLBACK: this._oidcCallback.bind(
this,
'oidc:REQUEST_TOKEN_CALLBACK'
),
REFRESH_TOKEN_CALLBACK: this._oidcCallback.bind(
OIDC_HUMAN_CALLBACK: this._oidcCallback.bind(
this,
'oidc:REFRESH_TOKEN_CALLBACK'
'oidc:OIDC_HUMAN_CALLBACK'
),
},
},
Expand All @@ -290,13 +280,12 @@ export class StateShareClient extends RpcClient {

private async _oidcCallback(
method: string,
info: IdPServerInfo,
_context: OIDCCallbackContext
params: OIDCCallbackParams
): Promise<IdPServerResponse> {
const timeoutContextId = (await promisify(crypto.randomBytes)(16)).toString(
'base64'
);
const { timeoutContext, ...context } = _context;
const { timeoutContext, ...callbackParams } = params;
const abort = async () => {
await this.makeRpcCall({
method: 'oidc:_abort',
Expand All @@ -308,8 +297,7 @@ export class StateShareClient extends RpcClient {
const { result } = await this.makeRpcCall({
method,
timeoutContextId,
context,
info,
callbackParams,
});
return result as IdPServerResponse;
} finally {
Expand Down

0 comments on commit bd7ed21

Please sign in to comment.