diff --git a/background.js b/background.js index a7f8a38..7cc5b2f 100644 --- a/background.js +++ b/background.js @@ -32,7 +32,7 @@ let rpcFunctions; async function requestVerifyEmail(email, token, gid) { return await encodeResponse( - await fetch("https://store.steampowered.com/join/ajaxverifyemail", { + await fetch("https://store.steampowered.com/join/ajaxverifyemail#sage", { method: "POST", mode: "cors", credentials: "include", @@ -44,6 +44,7 @@ let rpcFunctions; }) ); } + async function verifyEmail(link) { return await encodeResponse( await fetch(link, { @@ -54,8 +55,20 @@ let rpcFunctions; ); } + const steamCookieLock = lock(); async function createAccount(username, password, creationId) { - return await encodeResponse( + console.log("[SAGE] [CREATE ACCOUNT] waiting for lock", creationId); + await steamCookieLock(); + steamCookieLock.lock(); + console.log("[SAGE] [CREATE ACCOUNT] acquired lock", creationId); + // we remove the cookie because otherwise we can get an outdated cookie + // also for some reason, deleting cookies is not instant on firefox, so we + // just do it again until its really gone + while(await new Promise(res => { + chrome.cookies.remove({ name: "steamLoginSecure", url: "https://store.steampowered.com" }, res); + }) !== null) + await new Promise(res => setTimeout(res, 50)); + const response = await encodeResponse( await fetch("https://store.steampowered.com/join/createaccount", { method: "POST", mode: "cors", @@ -67,6 +80,21 @@ let rpcFunctions; } }) ); + let cookie, tries = 40; + while(tries > 0 && response.data.bSuccess) { + cookie = await new Promise(res => { + chrome.cookies.get({ name: "steamLoginSecure", url: "https://store.steampowered.com" }, res); + }); + if(cookie) break; + console.log("[SAGE] [CREATE ACCOUNT] no cookie yet, retrying in 50ms"); + await new Promise(res => setTimeout(res, 50)); + tries--; + } + + console.log("[SAGE] [CREATE ACCOUNT] cookie", cookie) + steamCookieLock.unlock(); + console.log("[SAGE] [CREATE ACCOUNT] released lock", creationId); + return { response, cookie }; } async function sendWebhook(webhook, payload) { @@ -82,8 +110,60 @@ let rpcFunctions; }) ); } + + async function disableSteamGuardRequest(cookie) { + console.log("[SAGE] [STEAMGUARD DISABLE] cookie", cookie); + delete cookie.hostOnly; + delete cookie.session; + if(cookie.domain === ".store.steampowered.com") cookie.domain = "store.steampowered.com"; + cookie.url = `https://${cookie.domain}${cookie.path}`; + + console.log("[SAGE] [STEAMGUARD DISABLE] waiting for lock"); + await steamCookieLock(); + steamCookieLock.lock(); + console.log("[SAGE] [STEAMGUARD DISABLE] acquired lock"); + await new Promise(res => chrome.cookies.set(cookie, res)); + + // enable steam guard first + let sessionID; + sessionID = await new Promise(res => { + chrome.cookies.get({ name: "sessionid", url: "https://store.steampowered.com" }, res); + }); + const enable = await fetch("https://store.steampowered.com/twofactor/manage_action", { + method: "POST", + mode: "cors", + credentials: "include", + body: new URLSearchParams({ action: "email", sessionid: sessionID.value, email_authenticator_check: "on" }).toString(), + headers: { + ...steamImpersonation, + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + } + }); + + sessionID = await new Promise(res => { + chrome.cookies.get({ name: "sessionid", url: "https://store.steampowered.com" }, res); + }); + const disable = await fetch("https://store.steampowered.com/twofactor/manage_action", { + method: "POST", + mode: "cors", + credentials: "include", + body: new URLSearchParams({ action: "actuallynone", sessionid: sessionID.value }).toString(), + headers: { + ...steamImpersonation, + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + } + }); + + steamCookieLock.unlock(); + console.log("[SAGE] [STEAMGUARD DISABLE] released lock"); + + return { + enable: await encodeResponse(enable), + disable: await encodeResponse(disable), + } + } - rpcFunctions = { about, requestVerifyEmail, verifyEmail, createAccount, sendWebhook }; + rpcFunctions = { about, requestVerifyEmail, verifyEmail, createAccount, sendWebhook, disableSteamGuardRequest }; browser.runtime.onConnect.addListener(port => { console.assert(port.name === "sage"); @@ -144,6 +224,15 @@ let rpcFunctions; const origin = details.originUrl || details.initiator; if(!origin) return; const IS_STEAM_CAPTCHA = details.url.indexOf(RECAPTCHA_SITEKEY) >= 0; + // our own request + if(details.url.endsWith("#sage")) { + console.log("[SAGE] HTTP own http request", details); + details.requestHeaders = modifyHeaders(details.requestHeaders, { + "user-agent": STEAM_USERAGENT, + origin: null, + ...removeOthers, ...removeAllClientHints + }) + } // main request to steam if(details.url.startsWith("https://store.steampowered.com/join/")) { const url = new URL(details.url); @@ -202,3 +291,40 @@ let rpcFunctions; rpcFunctions.about().then(about => { console.log(`[SAGE] Running version ${about.version} in ${about.browser} on ${about.platform}`); }); + +/* helpers */ + +// taken from https://github.com/hansSchall/simple-promise-locks/blob/main/index.js +function lock(lockedDefault = false) { + const waiting = []; + let locked = lockedDefault; + const lock = function () { + return new Promise( + resolve => { + if (locked) + waiting.push(resolve); + else + resolve(); + } + ); + }; + lock.unlock = () => { + locked = false; + while (!locked && waiting.length) { + waiting.shift()(); + } + }; + lock.lock = (locked_) => { + if (locked_ === false) lock.unlock(); + locked = true; + }; + Object.defineProperty(lock, "locked", { + get() { + return locked; + }, + set(locked) { + lock.lock(locked); + } + }); + return lock; +}; diff --git a/captcha.js b/captcha.js deleted file mode 100644 index 6eccc58..0000000 --- a/captcha.js +++ /dev/null @@ -1,15 +0,0 @@ -if(window.location.hash.startsWith("#sage/")) { - console.log("[SAGE] init"); - const css = document.createElement("style"); - css.innerText = `.section_title,.row_flex,.form_row:last-child,.responsive_header,#global_header,#footer,#footer_spacer{display:none !important;}.responsive_page_content,.responsive_page_template_content,.joinsteam_content_container{padding:0px !important;}#captcha_entry{position: fixed;top: 0;left: 0;height: 100vh;width: 100vw;display: flex;justify-content: center;align-items: center;}`; - document.head.appendChild(css); - setInterval(() => { - const token = document.getElementById("g-recaptcha-response"); - if(token && token.value) { - const gid = document.getElementById("captchagid").value; - console.log("[SAGE] captcha solved", token.value); - const session = window.location.hash.split("/")[1]; - document.location = `https://sage.leodev.xyz/gen#token=${token.value}&gid=${gid}&session=${session}`; - } - }, 500); -} \ No newline at end of file diff --git a/inject_recaptcha.js b/inject_recaptcha.js new file mode 100644 index 0000000..188a970 --- /dev/null +++ b/inject_recaptcha.js @@ -0,0 +1,9 @@ +if(window.location.search.includes("6LdIFr0ZAAAAAO3vz0O0OQrtAefzdJcWQM2TMYQH")) { + console.log("[SAGE] [RECAPTCHA] init"); + setInterval(() => { + if(document.querySelector(".recaptcha-checkbox-expired")) { + console.log("[SAGE] [RECAPTCHA] detected stuck recaptcha; reloading"); + window.location.reload(); + } + }, 500); +} \ No newline at end of file diff --git a/rpcbridge.js b/inject_sage.js similarity index 80% rename from rpcbridge.js rename to inject_sage.js index 6e6bcf7..df65c56 100644 --- a/rpcbridge.js +++ b/inject_sage.js @@ -2,6 +2,12 @@ const port = chrome.runtime.connect({ name: "sage" }); const isFirefox = !!globalThis.browser; console.log("[SAGE] [RPCBRIDGE] init"); +setTimeout(() => { + const manifest = chrome.runtime.getManifest(); + document.body.setAttribute("sage-data", JSON.stringify({ + version: +manifest.version + })); +}, 0) const customEventFactory = isFirefox ? ( (name, options) => { diff --git a/inject_steam.js b/inject_steam.js new file mode 100644 index 0000000..77bbaf7 --- /dev/null +++ b/inject_steam.js @@ -0,0 +1,18 @@ +if(window.location.hash === "#sage") { + console.log("[SAGE] [STEAM] init"); + setTimeout(() => { + // the setTimeout makes sure the DOM is setup and we can actually add the css + const css = document.createElement("style"); + css.innerText = `.section_title,.row_flex,.form_row:last-child,.responsive_header,#global_header,#footer,#footer_spacer{display:none !important;}.responsive_page_content,.responsive_page_template_content,.joinsteam_content_container{padding:0px !important;}#captcha_entry{position: fixed;top: 0;left: 0;height: 100vh;width: 100vw;display: flex;justify-content: center;align-items: center;}`; + document.head.appendChild(css); + }, 0); + setInterval(() => { + const token = document.getElementById("g-recaptcha-response"); + if(token && token.value) { + const gid = document.getElementById("captchagid").value; + console.log("[SAGE] [STEAM] captcha solved", token.value); + const origin = document.referrer ? new URL(document.referrer).origin : "https://sage.leodev.xyz"; + document.location = `${origin}/gen#token=${token.value}&gid=${gid}`; + }; + }, 500); +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index fe67543..fcd9b62 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,12 @@ { "name": "SAGE", "description": "Assistance for helping sage.leodev.xyz interact with Steam and other services.", - "version": "4", + "version": "5", "manifest_version": 2, "permissions": [ "webRequest", "webRequestBlocking", + "cookies", "*://recaptcha.net/recaptcha/enterprise/*", "*://sage.leodev.xyz/*", "*://store.steampowered.com/join/*" @@ -29,7 +30,17 @@ "https://store.steampowered.com/join/" ], "js": [ - "captcha.js" + "inject_steam.js" + ], + "all_frames": true, + "run_at": "document_start" + }, + { + "matches": [ + "https://recaptcha.net/recaptcha/enterprise/anchor*" + ], + "js": [ + "inject_recaptcha.js" ], "all_frames": true }, @@ -38,9 +49,10 @@ "https://sage.leodev.xyz/*" ], "js": [ - "rpcbridge.js" + "inject_sage.js" ], - "all_frames": true + "all_frames": true, + "run_at": "document_start" } ] } \ No newline at end of file