Sindbad~EG File Manager
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallbackWorkflow = void 0;
const bson_1 = require("bson");
const error_1 = require("../../../error");
const utils_1 = require("../../../utils");
const providers_1 = require("../providers");
const callback_lock_cache_1 = require("./callback_lock_cache");
const token_entry_cache_1 = require("./token_entry_cache");
/** The current version of OIDC implementation. */
const OIDC_VERSION = 0;
/** 5 minutes in seconds */
const TIMEOUT_S = 300;
/** Properties allowed on results of callbacks. */
const RESULT_PROPERTIES = ['accessToken', 'expiresInSeconds', 'refreshToken'];
/** Error message when the callback result is invalid. */
const CALLBACK_RESULT_ERROR = 'User provided OIDC callbacks must return a valid object with an accessToken.';
/**
* OIDC implementation of a callback based workflow.
* @internal
*/
class CallbackWorkflow {
/**
* Instantiate the workflow
*/
constructor() {
this.cache = new token_entry_cache_1.TokenEntryCache();
this.callbackCache = new callback_lock_cache_1.CallbackLockCache();
}
/**
* Get the document to add for speculative authentication. This also needs
* to add a db field from the credentials source.
*/
async speculativeAuth(credentials) {
const document = startCommandDocument(credentials);
document.db = credentials.source;
return { speculativeAuthenticate: document };
}
/**
* Execute the OIDC callback workflow.
*/
async execute(connection, credentials, reauthenticating, response) {
// Get the callbacks with locks from the callback lock cache.
const { requestCallback, refreshCallback, callbackHash } = this.callbackCache.getEntry(connection, credentials);
// Look for an existing entry in the cache.
const entry = this.cache.getEntry(connection.address, credentials.username, callbackHash);
let result;
if (entry) {
// Reauthentication cannot use a token from the cache since the server has
// stated it is invalid by the request for reauthentication.
if (entry.isValid() && !reauthenticating) {
// Presence of a valid cache entry means we can skip to the finishing step.
result = await this.finishAuthentication(connection, credentials, entry.tokenResult, response?.speculativeAuthenticate?.conversationId);
}
else {
// Presence of an expired cache entry means we must fetch a new one and
// then execute the final step.
const tokenResult = await this.fetchAccessToken(connection, credentials, entry.serverInfo, reauthenticating, callbackHash, requestCallback, refreshCallback);
try {
result = await this.finishAuthentication(connection, credentials, tokenResult, reauthenticating ? undefined : response?.speculativeAuthenticate?.conversationId);
}
catch (error) {
// If we are reauthenticating and this errors with reauthentication
// required, we need to do the entire process over again and clear
// the cache entry.
if (reauthenticating &&
error instanceof error_1.MongoError &&
error.code === error_1.MONGODB_ERROR_CODES.Reauthenticate) {
this.cache.deleteEntry(connection.address, credentials.username, callbackHash);
result = await this.execute(connection, credentials, reauthenticating);
}
else {
throw error;
}
}
}
}
else {
// No entry in the cache requires us to do all authentication steps
// from start to finish, including getting a fresh token for the cache.
const startDocument = await this.startAuthentication(connection, credentials, reauthenticating, response);
const conversationId = startDocument.conversationId;
const serverResult = bson_1.BSON.deserialize(startDocument.payload.buffer);
const tokenResult = await this.fetchAccessToken(connection, credentials, serverResult, reauthenticating, callbackHash, requestCallback, refreshCallback);
result = await this.finishAuthentication(connection, credentials, tokenResult, conversationId);
}
return result;
}
/**
* Starts the callback authentication process. If there is a speculative
* authentication document from the initial handshake, then we will use that
* value to get the issuer, otherwise we will send the saslStart command.
*/
async startAuthentication(connection, credentials, reauthenticating, response) {
let result;
if (!reauthenticating && response?.speculativeAuthenticate) {
result = response.speculativeAuthenticate;
}
else {
result = await connection.commandAsync((0, utils_1.ns)(credentials.source), startCommandDocument(credentials), undefined);
}
return result;
}
/**
* Finishes the callback authentication process.
*/
async finishAuthentication(connection, credentials, tokenResult, conversationId) {
const result = await connection.commandAsync((0, utils_1.ns)(credentials.source), finishCommandDocument(tokenResult.accessToken, conversationId), undefined);
return result;
}
/**
* Fetches an access token using either the request or refresh callbacks and
* puts it in the cache.
*/
async fetchAccessToken(connection, credentials, serverInfo, reauthenticating, callbackHash, requestCallback, refreshCallback) {
// Get the token from the cache.
const entry = this.cache.getEntry(connection.address, credentials.username, callbackHash);
let result;
const context = { timeoutSeconds: TIMEOUT_S, version: OIDC_VERSION };
// Check if there's a token in the cache.
if (entry) {
// If the cache entry is valid, return the token result.
if (entry.isValid() && !reauthenticating) {
return entry.tokenResult;
}
// If the cache entry is not valid, remove it from the cache and first attempt
// to use the refresh callback to get a new token. If no refresh callback
// exists, then fallback to the request callback.
if (refreshCallback) {
context.refreshToken = entry.tokenResult.refreshToken;
result = await refreshCallback(serverInfo, context);
}
else {
result = await requestCallback(serverInfo, context);
}
}
else {
// With no token in the cache we use the request callback.
result = await requestCallback(serverInfo, context);
}
// 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)) {
this.cache.deleteEntry(connection.address, credentials.username, callbackHash);
throw new error_1.MongoMissingCredentialsError(CALLBACK_RESULT_ERROR);
}
// Cleanup the cache.
this.cache.deleteExpiredEntries();
// Put the new entry into the cache.
this.cache.addEntry(connection.address, credentials.username || '', callbackHash, result, serverInfo);
return result;
}
}
exports.CallbackWorkflow = CallbackWorkflow;
/**
* Generate the finishing command document for authentication. Will be a
* saslStart or saslContinue depending on the presence of a conversation id.
*/
function finishCommandDocument(token, conversationId) {
if (conversationId != null && typeof conversationId === 'number') {
return {
saslContinue: 1,
conversationId: conversationId,
payload: new bson_1.Binary(bson_1.BSON.serialize({ jwt: token }))
};
}
// saslContinue requires a conversationId in the command to be valid so in this
// case the server allows "step two" to actually be a saslStart with the token
// as the jwt since the use of the cached value has no correlating conversating
// on the particular connection.
return {
saslStart: 1,
mechanism: providers_1.AuthMechanism.MONGODB_OIDC,
payload: new bson_1.Binary(bson_1.BSON.serialize({ jwt: token }))
};
}
/**
* Determines if a result returned from a request or refresh callback
* function is invalid. This means the result is nullish, doesn't contain
* the accessToken required field, and does not contain extra fields.
*/
function isCallbackResultInvalid(tokenResult) {
if (tokenResult == null || typeof tokenResult !== 'object')
return true;
if (!('accessToken' in tokenResult))
return true;
return !Object.getOwnPropertyNames(tokenResult).every(prop => RESULT_PROPERTIES.includes(prop));
}
/**
* Generate the saslStart command document.
*/
function startCommandDocument(credentials) {
const payload = {};
if (credentials.username) {
payload.n = credentials.username;
}
return {
saslStart: 1,
autoAuthorize: 1,
mechanism: providers_1.AuthMechanism.MONGODB_OIDC,
payload: new bson_1.Binary(bson_1.BSON.serialize(payload))
};
}
//# sourceMappingURL=callback_workflow.js.map
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists