Last active
September 19, 2025 17:53
-
-
Save gartnera/e190154e8eaf8fee79bb9bac0bba48c7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name AWS Console Session Reset Fixes | |
// @namespace https://agartner.com | |
// @version 1.1 | |
// @description Automatically refreshes the AWS console page when the 'Sign in again' modal appears. Ensures that the 'Sign in again' button correctly redirects to the the SSO portal. | |
// @author Alex Gartner | |
// @match *://*.console.aws.amazon.com/* | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @grant GM_registerMenuCommand | |
// @run-at document-end | |
// @noframes | |
// ==/UserScript== | |
// Configuration - can be customized through Tampermonkey menu | |
const DEFAULT_SSO_LOGIN_URL = "https://agtonomy.awsapps.com/start/#/console"; | |
const ssoLoginUrlBase = GM_getValue("ssoLoginUrl", DEFAULT_SSO_LOGIN_URL); | |
// Register menu command to configure SSO URL | |
GM_registerMenuCommand("Configure SSO URL", function () { | |
const newUrl = prompt("Enter SSO Login URL:", ssoLoginUrlBase); | |
if (newUrl !== null && newUrl.trim() !== "") { | |
GM_setValue("ssoLoginUrl", newUrl.trim()); | |
alert( | |
"SSO URL updated! Please refresh the page for changes to take effect.", | |
); | |
} | |
}); | |
/** | |
* Strips the AWS multisession prefix from a console URL. | |
* Tolerates URLs without a prefix, non-AWS URLs, and invalid inputs. | |
* | |
* @param {string} urlString The URL to process. | |
* @returns {string} The URL with the prefix removed, or the original URL if no prefix was found. | |
*/ | |
function stripAwsMultisessionPrefix(urlString) { | |
const CORE_DOMAIN = "console.aws.amazon.com"; | |
let url; | |
try { | |
// Use the URL API for robust parsing. | |
url = new URL(urlString); | |
} catch (error) { | |
// If the URL is invalid, return the original string. | |
return urlString; | |
} | |
// Only proceed if it's a valid AWS console domain. | |
if (!url.hostname.endsWith(CORE_DOMAIN)) { | |
return urlString; | |
} | |
const hostnameParts = url.hostname.split("."); | |
const coreDomainParts = CORE_DOMAIN.split("."); // ['console', 'aws', 'amazon', 'com'] | |
// Isolate the subdomains before 'console.aws.amazon.com' | |
const subdomains = hostnameParts.slice( | |
0, | |
hostnameParts.length - coreDomainParts.length, | |
); | |
// The pattern for a multisession prefix is: <prefix>.<region> | |
// So, we're looking for exactly two subdomains. | |
if (subdomains.length === 2) { | |
const potentialRegion = subdomains[1]; | |
// A simple regex to check if the second part looks like a region (e.g., 'us-west-2'). | |
const isRegion = /^[a-z]{2}-[a-z]+-\d$/.test(potentialRegion); | |
if (isRegion) { | |
// It matches the multisession pattern. Rebuild the hostname without the prefix. | |
const newHostname = [potentialRegion, ...coreDomainParts].join("."); | |
url.hostname = newHostname; | |
return url.toString(); | |
} | |
} | |
// If the pattern doesn't match, return the original URL. | |
return urlString; | |
} | |
// initiateSSOLogin will try to initiate sso login redirecting back to the current page with the same role | |
function initiateSSOLogin() { | |
const infoTile = document.querySelector( | |
"[data-testid='awsc-account-info-tile']", | |
); | |
const infoText = infoTile.innerText; | |
// 2. Define regular expressions to find the patterns | |
const accountNumberRegex = /\((\d{4}-\d{4}-\d{4})\)/; | |
const roleRegex = /\n(.*?)\//; | |
const accountId = infoText | |
.match(accountNumberRegex)[1] | |
.trim() | |
.replaceAll("-", ""); | |
const role = infoText.match(roleRegex)[1]; | |
const ssoLoginUrl = new URL(ssoLoginUrlBase); | |
ssoLoginUrl.searchParams.set("account_id", accountId); | |
ssoLoginUrl.searchParams.set("role_name", role); | |
ssoLoginUrl.searchParams.set( | |
"destination", | |
stripAwsMultisessionPrefix(document.location), | |
); | |
document.location = ssoLoginUrl.toString(); | |
} | |
// aws-consoleInfo is for single session | |
// aws-session-id is for multi-session support | |
function hasUnexpiredCookie() { | |
return document.cookie | |
.split(";") | |
.map((a) => a.split("=")[0]) | |
.find((a) => a.includes("aws-consoleInfo") || a.includes("aws-session-id")); | |
} | |
(function () { | |
"use strict"; | |
const targetNodeId = "awsc-nav-signin-again-modal-root"; | |
// Callback function to execute when mutations are observed | |
const callback = (mutationsList, observer) => { | |
// We only care if the modal now exists on the page | |
if (!document.getElementById(targetNodeId)) { | |
return; | |
} | |
console.log(`💡 Session modal (${targetNodeId}) detected`); | |
// Disconnect the observer to prevent it from firing again during reload | |
observer.disconnect(); | |
if (hasUnexpiredCookie()) { | |
// wait a bit for all the cookies to be reset | |
setTimeout(() => { | |
console.log( | |
"token is unexpired and seems to be valid after wait. reloading page.", | |
); | |
window.location.reload(); | |
}, 500); | |
return; | |
} | |
// replace the sign in button with one that correctly redirects to the sso page | |
const submitButtonOriginal = document.querySelector( | |
"#awsc-nav-signin-again-modal-root [type='submit']", | |
); | |
const submitButtonClone = submitButtonOriginal.cloneNode(true); | |
submitButtonOriginal.parentNode.replaceChild( | |
submitButtonClone, | |
submitButtonOriginal, | |
); | |
submitButtonClone.onclick = initiateSSOLogin; | |
// if you login on another tab and the cookie becomes valid again, we should refresh the page | |
let expiryInterval = null; | |
expiryInterval = setInterval(() => { | |
if (!hasUnexpiredCookie()) { | |
return; | |
} | |
clearInterval(expiryInterval); | |
// wait a bit for all the cookies to be reset | |
setTimeout(() => { | |
console.log( | |
"token is unexpired and seems to be valid after wait. reloading page.", | |
); | |
window.location.reload(); | |
}, 500); | |
return; | |
}, 500); | |
}; | |
// only run on the top level console page | |
if (!document.getElementById("consoleNavHeader")) { | |
return; | |
} | |
// Log to the console to confirm the script is running | |
console.log("🚀 AWS Auto-Refresh script active, watching for session modal."); | |
// Create a new observer instance linked to the callback function | |
const observer = new MutationObserver(callback); | |
// Configuration for the observer: | |
// We want to know if child elements are added or removed from the entire document | |
const config = { | |
childList: true, | |
subtree: true, | |
}; | |
// Start observing the document body for configured mutations | |
observer.observe(document.body, config); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment