Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 400x 400x 400x 400x 400x 400x | import { BSON } from '../../../bson'; import { MONGODB_ERROR_CODES, MongoError, MongoOIDCError } from '../../../error'; import { Timeout, TimeoutError } from '../../../timeout'; import { type Connection } from '../../connection'; import { type MongoCredentials } from '../mongo_credentials'; import { type IdPInfo, OIDC_VERSION, type OIDCCallbackFunction, type OIDCCallbackParams, type OIDCResponse } from '../mongodb_oidc'; import { CallbackWorkflow, HUMAN_TIMEOUT_MS } from './callback_workflow'; import { type TokenCache } from './token_cache'; /** * Class implementing behaviour for the non human callback workflow. * @internal */ export class HumanCallbackWorkflow extends CallbackWorkflow { /** * Instantiate the human callback workflow. */ constructor(cache: TokenCache, callback: OIDCCallbackFunction) { super(cache, callback); } /** * Execute the OIDC human callback workflow. */ async execute(connection: Connection, credentials: MongoCredentials): Promise<void> { // Check if the Client Cache has an access token. // If it does, cache the access token in the Connection Cache and perform a One-Step SASL conversation // using the access token. If the server returns an Authentication error (18), // invalidate the access token token from the Client Cache, clear the Connection Cache, // and restart the authentication flow. Raise any other errors to the user. On success, exit the algorithm. if (this.cache.hasAccessToken) { const token = this.cache.getAccessToken(); connection.accessToken = token; try { return await this.finishAuthentication(connection, credentials, token); } catch (error) { if ( error instanceof MongoError && error.code === MONGODB_ERROR_CODES.AuthenticationFailed ) { this.cache.removeAccessToken(); delete connection.accessToken; return await this.execute(connection, credentials); } else { throw error; } } } // Check if the Client Cache has a refresh token. // If it does, call the OIDC Human Callback with the cached refresh token and IdpInfo to get a // new access token. Cache the new access token in the Client Cache and Connection Cache. // Perform a One-Step SASL conversation using the new access token. If the the server returns // an Authentication error (18), clear the refresh token, invalidate the access token from the // Client Cache, clear the Connection Cache, and restart the authentication flow. Raise any other // errors to the user. On success, exit the algorithm. if (this.cache.hasRefreshToken) { const refreshToken = this.cache.getRefreshToken(); const result = await this.fetchAccessToken( this.cache.getIdpInfo(), credentials, refreshToken ); this.cache.put(result); connection.accessToken = result.accessToken; try { return await this.finishAuthentication(connection, credentials, result.accessToken); } catch (error) { if ( error instanceof MongoError && error.code === MONGODB_ERROR_CODES.AuthenticationFailed ) { this.cache.removeRefreshToken(); delete connection.accessToken; return await this.execute(connection, credentials); } else { throw error; } } } // Start a new Two-Step SASL conversation. // Run a PrincipalStepRequest to get the IdpInfo. // Call the OIDC Human Callback with the new IdpInfo to get a new access token and optional refresh // token. Drivers MUST NOT pass a cached refresh token to the callback when performing // a new Two-Step conversation. Cache the new IdpInfo and refresh token in the Client Cache and the // new access token in the Client Cache and Connection Cache. // Attempt to authenticate using a JwtStepRequest with the new access token. Raise any errors to the user. const startResponse = await this.startAuthentication(connection, credentials); const conversationId = startResponse.conversationId; const idpInfo = BSON.deserialize(startResponse.payload.buffer) as IdPInfo; const callbackResponse = await this.fetchAccessToken(idpInfo, credentials); this.cache.put(callbackResponse, idpInfo); connection.accessToken = callbackResponse.accessToken; return await this.finishAuthentication( connection, credentials, callbackResponse.accessToken, conversationId ); } /** * Fetches an access token using the callback. */ private async fetchAccessToken( idpInfo: IdPInfo, credentials: MongoCredentials, refreshToken?: string ): Promise<OIDCResponse> { const controller = new AbortController(); const params: OIDCCallbackParams = { timeoutContext: controller.signal, version: OIDC_VERSION, idpInfo: idpInfo }; if (credentials.username) { params.username = credentials.username; } if (refreshToken) { params.refreshToken = refreshToken; } const timeout = Timeout.expires(HUMAN_TIMEOUT_MS); try { return await Promise.race([this.executeAndValidateCallback(params), timeout]); } catch (error) { if (TimeoutError.is(error)) { controller.abort(); throw new MongoOIDCError(`OIDC callback timed out after ${HUMAN_TIMEOUT_MS}ms.`); } throw error; } finally { timeout.clear(); } } } |