From ea3d2bcc07cbfabbb00ae5c11e3e4340d9d65fb7 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 29 Jan 2024 22:40:03 +0100 Subject: [PATCH] feat: update for human and non human --- src/cmap/auth/mongo_credentials.ts | 11 ++-- src/cmap/auth/mongodb_oidc.ts | 11 ++-- .../mongodb_oidc/azure_machine_workflow.ts | 3 +- .../auth/mongodb_oidc/callback_workflow.ts | 65 ++++++++++++------- src/index.ts | 8 +-- 5 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index 1e118467184..fa2f5a6082c 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -8,7 +8,7 @@ import { MongoMissingCredentialsError } from '../../error'; import { GSSAPICanonicalizationValue } from './gssapi'; -import type { OIDCRequestFunction } from './mongodb_oidc'; +import type { OIDCCallbackFunction } from './mongodb_oidc'; import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers'; // https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst @@ -58,7 +58,9 @@ export interface AuthMechanismProperties extends Document { CANONICALIZE_HOST_NAME?: GSSAPICanonicalizationValue; AWS_SESSION_TOKEN?: string; /** @experimental */ - OIDC_TOKEN_CALLBACK?: OIDCRequestFunction; + OIDC_CALLBACK?: OIDCCallbackFunction; + /** @experimental */ + OIDC_HUMAN_CALLBACK?: OIDCCallbackFunction; /** @experimental */ PROVIDER_NAME?: 'aws' | 'azure'; /** @experimental */ @@ -206,10 +208,11 @@ export class MongoCredentials { if ( !this.mechanismProperties.PROVIDER_NAME && - !this.mechanismProperties.OIDC_TOKEN_CALLBACK + !this.mechanismProperties.OIDC_CALLBACK && + !this.mechanismProperties.OIDC_HUMAN_CALLBACK ) { throw new MongoInvalidArgumentError( - `Either a PROVIDER_NAME or a OIDC_TOKEN_CALLBACK must be specified for mechanism '${this.mechanism}'.` + `Either a PROVIDER_NAME, OIDC_CALLBACK, or OIDC_HUMAN_CALLBACK must be specified for mechanism '${this.mechanism}'.` ); } diff --git a/src/cmap/auth/mongodb_oidc.ts b/src/cmap/auth/mongodb_oidc.ts index 387d3fbdb95..6d9c3451d0b 100644 --- a/src/cmap/auth/mongodb_oidc.ts +++ b/src/cmap/auth/mongodb_oidc.ts @@ -16,7 +16,7 @@ const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.'; * @public * @experimental */ -export interface IdPServerInfo { +export interface IdPInfo { issuer: string; clientId: string; requestScopes?: string[]; @@ -36,25 +36,28 @@ export interface IdPServerResponse { * @public * @experimental */ -export interface OIDCToken { +export interface OIDCResponse { accessToken: string; expiresInSeconds?: number; + refreshToken?: string; } /** * @public * @experimental */ -export interface OIDCTokenParams { +export interface OIDCCallbackParams { timeoutContext: AbortSignal; version: number; + idpInfo?: IdPInfo; + refreshToken?: string; } /** * @public * @experimental */ -export type OIDCRequestFunction = (params: OIDCTokenParams) => Promise; +export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise; type ProviderName = 'aws' | 'azure' | 'callback'; diff --git a/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts b/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts index 946f8b60ad6..89c97a65717 100644 --- a/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +++ b/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts @@ -5,8 +5,7 @@ import { AzureTokenCache } from './azure_token_cache'; import { MachineWorkflow } from './machine_workflow'; /** Base URL for getting Azure tokens. */ -const AZURE_BASE_URL = - 'http://169.254.169.254/metadata/identity/oauth2/token?'; +const AZURE_BASE_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?'; /** Azure request headers. */ const AZURE_HEADERS = Object.freeze({ Metadata: 'true', Accept: 'application/json' }); diff --git a/src/cmap/auth/mongodb_oidc/callback_workflow.ts b/src/cmap/auth/mongodb_oidc/callback_workflow.ts index d631cb02967..325efdcb99e 100644 --- a/src/cmap/auth/mongodb_oidc/callback_workflow.ts +++ b/src/cmap/auth/mongodb_oidc/callback_workflow.ts @@ -3,12 +3,12 @@ import { BSON, type Document } from 'bson'; import { MongoMissingCredentialsError } from '../../../error'; import { ns } from '../../../utils'; import type { Connection } from '../../connection'; -import type { MongoCredentials } from '../mongo_credentials'; +import type { AuthMechanismProperties, MongoCredentials } from '../mongo_credentials'; import type { - IdPServerInfo, + IdPInfo, IdPServerResponse, - OIDCRequestFunction, - OIDCTokenParams, + OIDCCallbackFunction, + OIDCCallbackParams, Workflow } from '../mongodb_oidc'; import { finishCommandDocument, startCommandDocument } from './command_builders'; @@ -16,8 +16,8 @@ import { finishCommandDocument, startCommandDocument } from './command_builders' /** The current version of OIDC implementation. */ const OIDC_VERSION = 1; -/** 5 minutes in seconds */ -const TIMEOUT_S = 300; +/** 5 minutes in milliseconds */ +const HUMAN_TIMEOUT_MS = 300000; /** Properties allowed on results of callbacks. */ const RESULT_PROPERTIES = ['accessToken', 'expiresInSeconds', 'refreshToken']; @@ -26,7 +26,15 @@ const RESULT_PROPERTIES = ['accessToken', 'expiresInSeconds', 'refreshToken']; const CALLBACK_RESULT_ERROR = 'User provided OIDC callbacks must return a valid object with an accessToken.'; -const NO_REQUEST_CALLBACK = 'No OIDC_TOKEN_CALLBACK provided for callback workflow.'; +const NO_CALLBACK = 'No OIDC_CALLBACK or OIDC_HUMAN_CALLBACK provided for callback workflow.'; + +/** + * The OIDC callback information. + */ +interface OIDCCallbackInfo { + callback: OIDCCallbackFunction; + isHumanWorkflow: boolean; +} /** * OIDC implementation of a callback based workflow. @@ -67,19 +75,11 @@ export class CallbackWorkflow implements Workflow { credentials: MongoCredentials, response?: Document ): Promise { - const requestCallback = credentials.mechanismProperties.OIDC_TOKEN_CALLBACK; - if (!requestCallback) { - throw new MongoMissingCredentialsError(NO_REQUEST_CALLBACK); - } + const callbackInfo = getCallback(credentials.mechanismProperties); const startDocument = await this.startAuthentication(connection, credentials, response); const conversationId = startDocument.conversationId; - const serverResult = BSON.deserialize(startDocument.payload.buffer) as IdPServerInfo; - const tokenResult = await this.fetchAccessToken( - connection, - credentials, - serverResult, - requestCallback - ); + const idpInfo = BSON.deserialize(startDocument.payload.buffer) as IdPInfo; + const tokenResult = await this.fetchAccessToken(connection, credentials, idpInfo, callbackInfo); const result = await this.finishAuthentication( connection, credentials, @@ -136,15 +136,18 @@ export class CallbackWorkflow implements Workflow { private async fetchAccessToken( connection: Connection, credentials: MongoCredentials, - serverInfo: IdPServerInfo, - requestCallback: OIDCRequestFunction + idpInfo: IdPInfo, + callbackInfo: OIDCCallbackInfo ): Promise { - const params: OIDCTokenParams = { - timeoutContext: AbortSignal.timeout(TIMEOUT_S), - version: OIDC_VERSION + const params: OIDCCallbackParams = { + timeoutContext: AbortSignal.timeout( + callbackInfo.isHumanWorkflow ? HUMAN_TIMEOUT_MS : HUMAN_TIMEOUT_MS + ), // TODO: CSOT + version: OIDC_VERSION, + idpInfo: idpInfo // TODO: refreshToken? }; // With no token in the cache we use the request callback. - const result = await requestCallback(params); + const result = await callbackInfo.callback(params); // Validate that the result returned by the callback is acceptable. If it is not // we must clear the token result from the cache. if (isCallbackResultInvalid(result)) { @@ -154,6 +157,20 @@ export class CallbackWorkflow implements Workflow { } } +/** + * Returns a callback, either machine or human, and a flag whether the workflow is + * human or not. + */ +function getCallback(mechanismProperties: AuthMechanismProperties): OIDCCallbackInfo { + if (!mechanismProperties.OIDC_CALLBACK || !mechanismProperties.OIDC_HUMAN_CALLBACK) { + throw new MongoMissingCredentialsError(NO_CALLBACK); + } + if (mechanismProperties.OIDC_CALLBACK) { + return { callback: mechanismProperties.OIDC_CALLBACK, isHumanWorkflow: false }; + } + return { callback: mechanismProperties.OIDC_HUMAN_CALLBACK, isHumanWorkflow: true }; +} + /** * Determines if a result returned from a request or refresh callback * function is invalid. This means the result is nullish, doesn't contain diff --git a/src/index.ts b/src/index.ts index e839e4af106..c4a550c7f2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -250,11 +250,11 @@ export type { MongoCredentialsOptions } from './cmap/auth/mongo_credentials'; export type { - IdPServerInfo, + IdPInfo as IdPServerInfo, IdPServerResponse, - OIDCRequestFunction, - OIDCToken, - OIDCTokenParams + OIDCCallbackFunction as OIDCRequestFunction, + OIDCResponse as OIDCToken, + OIDCCallbackParams as OIDCTokenParams } from './cmap/auth/mongodb_oidc'; export type { MessageHeader,