forked from CSI-KJSCE/BOS-React-
181 lines
5.9 KiB
TypeScript
181 lines
5.9 KiB
TypeScript
import type { Document } from '../../bson';
|
|
import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../error';
|
|
import type { HandshakeDocument } from '../connect';
|
|
import type { Connection } from '../connection';
|
|
import { type AuthContext, AuthProvider } from './auth_provider';
|
|
import type { MongoCredentials } from './mongo_credentials';
|
|
import { AzureMachineWorkflow } from './mongodb_oidc/azure_machine_workflow';
|
|
import { GCPMachineWorkflow } from './mongodb_oidc/gcp_machine_workflow';
|
|
import { K8SMachineWorkflow } from './mongodb_oidc/k8s_machine_workflow';
|
|
import { TokenCache } from './mongodb_oidc/token_cache';
|
|
import { TokenMachineWorkflow } from './mongodb_oidc/token_machine_workflow';
|
|
|
|
/** Error when credentials are missing. */
|
|
const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.';
|
|
|
|
/**
|
|
* The information returned by the server on the IDP server.
|
|
* @public
|
|
*/
|
|
export interface IdPInfo {
|
|
/**
|
|
* A URL which describes the Authentication Server. This identifier should
|
|
* be the iss of provided access tokens, and be viable for RFC8414 metadata
|
|
* discovery and RFC9207 identification.
|
|
*/
|
|
issuer: string;
|
|
/** A unique client ID for this OIDC client. */
|
|
clientId: string;
|
|
/** A list of additional scopes to request from IdP. */
|
|
requestScopes?: string[];
|
|
}
|
|
|
|
/**
|
|
* The response from the IdP server with the access token and
|
|
* optional expiration time and refresh token.
|
|
* @public
|
|
*/
|
|
export interface IdPServerResponse {
|
|
/** The OIDC access token. */
|
|
accessToken: string;
|
|
/** The time when the access token expires. For future use. */
|
|
expiresInSeconds?: number;
|
|
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
refreshToken?: string;
|
|
}
|
|
|
|
/**
|
|
* The response required to be returned from the machine or
|
|
* human callback workflows' callback.
|
|
* @public
|
|
*/
|
|
export interface OIDCResponse {
|
|
/** The OIDC access token. */
|
|
accessToken: string;
|
|
/** The time when the access token expires. For future use. */
|
|
expiresInSeconds?: number;
|
|
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
refreshToken?: string;
|
|
}
|
|
|
|
/**
|
|
* The parameters that the driver provides to the user supplied
|
|
* human or machine callback.
|
|
*
|
|
* The version number is used to communicate callback API changes that are not breaking but that
|
|
* users may want to know about and review their implementation. Users may wish to check the version
|
|
* number and throw an error if their expected version number and the one provided do not match.
|
|
* @public
|
|
*/
|
|
export interface OIDCCallbackParams {
|
|
/** Optional username. */
|
|
username?: string;
|
|
/** The context in which to timeout the OIDC callback. */
|
|
timeoutContext: AbortSignal;
|
|
/** The current OIDC API version. */
|
|
version: 1;
|
|
/** The IdP information returned from the server. */
|
|
idpInfo?: IdPInfo;
|
|
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
refreshToken?: string;
|
|
}
|
|
|
|
/**
|
|
* The signature of the human or machine callback functions.
|
|
* @public
|
|
*/
|
|
export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise<OIDCResponse>;
|
|
|
|
/** The current version of OIDC implementation. */
|
|
export const OIDC_VERSION = 1;
|
|
|
|
type EnvironmentName = 'test' | 'azure' | 'gcp' | 'k8s' | undefined;
|
|
|
|
/** @internal */
|
|
export interface Workflow {
|
|
/**
|
|
* All device workflows must implement this method in order to get the access
|
|
* token and then call authenticate with it.
|
|
*/
|
|
execute(
|
|
connection: Connection,
|
|
credentials: MongoCredentials,
|
|
response?: Document
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* Each workflow should specify the correct custom behaviour for reauthentication.
|
|
*/
|
|
reauthenticate(connection: Connection, credentials: MongoCredentials): Promise<void>;
|
|
|
|
/**
|
|
* Get the document to add for speculative authentication.
|
|
*/
|
|
speculativeAuth(connection: Connection, credentials: MongoCredentials): Promise<Document>;
|
|
}
|
|
|
|
/** @internal */
|
|
export const OIDC_WORKFLOWS: Map<EnvironmentName, () => Workflow> = new Map();
|
|
OIDC_WORKFLOWS.set('test', () => new TokenMachineWorkflow(new TokenCache()));
|
|
OIDC_WORKFLOWS.set('azure', () => new AzureMachineWorkflow(new TokenCache()));
|
|
OIDC_WORKFLOWS.set('gcp', () => new GCPMachineWorkflow(new TokenCache()));
|
|
OIDC_WORKFLOWS.set('k8s', () => new K8SMachineWorkflow(new TokenCache()));
|
|
|
|
/**
|
|
* OIDC auth provider.
|
|
*/
|
|
export class MongoDBOIDC extends AuthProvider {
|
|
workflow: Workflow;
|
|
|
|
/**
|
|
* Instantiate the auth provider.
|
|
*/
|
|
constructor(workflow?: Workflow) {
|
|
super();
|
|
if (!workflow) {
|
|
throw new MongoInvalidArgumentError('No workflow provided to the OIDC auth provider.');
|
|
}
|
|
this.workflow = workflow;
|
|
}
|
|
|
|
/**
|
|
* Authenticate using OIDC
|
|
*/
|
|
override async auth(authContext: AuthContext): Promise<void> {
|
|
const { connection, reauthenticating, response } = authContext;
|
|
if (response?.speculativeAuthenticate?.done) {
|
|
return;
|
|
}
|
|
const credentials = getCredentials(authContext);
|
|
if (reauthenticating) {
|
|
await this.workflow.reauthenticate(connection, credentials);
|
|
} else {
|
|
await this.workflow.execute(connection, credentials, response);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the speculative auth for the initial handshake.
|
|
*/
|
|
override async prepare(
|
|
handshakeDoc: HandshakeDocument,
|
|
authContext: AuthContext
|
|
): Promise<HandshakeDocument> {
|
|
const { connection } = authContext;
|
|
const credentials = getCredentials(authContext);
|
|
const result = await this.workflow.speculativeAuth(connection, credentials);
|
|
return { ...handshakeDoc, ...result };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get credentials from the auth context, throwing if they do not exist.
|
|
*/
|
|
function getCredentials(authContext: AuthContext): MongoCredentials {
|
|
const { credentials } = authContext;
|
|
if (!credentials) {
|
|
throw new MongoMissingCredentialsError(MISSING_CREDENTIALS_ERROR);
|
|
}
|
|
return credentials;
|
|
}
|