Skip to content

Instantly share code, notes, and snippets.

@gartnera
Last active September 19, 2025 17:53
Show Gist options
  • Save gartnera/e190154e8eaf8fee79bb9bac0bba48c7 to your computer and use it in GitHub Desktop.
Save gartnera/e190154e8eaf8fee79bb9bac0bba48c7 to your computer and use it in GitHub Desktop.
// ==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