<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: Used MFA with browser clickpath in Synthetic Monitoring</title>
    <link>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/266961#M2935</link>
    <description>&lt;P&gt;Hi,&lt;BR /&gt;If you use a TOTP token for authentication, the JavaScript step is currently the only way to achieve it. We're working on better support for it.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;1. I do not know why your code does not work, but try this one:&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;async function getOTP(secretKeyArrayBuffer) {
// Define the time step and T0. T0 is commonly 0 in Unix epoch.
const epoch = Math.floor(Date.now() / 1000);
const timeStep = 30;
const counter = Math.floor(epoch / timeStep);

// Convert the counter to an ArrayBuffer (8 bytes, big-endian)
const counterBuffer = new ArrayBuffer(8);
const counterView = new DataView(counterBuffer);
counterView.setUint32(4, counter); // Set the last 4 bytes as the counter

// Import the secret key for HMAC-SHA-1
const key = await window.crypto.subtle.importKey(
'raw', // raw format of the key
secretKeyArrayBuffer, // the key
{
name: 'HMAC',
hash: {
name: 'SHA-1'
}
}, // algorithm details
false, // not extractable
['sign'] // only need to sign
);

// Generate HMAC-SHA-1 of the counter using the secret key
const hmacBuffer = await window.crypto.subtle.sign('HMAC', key, counterBuffer);

// Convert HMAC result into a Uint8Array for easy byte manipulation
const hmac = new Uint8Array(hmacBuffer);

// Dynamic Truncation to get a 4-byte string and then a 31-bit number
const offset = hmac[hmac.length - 1] &amp;amp; 0xf;
const binary = ((hmac[offset] &amp;amp; 0x7f) &amp;lt;&amp;lt; 24) | (hmac[offset + 1] &amp;lt;&amp;lt; 16) | (hmac[offset + 2] &amp;lt;&amp;lt; &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; | (hmac[offset + 3]);

// Modulo 1,000,000 to get the final OTP
const otp = binary % 1000000;

// Ensure the OTP is a 6-digit number
return otp.toString().padStart(6, '0');
}

function convertBase32ToArrayBuffer(base32) {
const base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let bits = "";
let paddingCount = 0;

base32 = base32.toUpperCase();

for (let i = 0; i &amp;lt; base32.length; i++) {
const char = base32.charAt(i);
if (char === "=") { // Padding character, stop processing
break;
}
const charIndex = base32Alphabet.indexOf(char);
if (charIndex === -1) {
throw new Error("Invalid Base32 character encountered.");
}
bits += charIndex.toString(2).padStart(5, '0');
}

// Convert bits to bytes
const bytes = [];
for (let i = 0; i &amp;lt; bits.length; i += &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; {
const byte = bits.substring(i, i + 8);
// Incomplete byte (less than 8 bits) ignored
if (byte.length === &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; {
bytes.push(parseInt(byte, 2));
}
}

// Convert byte array to ArrayBuffer
const arrayBuffer = new ArrayBuffer(bytes.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i &amp;lt; bytes.length; i++) {
uint8Array[i] = bytes[i];
}

return arrayBuffer;
}

var totpSecret = api.getCredential("CREDENTIALS_VAULT-XXXXXXXXXXX", "token");
api.startAsyncSyntheticEvent();
getOTP(convertBase32ToArrayBuffer(totpSecret)).then(otp =&amp;gt; {
api.setValue('totp_token', otp);
api.finish();
});&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;2. In the action where you put the code in yout input text, don't forget to pass the variable:&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Cezary_Tomaszew_0-1736411435819.png" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/25622i70A059CD75B7B013/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Cezary_Tomaszew_0-1736411435819.png" alt="Cezary_Tomaszew_0-1736411435819.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let me know if it helps you.&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Best regards,&lt;/P&gt;
&lt;P&gt;Cezary&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Thu, 09 Jan 2025 08:34:58 GMT</pubDate>
    <dc:creator>Cezary_Tomaszew</dc:creator>
    <dc:date>2025-01-09T08:34:58Z</dc:date>
    <item>
      <title>Used MFA with browser clickpath</title>
      <link>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/262808#M2765</link>
      <description>&lt;P&gt;&lt;SPAN&gt;Hello&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;we need to use MFA in our browser clickpath&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;here is the topic:&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;The application uses authentication via OKTA portal&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;Google authtificator is used as a software based authenticator&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;we need a script that is used by the scenario to generate a TOTP code that can be placed in the okta page to validate the authentication of Google athtifcator to authenticate with the application.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;I managed to get this code to work on a browser but it doesn't work on Dynatrace&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;const crypto = (globalThis.crypto || require("crypto").webcrypto).subtle;&lt;/P&gt;
&lt;P&gt;const base32 = {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 50: 26,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 51: 27,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 52: 28,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 53: 29,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 54: 30,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 55: 31,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 65: 0,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 66: 1,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 67: 2,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 68: 3,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 69: 4,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 70: 5,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 71: 6,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 72: 7,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 73: 8,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 74: 9,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 75: 10,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 76: 11,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 77: 12,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 78: 13,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 79: 14,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 80: 15,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 81: 16,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 82: 17,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 83: 18,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 84: 19,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 85: 20,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 86: 21,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 87: 22,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 88: 23,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 89: 24,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; 90: 25,&lt;BR /&gt;};&lt;/P&gt;
&lt;P&gt;class TOTP {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Generates a Time-based One-Time Password (TOTP).&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @async&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} key - The secret key for TOTP.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {Options} options - Optional parameters for TOTP.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {Promise&amp;lt;{otp: string, expires: number}&amp;gt;} A promise that resolves to an object containing the OTP and its expiry time.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static async generate(key, options) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const _options = {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; digits: 6,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; algorithm: "SHA-1",&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; encoding: "hex",&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; period: 30,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; timestamp: Date.now(),&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ...options,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; };&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const epochSeconds = Math.floor(_options.timestamp / 1000);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const timeHex = this.dec2hex(Math.floor(epochSeconds / _options.period)).padStart(16, "0");&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const keyBuffer = _options.encoding === "hex" ? this.base32ToBuffer(key) : this.asciiToBuffer(key);&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const hmacKey = await crypto.importKey(&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "raw",&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; keyBuffer, {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; name: "HMAC",&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; hash: {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; name: _options.algorithm&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; },&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; false, ["sign"]&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; );&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const signature = await crypto.sign("HMAC", hmacKey, this.hex2buf(timeHex));&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const signatureHex = this.buf2hex(signature);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const offset = this.hex2dec(signatureHex.slice(-1)) * 2;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const masked = this.hex2dec(signatureHex.slice(offset, offset + 8)) &amp;amp; 0x7fffffff;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const otp = masked.toString().slice(-_options.digits);&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const period = _options.period * 1000;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const expires = Math.ceil((_options.timestamp + 1) / period) * period;&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; otp,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; expires&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; };&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts a hexadecimal string to a decimal number.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} hex - The hex string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {number} The decimal representation.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static hex2dec(hex) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return parseInt(hex, 16);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts a decimal number to a hexadecimal string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {number} dec - The decimal number.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {string} The hex representation.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static dec2hex(dec) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return (dec &amp;lt; 15.5 ? "0" : "") + Math.round(dec).toString(16);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts a base32 encoded string to an ArrayBuffer.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} str - The base32 encoded string to convert.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {ArrayBuffer} The ArrayBuffer representation of the base32 encoded string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static base32ToBuffer(str) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; str = str.toUpperCase();&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; let length = str.length;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; while (str.charCodeAt(length - 1) === 61) length--; // Remove pads&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const bufferSize = (length * 5) / 8; // Estimate buffer size&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const buffer = new Uint8Array(bufferSize);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; let value = 0,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bits = 0,&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; index = 0;&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; for (let i = 0; i &amp;lt; length; i++) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const charCode = base32[str.charCodeAt(i)];&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (charCode === undefined) throw new Error("Invalid base32 character in key");&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; value = (value &amp;lt;&amp;lt; 5) | charCode;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bits += 5;&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (bits &amp;gt;= &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; buffer[index++] = value &amp;gt;&amp;gt;&amp;gt; (bits -= 8);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return buffer.buffer;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts an ASCII string to an ArrayBuffer.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} str - The ASCII string to convert.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {ArrayBuffer} The ArrayBuffer representation of the ASCII string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static asciiToBuffer(str) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const buffer = new Uint8Array(str.length);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; for (let i = 0; i &amp;lt; str.length; i++) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; buffer[i] = str.charCodeAt(i);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return buffer.buffer;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts a hexadecimal string to an ArrayBuffer.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} hex - The hexadecimal string to convert.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {ArrayBuffer} The ArrayBuffer representation of the hexadecimal string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static hex2buf(hex) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const buffer = new Uint8Array(hex.length / 2);&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; for (let i = 0, j = 0; i &amp;lt; hex.length; i += 2, j++) buffer[j] = this.hex2dec(hex.slice(i, i + 2));&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return buffer.buffer;&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Converts an ArrayBuffer to a hexadecimal string.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {ArrayBuffer} buffer - The ArrayBuffer to convert.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {string} The hexadecimal string representation of the buffer.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;&amp;nbsp; &amp;nbsp; static buf2hex(buffer) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return [...new Uint8Array(buffer)].map((x) =&amp;gt; x.toString(16).padStart(2, "0")).join("");&lt;BR /&gt;&amp;nbsp; &amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; /**&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* The cryptographic interface used for HMAC operations.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* Chooses the Web Crypto API if available, otherwise falls back to Node's crypto module.&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;* @type {SubtleCrypto}&lt;BR /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;BR /&gt;}&lt;/P&gt;
&lt;P&gt;const TOTP_KEY = "YOUR_SECRET_KEY_GOOGLE_AUTHENTIFICATOR";&lt;BR /&gt;const result = TOTP.generate(TOTP_KEY).then(function(result) {&lt;BR /&gt;&amp;nbsp; &amp;nbsp; api.info(result);&lt;BR /&gt;&amp;nbsp; &amp;nbsp; api.setValue("CODE_TOTP", result);&lt;BR /&gt;});&lt;BR /&gt;&lt;BR /&gt;has anyone managed to get MFA working with Google Authenticator in Browser clipart ?&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;</description>
      <pubDate>Tue, 19 Nov 2024 07:25:25 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/262808#M2765</guid>
      <dc:creator>Akli</dc:creator>
      <dc:date>2024-11-19T07:25:25Z</dc:date>
    </item>
    <item>
      <title>Re: Used MFA with browser clickpath</title>
      <link>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/266406#M2922</link>
      <description>&lt;P&gt;Once a vendor achieves this, they will strike gold. I don't know of any Synthetic vendor that offers MFA, 2FA etc....&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 31 Dec 2024 19:46:31 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/266406#M2922</guid>
      <dc:creator>ChadTurner</dc:creator>
      <dc:date>2024-12-31T19:46:31Z</dc:date>
    </item>
    <item>
      <title>Re: Used MFA with browser clickpath</title>
      <link>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/266961#M2935</link>
      <description>&lt;P&gt;Hi,&lt;BR /&gt;If you use a TOTP token for authentication, the JavaScript step is currently the only way to achieve it. We're working on better support for it.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;1. I do not know why your code does not work, but try this one:&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;async function getOTP(secretKeyArrayBuffer) {
// Define the time step and T0. T0 is commonly 0 in Unix epoch.
const epoch = Math.floor(Date.now() / 1000);
const timeStep = 30;
const counter = Math.floor(epoch / timeStep);

// Convert the counter to an ArrayBuffer (8 bytes, big-endian)
const counterBuffer = new ArrayBuffer(8);
const counterView = new DataView(counterBuffer);
counterView.setUint32(4, counter); // Set the last 4 bytes as the counter

// Import the secret key for HMAC-SHA-1
const key = await window.crypto.subtle.importKey(
'raw', // raw format of the key
secretKeyArrayBuffer, // the key
{
name: 'HMAC',
hash: {
name: 'SHA-1'
}
}, // algorithm details
false, // not extractable
['sign'] // only need to sign
);

// Generate HMAC-SHA-1 of the counter using the secret key
const hmacBuffer = await window.crypto.subtle.sign('HMAC', key, counterBuffer);

// Convert HMAC result into a Uint8Array for easy byte manipulation
const hmac = new Uint8Array(hmacBuffer);

// Dynamic Truncation to get a 4-byte string and then a 31-bit number
const offset = hmac[hmac.length - 1] &amp;amp; 0xf;
const binary = ((hmac[offset] &amp;amp; 0x7f) &amp;lt;&amp;lt; 24) | (hmac[offset + 1] &amp;lt;&amp;lt; 16) | (hmac[offset + 2] &amp;lt;&amp;lt; &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; | (hmac[offset + 3]);

// Modulo 1,000,000 to get the final OTP
const otp = binary % 1000000;

// Ensure the OTP is a 6-digit number
return otp.toString().padStart(6, '0');
}

function convertBase32ToArrayBuffer(base32) {
const base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let bits = "";
let paddingCount = 0;

base32 = base32.toUpperCase();

for (let i = 0; i &amp;lt; base32.length; i++) {
const char = base32.charAt(i);
if (char === "=") { // Padding character, stop processing
break;
}
const charIndex = base32Alphabet.indexOf(char);
if (charIndex === -1) {
throw new Error("Invalid Base32 character encountered.");
}
bits += charIndex.toString(2).padStart(5, '0');
}

// Convert bits to bytes
const bytes = [];
for (let i = 0; i &amp;lt; bits.length; i += &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; {
const byte = bits.substring(i, i + 8);
// Incomplete byte (less than 8 bits) ignored
if (byte.length === &lt;span class="lia-unicode-emoji" title=":smiling_face_with_sunglasses:"&gt;😎&lt;/span&gt; {
bytes.push(parseInt(byte, 2));
}
}

// Convert byte array to ArrayBuffer
const arrayBuffer = new ArrayBuffer(bytes.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i &amp;lt; bytes.length; i++) {
uint8Array[i] = bytes[i];
}

return arrayBuffer;
}

var totpSecret = api.getCredential("CREDENTIALS_VAULT-XXXXXXXXXXX", "token");
api.startAsyncSyntheticEvent();
getOTP(convertBase32ToArrayBuffer(totpSecret)).then(otp =&amp;gt; {
api.setValue('totp_token', otp);
api.finish();
});&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;2. In the action where you put the code in yout input text, don't forget to pass the variable:&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Cezary_Tomaszew_0-1736411435819.png" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/25622i70A059CD75B7B013/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Cezary_Tomaszew_0-1736411435819.png" alt="Cezary_Tomaszew_0-1736411435819.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let me know if it helps you.&amp;nbsp;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Best regards,&lt;/P&gt;
&lt;P&gt;Cezary&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 09 Jan 2025 08:34:58 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Synthetic-Monitoring/Used-MFA-with-browser-clickpath/m-p/266961#M2935</guid>
      <dc:creator>Cezary_Tomaszew</dc:creator>
      <dc:date>2025-01-09T08:34:58Z</dc:date>
    </item>
  </channel>
</rss>

