Skip to content

Commit

Permalink
feat: update for human and non human
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed Jan 29, 2024
1 parent 24dc578 commit ea3d2bc
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 38 deletions.
11 changes: 7 additions & 4 deletions src/cmap/auth/mongo_credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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}'.`
);
}

Expand Down
11 changes: 7 additions & 4 deletions src/cmap/auth/mongodb_oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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<OIDCToken>;
export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise<OIDCResponse>;

type ProviderName = 'aws' | 'azure' | 'callback';

Expand Down
3 changes: 1 addition & 2 deletions src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
Expand Down
65 changes: 41 additions & 24 deletions src/cmap/auth/mongodb_oidc/callback_workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ 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';

/** 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'];
Expand All @@ -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.
Expand Down Expand Up @@ -67,19 +75,11 @@ export class CallbackWorkflow implements Workflow {
credentials: MongoCredentials,
response?: Document
): Promise<Document> {
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,
Expand Down Expand Up @@ -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<IdPServerResponse> {
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)) {
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit ea3d2bc

Please sign in to comment.