Skip to content

Instantly share code, notes, and snippets.

@PrintNow
Last active December 10, 2025 02:42
Show Gist options
  • Select an option

  • Save PrintNow/03829be994c2866de0ed7963b94e41d2 to your computer and use it in GitHub Desktop.

Select an option

Save PrintNow/03829be994c2866de0ed7963b94e41d2 to your computer and use it in GitHub Desktop.
AWS 自动登录脚本 (包括自动填充 OTOP)
// ==UserScript==
// @name AWS 自动登录脚本
// @namespace https://nowtime.cc
// @version v1.4.0
// @description 自动登录 AWS 控制台, 2025-12-10 更新
// @author Shine
// @match https://signin.aws.amazon.com/oauth?*
// @match https://*.signin.aws.amazon.com/oauth?*
// @icon https://www.google.com/s2/favicons?sz=64&domain=amazon.com
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @run-at document-idle
// ==/UserScript==
(async function () {
'use strict';
const context = {
otopConfig: {},
username: undefined,
password: undefined,
}
// 初始化数据
window.addEventListener('load', await initData(() => {
setTimeout(() => {
console.log('开始执行自动登录 AWS')
handleToLogin()
}, 800)
}));
async function initData(then) {
let otopURL = await GM.getValue('otopURL'),
username = await GM.getValue('username'),
password = await GM.getValue('password');
if (!otopURL) {
otopURL = prompt("请输入 OTPAuth URL")
if (otopURL === '') {
throw new Error(`请输入 OTPAuth URL`)
}
GM.setValue('otopURL', otopURL);
console.log('otopURL 设置成功')
}
if (!username) {
username = prompt("请输入 AWS 用户名")
if (username === '') {
throw new Error(`请输入用户名`)
}
GM.setValue('username', username);
console.log('AWS 用户名设置成功')
}
if (!password) {
password = prompt("请输入 AWS 密码")
if (password === '') {
throw new Error(`请输入密码`)
}
GM.setValue('password', password);
console.log('AWS 密码设置成功')
}
context.username = username;
context.password = password;
context.otopConfig = parseOtpauthURL(otopURL);
then && then()
}
/**
*
* @param attempt 重试次数
* @param action 0:输入账号密码页面,1:输入 OTP 页面
*/
async function handleToLogin(attempt = 0, action = 0) {
// 如果实在输入账号密码页面
if (action === 0) {
const $signin_button = document.getElementById(`signin_button`)
if (!$signin_button) {
if (attempt > 10) {
const msg = `[ACTION:${action}] 已尝试 ${attempt} 次 (per 200ms),未找到 #signin_button 元素`
alert(msg)
throw new Error(msg)
}
setTimeout(() => {
handleToLogin(attempt + 1)
}, 200)
return;
}
setInputValue(document.querySelector(`input[name="username"]`), context.username)
setInputValue(document.querySelector(`input[name="password"]`), context.password)
// 模拟点击按钮
$signin_button.click();
setTimeout(() => {
handleToLogin(0, 1)
}, 200)
} else if (action === 1) {
// 输入 OTP 页面
if (attempt > 10) {
const msg = `[ACTION:${action}] 已尝试 ${attempt} 次 (per 200ms)`
alert(msg)
throw new Error(msg)
}
const $otp_input = document.querySelector(`input[name="mfaCode"]`)
if (!$otp_input) {
setTimeout(() => {
handleToLogin(attempt + 1, 1)
}, 200)
return;
}
const otp = await generateTOTP(context.otopConfig.secret, {
step: context.otopConfig.period,
digits: context.otopConfig.digits,
algorithm: context.otopConfig?.algorithm ?? 'SHA-1'
});
setInputValue($otp_input, otp, ["input", "change"]);
// 模拟点击
document.querySelector(`button[data-testid="mfa-submit-button"]`).click()
}
}
function setInputValue(input, value, trigger = ["input"]) {
const setter = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(input),
"value"
)?.set;
if (setter) {
setter.call(input, value); // 最通用
} else {
input.value = value;
}
trigger.forEach(eventName => {
input.dispatchEvent(new Event(eventName, {bubbles: true}));
});
}
function base32Decode(input) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const clean = input.replace(/=+$/, "").toUpperCase();
let bits = "";
for (let char of clean) {
const val = alphabet.indexOf(char);
if (val === -1) continue;
bits += val.toString(2).padStart(5, "0");
}
const bytes = [];
for (let i = 0; i + 8 <= bits.length; i += 8) {
bytes.push(parseInt(bits.slice(i, i + 8), 2));
}
return new Uint8Array(bytes);
}
async function generateTOTP(secret, {
step = 30, // Time step (default 30 seconds)
digits = 6, // Output digits
algorithm = "SHA-1" // SHA-1, SHA-256, SHA-512
} = {}) {
const keyBytes = base32Decode(secret);
const counter = Math.floor(Date.now() / 1000 / step);
// Convert counter to 8-byte buffer (Big Endian)
const buf = new ArrayBuffer(8);
const view = new DataView(buf);
view.setUint32(4, counter);
const algorithmMapping = {
"SHA1": "SHA-1",
"SHA256": "SHA-256",
"SHA512": "SHA-512"
};
algorithm = algorithm.toUpperCase();// 转换为大写
// 重新映射
if (Object.keys(algorithmMapping).includes(algorithm)) {
algorithm = algorithmMapping[algorithm];
console.log('Re mapping', algorithm);
}
// 再次判断
if (!["SHA-1", "SHA-256", "SHA512"].includes(algorithm)) {
let message = `algorithm: ${algorithm} 不符合预期`;
alert(message)
throw new Error(message)
}
// Import key for HMAC
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyBytes,
{name: "HMAC", hash: algorithm},
false,
["sign"]
);
const hmac = new Uint8Array(
await crypto.subtle.sign("HMAC", cryptoKey, buf)
);
// Dynamic Truncation
const offset = hmac[hmac.length - 1] & 0x0f;
const binary =
((hmac[offset] & 0x7f) << 24) |
((hmac[offset + 1] & 0xff) << 16) |
((hmac[offset + 2] & 0xff) << 8) |
(hmac[offset + 3] & 0xff);
const otp = (binary % 10 ** digits).toString().padStart(digits, "0");
return otp;
}
function parseOtpauthURL(url) {
const u = new URL(url);
if (u.protocol !== "otpauth:") {
throw new Error("Invalid otpauth URL");
}
const type = u.hostname; // totp / hotp
const label = decodeURIComponent(u.pathname.slice(1));
const params = Object.fromEntries(u.searchParams.entries());
return {
type,
label,
secret: params.secret,
issuer: params.issuer,
algorithm: params.algorithm || "SHA-1",
digits: params.digits ? Number(params.digits) : 6,
period: params.period ? Number(params.period) : 30,
};
}
})();
@PrintNow
Copy link
Author

PrintNow commented Dec 2, 2025

A. 安装前提

  1. 安装 Tampermonkey:https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en

  2. 打开扩展开发者模式 - 访问 chrome://extensions/
    image

  3. 配置插件 Tampermonkey - 允许用户脚本
    image

  4. 重启浏览器 - 输入 chrome://restart 按回车
    image

B. 正式安装插件

PixPin_2025-12-02_11-46-45

“OTPAuth URL” 是什么?

AWS 添加 OTP 时,会给你一个二维码,使用相关软件扫描添加,你需要把这张二维码图片保存下来,使用二维码解析工具解析得到 URL,这个就是 OTPAuth URL

格式类似于:otpauth://totp/Amazon%20Web%20Services:shine@123456789?secret=SECRET-SECRET-SECRET-SECRET&issuer=Amazon%20Web%20Services

⚠️ 注意:

  1. 如果你是从 Google Authenticator 导出的二维码,还需要通过工具最终解码才能得到真实的 OTPAuth URL,推荐解码工具:
  2. 如果你是使用 Microsoft Authenticator,不好意思根本导出不了,只能重置 OTPAuth 改成其他软件添加比如 Google Authenticator, Stratum (开源)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment