From a8e86f47c16bc2fade26e3313aca2e35fe20999f Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 20 Feb 2024 11:15:59 +0100 Subject: [PATCH 01/10] Support 2FA and Moodle The ability to auto-login in Moodle (foreign language learning platform from LSK MSZ) has been added. The code also waits for a two-factor authentication code to be entered (e.g. as recommended by TUD via the 2FA extension or on the keyboard) --- script.user.js | 53 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/script.user.js b/script.user.js index 8b8684e..b2d23e9 100644 --- a/script.user.js +++ b/script.user.js @@ -1,10 +1,10 @@ // ==UserScript== -// @name TUD AutoLogin +// @name TUD AutoLogin with 2FA // @namespace http://tampermonkey.net/ -// @version 0.3.5 -// @description Stop wasting your time entering login credentials or pressing useless buttons! -// @author spyfly -// @website https://tud-autologin.spyfly.xyz/ +// @version 0.4.1 +// @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) +// @author FurTactics +// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg // @match https://bildungsportal.sachsen.de/* // @match https://idp2.tu-dresden.de/* // @match https://jexam.inf.tu-dresden.de/* @@ -18,10 +18,9 @@ // @match https://idp.tu-dresden.de/idp* // @match https://tud-autologin.spyfly.xyz/configuration/ // @match https://tex.zih.tu-dresden.de/* -// @supportURL https://github.com/spyfly/TUD-AutoLogin/issues -// @updateURL https://raw.githubusercontent.com/spyfly/TUD-AutoLogin/master/script.user.js -// @grant GM_getValue -// @grant GM_setValue +// @match https://tud.uni-leipzig.de/moodle2/* +// @grant GM_getValue +// @grant GM_setValue // ==/UserScript== (async function () { @@ -47,6 +46,7 @@ const isLskOnline = (window.location.host == "lskonline.tu-dresden.de"); const isTudIdp = (window.location.host == "idp.tu-dresden.de"); const isShareLatex = (window.location.host == "tex.zih.tu-dresden.de"); + const isMoodle = (window.location.host == "tud.uni-leipzig.de") const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); @@ -95,6 +95,7 @@ } else if (isTudLoginPage || isTudIdp) { // We are on the TUD I2DP Page const hasLoginField = (document.getElementById("username") != undefined); + const hasSecretField = (document.getElementById("fudis_otp_input") != undefined); if (hasLoginField) { // Try to fill in credentials @@ -103,9 +104,25 @@ if (credentialsAvailable) { document.getElementsByName("_eventId_proceed")[0].click(); } - } else { - // Just press the continue button - document.getElementsByName("_eventId_proceed")[0].click(); + } + if (hasSecretField) { + + //this setup works with either keyboard input or, for example, 2FAS browser extension + let typingTimer; //timer identifier + let doneTypingInterval = 2000; //time in ms (2 seconds) + let myInput = document.getElementById('fudis_otp_input'); + + //on keyup, start the countdown + myInput.addEventListener('keypress', evt => { + clearTimeout(typingTimer); + if (myInput.value) { + typingTimer = setTimeout(doneTyping, doneTypingInterval); + } + }); + //user is "finished typing," do click + function doneTyping() { + document.getElementsByName("_eventId_proceed")[0].click(); + } } } else if (isJExam) { // AutoLogin for JExam 5 @@ -128,6 +145,18 @@ document.getElementById("logIn_btn").click(); } } + } else if (isMoodle) { + // AutoLogin for Moodle + if (window.location.pathname == "/moodle2/login/index.php") { + // Check if we are on the login page + document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/auth/shibboleth/index.php"]').click(); + } else { + //Go to the login page if we need to login + const loginBtn = document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/login/index.php"]'); + if (loginBtn) { + loginBtn.click(); + } + } } else if (isQisServer) { //AutoLogin for QISServer if (document.getElementsByClassName("loginuser").length >= 1) { From 3d28736e0c7135bccdd4a66e8c1156735a79e057 Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 20 Feb 2024 11:41:30 +0100 Subject: [PATCH 02/10] TUD Autologin with generating the 2FA The ability to auto-login in Moodle (foreign language learning platform from LSK MSZ) has been added. The script also generates a six-digit OTP code by a given key (ONLY FOR GEEKS) --- script_only_for_Geeks.js | 290 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 script_only_for_Geeks.js diff --git a/script_only_for_Geeks.js b/script_only_for_Geeks.js new file mode 100644 index 0000000..c448e8f --- /dev/null +++ b/script_only_for_Geeks.js @@ -0,0 +1,290 @@ +// ==UserScript== +// @name TUD AutoLogin with generating the 2FA +// @namespace http://tampermonkey.net/ +// @version 0.4.1 (for Geeks) +// @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) +// @author FurTactics +// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg +// @match https://bildungsportal.sachsen.de/* +// @match https://idp2.tu-dresden.de/* +// @match https://jexam.inf.tu-dresden.de/* +// @match https://selma.tu-dresden.de/* +// @match https://exam.zih.tu-dresden.de/* +// @match https://exam2.zih.tu-dresden.de/* +// @match https://exam3.zih.tu-dresden.de/* +// @match https://qis.dez.tu-dresden.de/qisserver/* +// @match https://msx.tu-dresden.de/owa/auth/logon* +// @match https://lskonline.tu-dresden.de/lskonline/de/102.0.1* +// @match https://idp.tu-dresden.de/idp* +// @match https://tud-autologin.spyfly.xyz/configuration/ +// @match https://tex.zih.tu-dresden.de/* +// @match https://tud.uni-leipzig.de/moodle2/* +// @grant GM_getValue +// @grant GM_setValue +// ==/UserScript== + +'use strict'; (function (G) { + // this function is used to generate the OTP code + function r(d, b, c) { + var h = 0, a = [], f = 0, g, m, k, e, l, p, q, t, w = !1, n = [], u = [], v, r = !1; c = c || {}; g = c.encoding || "UTF8"; v = c.numRounds || 1; if (v !== parseInt(v, 10) || 1 > v) throw Error("numRounds must a integer >= 1"); if ("SHA-1" === d) l = 512, p = z, q = H, e = 160, t = function (a) { return a.slice() }; else throw Error("Chosen SHA variant is not supported"); k = A(b, g); m = x(d); this.setHMACKey = function (a, f, b) { + var c; if (!0 === w) throw Error("HMAC key already set"); if (!0 === r) throw Error("Cannot set HMAC key after calling update"); + g = (b || {}).encoding || "UTF8"; f = A(f, g)(a); a = f.binLen; f = f.value; c = l >>> 3; b = c / 4 - 1; if (c < a / 8) { for (f = q(f, a, 0, x(d), e); f.length <= b;)f.push(0); f[b] &= 4294967040 } else if (c > a / 8) { for (; f.length <= b;)f.push(0); f[b] &= 4294967040 } for (a = 0; a <= b; a += 1)n[a] = f[a] ^ 909522486, u[a] = f[a] ^ 1549556828; m = p(n, m); h = l; w = !0 + }; this.update = function (b) { var e, g, c, d = 0, q = l >>> 5; e = k(b, a, f); b = e.binLen; g = e.value; e = b >>> 5; for (c = 0; c < e; c += q)d + l <= b && (m = p(g.slice(c, c + q), m), d += l); h += d; a = g.slice(d >>> 5); f = b % l; r = !0 }; this.getHash = function (b, g) { + var c, k, l, p; if (!0 === + w) throw Error("Cannot call getHash after setting HMAC key"); l = B(g); switch (b) { case "HEX": c = function (a) { return C(a, e, l) }; break; case "B64": c = function (a) { return D(a, e, l) }; break; case "BYTES": c = function (a) { return E(a, e) }; break; case "ARRAYBUFFER": try { k = new ArrayBuffer(0) } catch (I) { throw Error("ARRAYBUFFER not supported by this environment"); } c = function (a) { return F(a, e) }; break; default: throw Error("format must be HEX, B64, BYTES, or ARRAYBUFFER"); }p = q(a.slice(), f, h, t(m), e); for (k = 1; k < v; k += 1)p = q(p, e, 0, x(d), e); + return c(p) + }; this.getHMAC = function (b, g) { + var c, k, n, r; if (!1 === w) throw Error("Cannot call getHMAC without first setting HMAC key"); n = B(g); switch (b) { + case "HEX": c = function (a) { return C(a, e, n) }; break; case "B64": c = function (a) { return D(a, e, n) }; break; case "BYTES": c = function (a) { return E(a, e) }; break; case "ARRAYBUFFER": try { c = new ArrayBuffer(0) } catch (I) { throw Error("ARRAYBUFFER not supported by this environment"); } c = function (a) { return F(a, e) }; break; default: throw Error("outputFormat must be HEX, B64, BYTES, or ARRAYBUFFER"); + }k = q(a.slice(), f, h, t(m), e); r = p(u, x(d)); r = q(k, e, l, r, e); return c(r) + } + } function C(d, b, c) { var h = ""; b /= 8; var a, f; for (a = 0; a < b; a += 1)f = d[a >>> 2] >>> 8 * (3 + a % 4 * -1), h += "0123456789abcdef".charAt(f >>> 4 & 15) + "0123456789abcdef".charAt(f & 15); return c.outputUpper ? h.toUpperCase() : h } function D(d, b, c) { + var h = "", a = b / 8, f, g, m; for (f = 0; f < a; f += 3)for (g = f + 1 < a ? d[f + 1 >>> 2] : 0, m = f + 2 < a ? d[f + 2 >>> 2] : 0, m = (d[f >>> 2] >>> 8 * (3 + f % 4 * -1) & 255) << 16 | (g >>> 8 * (3 + (f + 1) % 4 * -1) & 255) << 8 | m >>> 8 * (3 + (f + 2) % 4 * -1) & 255, g = 0; 4 > g; g += 1)8 * f + 6 * g <= b ? h += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(m >>> + 6 * (3 - g) & 63) : h += c.b64Pad; return h + } function E(d, b) { var c = "", h = b / 8, a, f; for (a = 0; a < h; a += 1)f = d[a >>> 2] >>> 8 * (3 + a % 4 * -1) & 255, c += String.fromCharCode(f); return c } function F(d, b) { var c = b / 8, h, a = new ArrayBuffer(c), f; f = new Uint8Array(a); for (h = 0; h < c; h += 1)f[h] = d[h >>> 2] >>> 8 * (3 + h % 4 * -1) & 255; return a } function B(d) { + var b = { outputUpper: !1, b64Pad: "=", shakeLen: -1 }; d = d || {}; b.outputUpper = d.outputUpper || !1; !0 === d.hasOwnProperty("b64Pad") && (b.b64Pad = d.b64Pad); if ("boolean" !== typeof b.outputUpper) throw Error("Invalid outputUpper formatting option"); + if ("string" !== typeof b.b64Pad) throw Error("Invalid b64Pad formatting option"); return b + } function A(d, b) { + var c; switch (b) { case "UTF8": case "UTF16BE": case "UTF16LE": break; default: throw Error("encoding must be UTF8, UTF16BE, or UTF16LE"); }switch (d) { + case "HEX": c = function (b, a, f) { + var g = b.length, c, d, e, l, p; if (0 !== g % 2) throw Error("String of HEX type must be in byte increments"); a = a || [0]; f = f || 0; p = f >>> 3; for (c = 0; c < g; c += 2) { + d = parseInt(b.substr(c, 2), 16); if (isNaN(d)) throw Error("String of HEX type contains invalid characters"); + l = (c >>> 1) + p; for (e = l >>> 2; a.length <= e;)a.push(0); a[e] |= d << 8 * (3 + l % 4 * -1) + } return { value: a, binLen: 4 * g + f } + }; break; case "TEXT": c = function (c, a, f) { + var g, d, k = 0, e, l, p, q, t, n; a = a || [0]; f = f || 0; p = f >>> 3; if ("UTF8" === b) for (n = 3, e = 0; e < c.length; e += 1)for (g = c.charCodeAt(e), d = [], 128 > g ? d.push(g) : 2048 > g ? (d.push(192 | g >>> 6), d.push(128 | g & 63)) : 55296 > g || 57344 <= g ? d.push(224 | g >>> 12, 128 | g >>> 6 & 63, 128 | g & 63) : (e += 1, g = 65536 + ((g & 1023) << 10 | c.charCodeAt(e) & 1023), d.push(240 | g >>> 18, 128 | g >>> 12 & 63, 128 | g >>> 6 & 63, 128 | g & 63)), l = 0; l < d.length; l += 1) { + t = k + + p; for (q = t >>> 2; a.length <= q;)a.push(0); a[q] |= d[l] << 8 * (n + t % 4 * -1); k += 1 + } else if ("UTF16BE" === b || "UTF16LE" === b) for (n = 2, d = "UTF16LE" === b && !0 || "UTF16LE" !== b && !1, e = 0; e < c.length; e += 1) { g = c.charCodeAt(e); !0 === d && (l = g & 255, g = l << 8 | g >>> 8); t = k + p; for (q = t >>> 2; a.length <= q;)a.push(0); a[q] |= g << 8 * (n + t % 4 * -1); k += 2 } return { value: a, binLen: 8 * k + f } + }; break; case "B64": c = function (b, a, f) { + var c = 0, d, k, e, l, p, q, n; if (-1 === b.search(/^[a-zA-Z0-9=+\/]+$/)) throw Error("Invalid character in base-64 string"); k = b.indexOf("="); b = b.replace(/\=/g, + ""); if (-1 !== k && k < b.length) throw Error("Invalid '=' found in base-64 string"); a = a || [0]; f = f || 0; q = f >>> 3; for (k = 0; k < b.length; k += 4) { p = b.substr(k, 4); for (e = l = 0; e < p.length; e += 1)d = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(p[e]), l |= d << 18 - 6 * e; for (e = 0; e < p.length - 1; e += 1) { n = c + q; for (d = n >>> 2; a.length <= d;)a.push(0); a[d] |= (l >>> 16 - 8 * e & 255) << 8 * (3 + n % 4 * -1); c += 1 } } return { value: a, binLen: 8 * c + f } + }; break; case "BYTES": c = function (b, a, c) { + var d, m, k, e, l; a = a || [0]; c = c || 0; k = c >>> 3; for (m = 0; m < b.length; m += + 1)d = b.charCodeAt(m), l = m + k, e = l >>> 2, a.length <= e && a.push(0), a[e] |= d << 8 * (3 + l % 4 * -1); return { value: a, binLen: 8 * b.length + c } + }; break; case "ARRAYBUFFER": try { c = new ArrayBuffer(0) } catch (h) { throw Error("ARRAYBUFFER not supported by this environment"); } c = function (b, a, c) { var d, m, k, e, l; a = a || [0]; c = c || 0; m = c >>> 3; l = new Uint8Array(b); for (d = 0; d < b.byteLength; d += 1)e = d + m, k = e >>> 2, a.length <= k && a.push(0), a[k] |= l[d] << 8 * (3 + e % 4 * -1); return { value: a, binLen: 8 * b.byteLength + c } }; break; default: throw Error("format must be HEX, TEXT, B64, BYTES, or ARRAYBUFFER"); + }return c + } function n(d, b) { return d << b | d >>> 32 - b } function u(d, b) { var c = (d & 65535) + (b & 65535); return ((d >>> 16) + (b >>> 16) + (c >>> 16) & 65535) << 16 | c & 65535 } function y(d, b, c, h, a) { var f = (d & 65535) + (b & 65535) + (c & 65535) + (h & 65535) + (a & 65535); return ((d >>> 16) + (b >>> 16) + (c >>> 16) + (h >>> 16) + (a >>> 16) + (f >>> 16) & 65535) << 16 | f & 65535 } function x(d) { var b = []; if ("SHA-1" === d) b = [1732584193, 4023233417, 2562383102, 271733878, 3285377520]; else throw Error("No SHA variants supported"); return b } function z(d, b) { + var c = [], h, a, f, g, m, k, e; h = b[0]; a = b[1]; + f = b[2]; g = b[3]; m = b[4]; for (e = 0; 80 > e; e += 1)c[e] = 16 > e ? d[e] : n(c[e - 3] ^ c[e - 8] ^ c[e - 14] ^ c[e - 16], 1), k = 20 > e ? y(n(h, 5), a & f ^ ~a & g, m, 1518500249, c[e]) : 40 > e ? y(n(h, 5), a ^ f ^ g, m, 1859775393, c[e]) : 60 > e ? y(n(h, 5), a & f ^ a & g ^ f & g, m, 2400959708, c[e]) : y(n(h, 5), a ^ f ^ g, m, 3395469782, c[e]), m = g, g = f, f = n(a, 30), a = h, h = k; b[0] = u(h, b[0]); b[1] = u(a, b[1]); b[2] = u(f, b[2]); b[3] = u(g, b[3]); b[4] = u(m, b[4]); return b + } function H(d, b, c, h) { + var a; for (a = (b + 65 >>> 9 << 4) + 15; d.length <= a;)d.push(0); d[b >>> 5] |= 128 << 24 - b % 32; b += c; d[a] = b & 4294967295; d[a - 1] = b / 4294967296 | 0; + b = d.length; for (a = 0; a < b; a += 16)h = z(d.slice(a, a + 16), h); return h + } "function" === typeof define && define.amd ? define(function () { return r }) : "undefined" !== typeof exports ? ("undefined" !== typeof module && module.exports && (module.exports = r), exports = r) : G.jsSHA = r +})(this); + +(async function () { + 'use strict'; + //Load Configuration values + var tud = { + username: "", + password: "", + secret: "" // 2FA Secret from QR-Code + } + if (GM_getValue("tud_creds") != undefined) { + tud = GM_getValue("tud_creds"); + } + + // Code starts here + const isConfigPage = (window.location.host == "tud-autologin.spyfly.xyz"); + const isOpalLoginPage = (window.location.host == "bildungsportal.sachsen.de"); + const isTudExamLoginPage = (window.location.host.startsWith("exam") && window.location.host.endsWith(".zih.tu-dresden.de")); + const isTudLoginPage = (window.location.host == "idp2.tu-dresden.de"); + const isJExam = (window.location.host == "jexam.inf.tu-dresden.de"); + const isSelma = (window.location.host == "selma.tu-dresden.de"); + const isQisServer = (window.location.host == "qis.dez.tu-dresden.de"); + const isOWA = (window.location.host == "msx.tu-dresden.de"); + const isLskOnline = (window.location.host == "lskonline.tu-dresden.de"); + const isTudIdp = (window.location.host == "idp.tu-dresden.de"); + const isShareLatex = (window.location.host == "tex.zih.tu-dresden.de"); + const isMoodle = (window.location.host == "tud.uni-leipzig.de") + + const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); + const secretIsAvailable = tud.secret.length > 0; + + if (isConfigPage) { + document.getElementById("notinstalled").remove(); + document.getElementById("username").value = tud.username; + document.getElementById("password").value = tud.password; + + document.getElementById("save").addEventListener("click", function () { + GM_setValue("tud_creds", { + username: document.getElementById("username").value, + password: document.getElementById("password").value + }); + alert("Configuration updated!") + }); + } else if (isOpalLoginPage || isTudExamLoginPage) { + const mainPageLoginBtn = document.querySelector("button[name=shibLogin]"); + const contentPageLoginBtn = document.querySelector('.btn-sm[title="Login"]'); + var loginSelector = document.querySelector("select[name$='wayfselection']"); + var loginIndex; + + //Do we have to perform login action in the first place? + if (mainPageLoginBtn || contentPageLoginBtn) { + if (contentPageLoginBtn) { + + contentPageLoginBtn.click(); + // Wait for Login Prompt to appear + while (loginSelector == null) { + loginSelector = document.querySelector("select[name$='wayfselection']"); + await sleep(100); + } + } + + // Select TU-Dresden as Login Option + for (const option of loginSelector.options) { + if (option.text == "TU Dresden") { + loginIndex = option.index; + break; + } + } + loginSelector.selectedIndex = loginIndex; + + //Press Login Button + document.querySelector("button[name$='shibLogin']").click(); + } + } else if (isTudLoginPage || isTudIdp) { + // We are on the TUD I2DP Page + const hasLoginField = (document.getElementById("username") != undefined); + const hasSecretField = (document.getElementById("fudis_otp_input") != undefined); + + if (hasLoginField) { + // Try to fill in credentials + document.getElementById("username").value = tud.username; + document.getElementById("password").value = tud.password; + if (credentialsAvailable) { + document.getElementsByName("_eventId_proceed")[0].click(); + } + } + if (hasSecretField) { + document.getElementById("fudis_otp_input").value = getOTP(tud.secret); + if (secretIsAvailable) { + document.getElementsByName("_eventId_proceed")[0].click(); + } + } + } else if (isJExam) { + // AutoLogin for JExam 5 + if (window.location.pathname == "/") { + window.location = "https://jexam.inf.tu-dresden.de/de.jexam.web.v5/" + } else if (window.location.pathname == "/de.jexam.web.v5/spring/welcome") { + // Try to fill in credentials + document.getElementById("username").value = tud.username; + document.getElementById("password").value = tud.password; + if (credentialsAvailable) { + document.getElementsByClassName("submit")[0].click(); + } + } + } else if (isSelma) { + // AutoLogin for selma + if (document.getElementById("field_user") != undefined) { + document.getElementById("field_user").value = tud.username; + document.getElementById("field_pass").value = tud.password; + if (credentialsAvailable) { + document.getElementById("logIn_btn").click(); + } + } + } else if (isMoodle) { + // AutoLogin for Moodle + if (window.location.pathname == "/moodle2/login/index.php") { + // Check if we are on the login page + document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/auth/shibboleth/index.php"]').click(); + } else { + //Go to the login page if we need to login + const loginBtn = document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/login/index.php"]'); + if (loginBtn) { + loginBtn.click(); + } + } + } else if (isQisServer) { + //AutoLogin for QISServer + if (document.getElementsByClassName("loginuser").length >= 1) { + document.getElementsByClassName("loginuser")[0].value = tud.username; + document.getElementsByClassName("loginpass")[0].value = tud.password; + if (credentialsAvailable) { + document.getElementsByClassName("submit")[0].click(); + } + } + } else if (isOWA) { + //AutoLogin for OWA + document.getElementById('username').value = tud.username; + document.getElementById('password').value = tud.password; + if (credentialsAvailable) { + document.getElementsByClassName("signinbutton")[0].click(); + } + } else if (isLskOnline) { + //AutoLogin for LSKOnline + document.getElementsByName('j_username')[0].value = tud.username; + document.getElementsByName('j_password')[0].value = tud.password; + if (credentialsAvailable) { + document.getElementsByName('submit')[0].click(); + } + } else if (isShareLatex) { + //AutoLogin for ShareLaTeX + if (window.location.pathname == "/saml/login") { + // Check if we are on the login page + document.querySelector('[href="/saml/login/go"]').click(); + } else { + //Go to the login page if we need to login + const loginBtn = document.querySelector('[href="/login"]'); + if (loginBtn) { + loginBtn.click(); + } + } + } +})(); + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// TOTP Code modified from https://github.com/tkooda/totp.info and https://gist.github.com/gbraad/2885828 + +function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); } +function hex2dec(s) { return parseInt(s, 16); } + +function base32tohex(base32) { + var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + var bits = ""; + var hex = ""; + + for (var i = 0; i < base32.length; i++) { + var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); + bits += leftpad(val.toString(2), 5, '0'); + } + + for (var n = 0; n + 4 <= bits.length; n += 4) { + var chunk = bits.substr(n, 4); + hex = hex + parseInt(chunk, 2).toString(16); + } + return hex.length % 2 ? hex + "0" : hex; +}; + + +function leftpad(str, len, pad) { + if (len + 1 >= str.length) { + str = Array(len + 1 - str.length).join(pad) + str; + } + return str; +}; + +function updateOtp(secret) { + var epoch = Math.round(new Date().getTime() / 1000.0); + var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); + var shaObj = new jsSHA("SHA-1", "HEX"); + shaObj.setHMACKey(base32tohex(secret), "HEX"); + shaObj.update(time); + var hmac = shaObj.getHMAC("HEX"); + var offset = hex2dec(hmac.substring(hmac.length - 1)); + var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; + otp = (otp).substr(otp.length - 6, 6); + return otp; +}; + +function getOTP(secret) { + if (secret) { + return updateOtp(secret); + } else { + return undefined + } +}; \ No newline at end of file From 4c225cafca8f30de045d8d82463355546875cfce Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 9 Apr 2024 00:10:52 +0200 Subject: [PATCH 03/10] Bug Fixes and no infinite loops --- ..._Geeks.js => script_only_for_Geeks.user.js | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) rename script_only_for_Geeks.js => script_only_for_Geeks.user.js (93%) diff --git a/script_only_for_Geeks.js b/script_only_for_Geeks.user.js similarity index 93% rename from script_only_for_Geeks.js rename to script_only_for_Geeks.user.js index c448e8f..120b7e5 100644 --- a/script_only_for_Geeks.js +++ b/script_only_for_Geeks.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TUD AutoLogin with generating the 2FA // @namespace http://tampermonkey.net/ -// @version 0.4.1 (for Geeks) +// @version 0.4.2 // @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) // @author FurTactics // @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg @@ -24,7 +24,6 @@ // ==/UserScript== 'use strict'; (function (G) { - // this function is used to generate the OTP code function r(d, b, c) { var h = 0, a = [], f = 0, g, m, k, e, l, p, q, t, w = !1, n = [], u = [], v, r = !1; c = c || {}; g = c.encoding || "UTF8"; v = c.numRounds || 1; if (v !== parseInt(v, 10) || 1 > v) throw Error("numRounds must a integer >= 1"); if ("SHA-1" === d) l = 512, p = z, q = H, e = 160, t = function (a) { return a.slice() }; else throw Error("Chosen SHA variant is not supported"); k = A(b, g); m = x(d); this.setHMACKey = function (a, f, b) { var c; if (!0 === w) throw Error("HMAC key already set"); if (!0 === r) throw Error("Cannot set HMAC key after calling update"); @@ -54,7 +53,7 @@ }; break; case "TEXT": c = function (c, a, f) { var g, d, k = 0, e, l, p, q, t, n; a = a || [0]; f = f || 0; p = f >>> 3; if ("UTF8" === b) for (n = 3, e = 0; e < c.length; e += 1)for (g = c.charCodeAt(e), d = [], 128 > g ? d.push(g) : 2048 > g ? (d.push(192 | g >>> 6), d.push(128 | g & 63)) : 55296 > g || 57344 <= g ? d.push(224 | g >>> 12, 128 | g >>> 6 & 63, 128 | g & 63) : (e += 1, g = 65536 + ((g & 1023) << 10 | c.charCodeAt(e) & 1023), d.push(240 | g >>> 18, 128 | g >>> 12 & 63, 128 | g >>> 6 & 63, 128 | g & 63)), l = 0; l < d.length; l += 1) { t = k + - p; for (q = t >>> 2; a.length <= q;)a.push(0); a[q] |= d[l] << 8 * (n + t % 4 * -1); k += 1 + p; for (q = t >>> 2; a.length <= q;)a.push(0); a[q] |= d[l] << 8 * (n + t % 4 * -1); k += 1 } else if ("UTF16BE" === b || "UTF16LE" === b) for (n = 2, d = "UTF16LE" === b && !0 || "UTF16LE" !== b && !1, e = 0; e < c.length; e += 1) { g = c.charCodeAt(e); !0 === d && (l = g & 255, g = l << 8 | g >>> 8); t = k + p; for (q = t >>> 2; a.length <= q;)a.push(0); a[q] |= g << 8 * (n + t % 4 * -1); k += 2 } return { value: a, binLen: 8 * k + f } }; break; case "B64": c = function (b, a, f) { var c = 0, d, k, e, l, p, q, n; if (-1 === b.search(/^[a-zA-Z0-9=+\/]+$/)) throw Error("Invalid character in base-64 string"); k = b.indexOf("="); b = b.replace(/\=/g, @@ -79,7 +78,7 @@ var tud = { username: "", password: "", - secret: "" // 2FA Secret from QR-Code + secret: "" } if (GM_getValue("tud_creds") != undefined) { tud = GM_getValue("tud_creds"); @@ -100,7 +99,9 @@ const isMoodle = (window.location.host == "tud.uni-leipzig.de") const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); - const secretIsAvailable = tud.secret.length > 0; + const secretIsAvailable = tud.secret.length == 32; + let isSecretEntered; + var stats; if (isConfigPage) { document.getElementById("notinstalled").remove(); @@ -108,10 +109,13 @@ document.getElementById("password").value = tud.password; document.getElementById("save").addEventListener("click", function () { + let secretInput = prompt("Enter your secret code with 32 letters and digits from QR-Code"); GM_setValue("tud_creds", { username: document.getElementById("username").value, - password: document.getElementById("password").value + password: document.getElementById("password").value, + secret: secretInput }); + GM_setValue("stats", null); alert("Configuration updated!") }); } else if (isOpalLoginPage || isTudExamLoginPage) { @@ -141,7 +145,7 @@ } loginSelector.selectedIndex = loginIndex; - //Press Login Button + // Press Login Button document.querySelector("button[name$='shibLogin']").click(); } } else if (isTudLoginPage || isTudIdp) { @@ -154,13 +158,19 @@ document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; if (credentialsAvailable) { + GM_setValue("isSecretEntered", false); + GM_setValue("stats", GM_getValue("stats") + 1); document.getElementsByName("_eventId_proceed")[0].click(); } } if (hasSecretField) { document.getElementById("fudis_otp_input").value = getOTP(tud.secret); - if (secretIsAvailable) { + await sleep(100); + if (secretIsAvailable && !GM_getValue("isSecretEntered")) { + GM_setValue("isSecretEntered", true); // this is for insert the code only once document.getElementsByName("_eventId_proceed")[0].click(); + } else { + alert("Something went wrong with your 2FA code"); } } } else if (isJExam) { @@ -172,6 +182,7 @@ document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); } } @@ -202,6 +213,7 @@ document.getElementsByClassName("loginuser")[0].value = tud.username; document.getElementsByClassName("loginpass")[0].value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); } } @@ -210,6 +222,7 @@ document.getElementById('username').value = tud.username; document.getElementById('password').value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("signinbutton")[0].click(); } } else if (isLskOnline) { @@ -238,7 +251,7 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -// TOTP Code modified from https://github.com/tkooda/totp.info and https://gist.github.com/gbraad/2885828 +// TOTP Code modified from https://github.com/tkooda/totp.info and fixed base32tohex function without check the correctness the secret set function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); } function hex2dec(s) { return parseInt(s, 16); } @@ -269,15 +282,22 @@ function leftpad(str, len, pad) { }; function updateOtp(secret) { - var epoch = Math.round(new Date().getTime() / 1000.0); + var epoch = Math.round((new Date().getTime()) / 1000.0); var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); var shaObj = new jsSHA("SHA-1", "HEX"); shaObj.setHMACKey(base32tohex(secret), "HEX"); shaObj.update(time); var hmac = shaObj.getHMAC("HEX"); - var offset = hex2dec(hmac.substring(hmac.length - 1)); + + if (hmac == 'KEY MUST BE IN BYTE INCREMENTS') { + + } else { + var offset = hex2dec(hmac.substring(hmac.length - 1)); + } + var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; otp = (otp).substr(otp.length - 6, 6); + return otp; }; @@ -285,6 +305,6 @@ function getOTP(secret) { if (secret) { return updateOtp(secret); } else { - return undefined + return undefined; } }; \ No newline at end of file From 72cb6150bf1143bc6fea099d7cc6485eb4dfd3c4 Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 9 Apr 2024 00:50:25 +0200 Subject: [PATCH 04/10] Update instructions --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 80953b2..f178727 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,24 @@ This is a browser script, that performs an automatic login for the following TU - [Mailbox@TUD](https://msx.tu-dresden.de/owa) - [LSKOnline](https://lskonline.tu-dresden.de) - [ShareLaTeX](https://tex.zih.tu-dresden.de) +- [Moodle](https://tud.uni-leipzig.de/moodle2/) - and any other service with the TUD Login Page -## Installation +## Installation for use with an application or for manual input 1. Install [Tampermonkey](https://www.tampermonkey.net) for your browser -2. Download the [TUD-AutoLogin UserScript](https://raw.githubusercontent.com/spyfly/TUD-AutoLogin/master/script.user.js) +2. Download the [TUD-AutoLogin UserScript](https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script.user.js) 3. Click the install button to complete the installation +4. Visit the [configuration page](https://tud-autologin.spyfly.xyz/configuration) after installing the script, to add your login credentials +5. (optional) You can use [2FAS Browser Extension](https://2fas.com/browser-extension/) to automate the entry, but confirmation on your phone will be required each time +6. Enjoy :partying_face: -## Configuration -Visit the [configuration page](https://tud-autologin.spyfly.xyz/configuration) after installing the script, to add your login credentials. +## Installation for those who forever want to forget about the existence of login pages (nothing manual) +1. Install [Tampermonkey](https://www.tampermonkey.net) for your browser +2. Download the [TUD-AutoLogin UserScript](https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js) +3. Click the install button to complete the installation +4. Visit the [configuration page](https://tud-autologin.spyfly.xyz/configuration) after installing the script, to add your login credentials +> You will need a 32-digit secret code (Geheimschlüssel), which can be found in the 2FAS app or by decoding the QR code +5. Enjoy more :partying_face: ## Compatibility This script is compatible with any browser capable of supporting [Tampermonkey](https://www.tampermonkey.net) or [Greasemonkey (Firefox only)](https://addons.mozilla.org/de/firefox/addon/greasemonkey/) @@ -26,13 +35,13 @@ Compatible Platforms: - Windows - Linux - MacOS -- Android (tested with [Firefox Nightly](https://blog.mozilla.org/addons/2020/09/29/expanded-extension-support-in-firefox-for-android-nightly/)) +- Android (tested with [Firefox Nightly](https://blog.mozilla.org/addons/2020/09/29/expanded-extension-support-in-firefox-for-android-nightly/) and [Kiwi](https://kiwibrowser.com/)) - iOS/iPadOS via [Custom Shortcut](https://www.icloud.com/shortcuts/b3bef7f4836d461fb73bc3a39f9e3411) (Safari only) ## Usage Visit your favorite TUD websites and enjoy automatic login. -On iOS/iPadOS you will have to run the [Custom Shortcut](https://www.icloud.com/shortcuts/b3bef7f4836d461fb73bc3a39f9e3411) via the Share Menu in Safari. +On iOS/iPadOS you will have to run the Custom Shortcut via the Share Menu in Safari. ## Future development and issues -If you have improvement ideas or encounter issues with the script don't hesitate to open a [new issue](https://github.com/spyfly/TUD-AutoLogin/issues). This could be a request for adding more supported services or reporting a bug. +If you have improvement ideas or encounter issues with the script don't hesitate to open a [new issue](https://github.com/FurTactics/TUD-AutoLogin/issues). This could be a request for adding more supported services or reporting a bug. From e70d8c9a662e7254e09eb78bf10c953725112c61 Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 9 Apr 2024 00:50:55 +0200 Subject: [PATCH 05/10] Update TUD AutoLogin script with improved descriptions, support for additional platforms and 2FA(S) --- script.user.js | 76 +++++++++++++++++------------------ script_only_for_Geeks.user.js | 49 ++++++++++++---------- 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/script.user.js b/script.user.js index b2d23e9..4588f27 100644 --- a/script.user.js +++ b/script.user.js @@ -1,26 +1,31 @@ // ==UserScript== -// @name TUD AutoLogin with 2FA -// @namespace http://tampermonkey.net/ -// @version 0.4.1 -// @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) -// @author FurTactics -// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg -// @match https://bildungsportal.sachsen.de/* -// @match https://idp2.tu-dresden.de/* -// @match https://jexam.inf.tu-dresden.de/* -// @match https://selma.tu-dresden.de/* -// @match https://exam.zih.tu-dresden.de/* -// @match https://exam2.zih.tu-dresden.de/* -// @match https://exam3.zih.tu-dresden.de/* -// @match https://qis.dez.tu-dresden.de/qisserver/* -// @match https://msx.tu-dresden.de/owa/auth/logon* -// @match https://lskonline.tu-dresden.de/lskonline/de/102.0.1* -// @match https://idp.tu-dresden.de/idp* -// @match https://tud-autologin.spyfly.xyz/configuration/ -// @match https://tex.zih.tu-dresden.de/* -// @match https://tud.uni-leipzig.de/moodle2/* -// @grant GM_getValue -// @grant GM_setValue +// @name TUD AutoLogin +// @namespace https://tud-autologin.spyfly.xyz/ +// @version 0.4.2 +// @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) +// @description:de Verschwende keine Zeit mehr mit dem Eingeben von Anmeldedaten oder dem Drücken sinnloser Tasten! +// @description:ru Перестань тратить время на ввод данных или кликанье бесполезных кнопок! +// @author FurTactics +// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg +// @match https://bildungsportal.sachsen.de/* +// @match https://idp2.tu-dresden.de/* +// @match https://jexam.inf.tu-dresden.de/* +// @match https://selma.tu-dresden.de/* +// @match https://exam.zih.tu-dresden.de/* +// @match https://exam2.zih.tu-dresden.de/* +// @match https://exam3.zih.tu-dresden.de/* +// @match https://qis.dez.tu-dresden.de/qisserver/* +// @match https://msx.tu-dresden.de/owa/auth/logon* +// @match https://lskonline.tu-dresden.de/lskonline/de/102.0.1* +// @match https://idp.tu-dresden.de/idp* +// @match https://tud-autologin.spyfly.xyz/configuration/ +// @match https://tex.zih.tu-dresden.de/* +// @match https://tud.uni-leipzig.de/moodle2/* +// @supportURL https://github.com/FurTactics/TUD-AutoLogin/issues +// @updateURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script.user.js +// @downloadURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script.user.js +// @grant GM_getValue +// @grant GM_setValue // ==/UserScript== (async function () { @@ -49,6 +54,7 @@ const isMoodle = (window.location.host == "tud.uni-leipzig.de") const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); + var stats; if (isConfigPage) { document.getElementById("notinstalled").remove(); @@ -60,6 +66,7 @@ username: document.getElementById("username").value, password: document.getElementById("password").value }); + GM_setValue("stats", null); alert("Configuration updated!") }); } else if (isOpalLoginPage || isTudExamLoginPage) { @@ -89,7 +96,7 @@ } loginSelector.selectedIndex = loginIndex; - //Press Login Button + // Press Login Button document.querySelector("button[name$='shibLogin']").click(); } } else if (isTudLoginPage || isTudIdp) { @@ -103,26 +110,14 @@ document.getElementById("password").value = tud.password; if (credentialsAvailable) { document.getElementsByName("_eventId_proceed")[0].click(); + GM_setValue("stats", GM_getValue("stats") + 1); } } if (hasSecretField) { - - //this setup works with either keyboard input or, for example, 2FAS browser extension - let typingTimer; //timer identifier - let doneTypingInterval = 2000; //time in ms (2 seconds) - let myInput = document.getElementById('fudis_otp_input'); - - //on keyup, start the countdown - myInput.addEventListener('keypress', evt => { - clearTimeout(typingTimer); - if (myInput.value) { - typingTimer = setTimeout(doneTyping, doneTypingInterval); - } - }); - //user is "finished typing," do click - function doneTyping() { - document.getElementsByName("_eventId_proceed")[0].click(); + while (document.getElementById("fudis_otp_input").value.length < 6) { + await sleep(1500); } + document.getElementsByName("_eventId_proceed")[0].click(); } } else if (isJExam) { // AutoLogin for JExam 5 @@ -133,6 +128,7 @@ document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); } } @@ -163,6 +159,7 @@ document.getElementsByClassName("loginuser")[0].value = tud.username; document.getElementsByClassName("loginpass")[0].value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); } } @@ -171,6 +168,7 @@ document.getElementById('username').value = tud.username; document.getElementById('password').value = tud.password; if (credentialsAvailable) { + GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("signinbutton")[0].click(); } } else if (isLskOnline) { diff --git a/script_only_for_Geeks.user.js b/script_only_for_Geeks.user.js index 120b7e5..514937d 100644 --- a/script_only_for_Geeks.user.js +++ b/script_only_for_Geeks.user.js @@ -1,26 +1,31 @@ // ==UserScript== -// @name TUD AutoLogin with generating the 2FA -// @namespace http://tampermonkey.net/ -// @version 0.4.2 -// @description Stop wasting your time entering login credentials or pressing useless buttons! (updated from spyfly) -// @author FurTactics -// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg -// @match https://bildungsportal.sachsen.de/* -// @match https://idp2.tu-dresden.de/* -// @match https://jexam.inf.tu-dresden.de/* -// @match https://selma.tu-dresden.de/* -// @match https://exam.zih.tu-dresden.de/* -// @match https://exam2.zih.tu-dresden.de/* -// @match https://exam3.zih.tu-dresden.de/* -// @match https://qis.dez.tu-dresden.de/qisserver/* -// @match https://msx.tu-dresden.de/owa/auth/logon* -// @match https://lskonline.tu-dresden.de/lskonline/de/102.0.1* -// @match https://idp.tu-dresden.de/idp* -// @match https://tud-autologin.spyfly.xyz/configuration/ -// @match https://tex.zih.tu-dresden.de/* -// @match https://tud.uni-leipzig.de/moodle2/* -// @grant GM_getValue -// @grant GM_setValue +// @name TUD AutoLogin with generating the 2FA +// @namespace https://tud-autologin.spyfly.xyz/ +// @version 0.4.2 +// @description Stop wasting your time entering login credentials or pressing useless buttons! The script allows you to fully automate the entry of all login details, including two-factor authentication. (updated from spyfly) +// @description:de Verschwende keine Zeit mehr mit dem Eingeben von Anmeldedaten oder dem Drücken sinnloser Tasten! Mit dem Skript kann man die Eingabe aller Anmeldedaten, einschließlich der Zwei-Faktor-Authentifizierung, vollständig automatisieren. +// @description:ru Перестань тратить время на ввод данных или кликанье бесполезных кнопок! Скрипт позволяет полностью автоматизировать ввод всех данных, включая двухфакторную аутентификацию. +// @author FurTactics +// @icon https://upload.wikimedia.org/wikipedia/commons/a/a3/Logo_TU_Dresden_small.svg +// @match https://bildungsportal.sachsen.de/* +// @match https://idp2.tu-dresden.de/* +// @match https://jexam.inf.tu-dresden.de/* +// @match https://selma.tu-dresden.de/* +// @match https://exam.zih.tu-dresden.de/* +// @match https://exam2.zih.tu-dresden.de/* +// @match https://exam3.zih.tu-dresden.de/* +// @match https://qis.dez.tu-dresden.de/qisserver/* +// @match https://msx.tu-dresden.de/owa/auth/logon* +// @match https://lskonline.tu-dresden.de/lskonline/de/102.0.1* +// @match https://idp.tu-dresden.de/idp* +// @match https://tud-autologin.spyfly.xyz/configuration/ +// @match https://tex.zih.tu-dresden.de/* +// @match https://tud.uni-leipzig.de/moodle2/* +// @supportURL https://github.com/FurTactics/TUD-AutoLogin/issues +// @updateURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js +// @downloadURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js +// @grant GM_getValue +// @grant GM_setValue // ==/UserScript== 'use strict'; (function (G) { From f7ca7bff08bfe32a99e0349794f029b236244d3b Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 18 Jun 2024 13:30:54 +0200 Subject: [PATCH 06/10] Added commands to the Tampermonkey menu. Click statistics, dark theme in IDP and notifications. Now in the Tampermonkey menu you can view click statistics, as well as erase all saved data. A blackout has been added to the IDP website. The configuration site has also been slightly updated and beautiful notifications are now sent. --- script_only_for_Geeks.user.js | 192 +++++++++++++++++++++++++++++----- 1 file changed, 164 insertions(+), 28 deletions(-) diff --git a/script_only_for_Geeks.user.js b/script_only_for_Geeks.user.js index 514937d..910a47b 100644 --- a/script_only_for_Geeks.user.js +++ b/script_only_for_Geeks.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TUD AutoLogin with generating the 2FA // @namespace https://tud-autologin.spyfly.xyz/ -// @version 0.4.2 +// @version 0.4.3 // @description Stop wasting your time entering login credentials or pressing useless buttons! The script allows you to fully automate the entry of all login details, including two-factor authentication. (updated from spyfly) // @description:de Verschwende keine Zeit mehr mit dem Eingeben von Anmeldedaten oder dem Drücken sinnloser Tasten! Mit dem Skript kann man die Eingabe aller Anmeldedaten, einschließlich der Zwei-Faktor-Authentifizierung, vollständig automatisieren. // @description:ru Перестань тратить время на ввод данных или кликанье бесполезных кнопок! Скрипт позволяет полностью автоматизировать ввод всех данных, включая двухфакторную аутентификацию. @@ -21,11 +21,15 @@ // @match https://tud-autologin.spyfly.xyz/configuration/ // @match https://tex.zih.tu-dresden.de/* // @match https://tud.uni-leipzig.de/moodle2/* +// @match https://artemis-app.inf.tu-dresden.de/* // @supportURL https://github.com/FurTactics/TUD-AutoLogin/issues -// @updateURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js // @downloadURL https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js // @grant GM_getValue // @grant GM_setValue +// @grant GM_notification +// @grant GM_registerMenuCommand +// @grant GM_listValues +// @grant GM_deleteValue // ==/UserScript== 'use strict'; (function (G) { @@ -101,14 +105,85 @@ const isLskOnline = (window.location.host == "lskonline.tu-dresden.de"); const isTudIdp = (window.location.host == "idp.tu-dresden.de"); const isShareLatex = (window.location.host == "tex.zih.tu-dresden.de"); - const isMoodle = (window.location.host == "tud.uni-leipzig.de") + const isMoodle = (window.location.host == "tud.uni-leipzig.de"); const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); - const secretIsAvailable = tud.secret.length == 32; - let isSecretEntered; - var stats; + const secretIsAvailable = tud.secret.length > 5; + const stat = GM_getValue("stats"); + const menu_command_id_1 = GM_registerMenuCommand("You saved " + stat + " clicks 👍", function (event) { console.clear; console.log("You are awesome. " + stat + " clicks is more than " + Math.round(stat / 60) + " minutes. If you want, you can support me on GitHub https://github.com/FurTactics/TUD-AutoLogin/") }, { autoClose: false }); + const menu_command_id_2 = GM_registerMenuCommand("Delete all your saved data", function (event) { + const keys = GM_listValues(); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + GM_deleteValue(key); + } + GM_notification({ + text: "Your data has been successfully deleted\nClick to visit configuration website", + title: "TUD Autologin", + url: 'https://tud-autologin.spyfly.xyz/configuration/#configuration', + silent: true, + timeout: 10000, + onclick: (event) => { } + }); + }, { autoClose: true }); + if (isConfigPage) { + // Change all links to GitHub on a config page + var links = [document.querySelectorAll("a")[0], document.querySelectorAll("a")[2], document.querySelectorAll("a")[4]]; + for (var i = 0; i < links.length; i++) { + links[i].getAttribute("href"); + links[i].setAttribute("href", "https://github.com/FurTactics/TUD-AutoLogin/"); + links[i].addEventListener("click", function (event) { + event.preventDefault(); // Prevent the default action + window.open("https://github.com/FurTactics/TUD-AutoLogin/", "_blank"); // Open the link in a new tab + }); + } + + // Add GitHub link in footer + const footer = document.querySelector('.site-footer'); + if (footer) { + const spyflyLink = footer.querySelector('a[href="https://github.com/spyfly"]'); + if (spyflyLink) { + // Create and insert "and" text node + const andText = document.createTextNode(' and modified by '); + spyflyLink.parentNode.insertBefore(andText, spyflyLink.nextSibling); + // create link + const newLink = document.createElement('a'); + newLink.href = 'https://github.com/FurTactics/'; + newLink.textContent = 'FurTactics'; + // Insert the new link after the spyfly link + andText.parentNode.insertBefore(newLink, andText.nextSibling); + } + } + + // Test notification + const p_6 = document.querySelectorAll("p")[6]; + p_6.textContent = "Make sure, that you can receive notifications from browser."; + + const p_before_button = document.createElement('p'); + p_before_button.textContent = 'Here is a test.'; + p_6.parentNode.insertBefore(p_before_button, p_6.nextSibling); + + const button = document.createElement('button'); + button.textContent = 'Test'; + button.addEventListener('click', () => { + GM_notification({ + title: 'Test Notification', + text: 'This is a test notification from the script.\nYou will receive such notifications if an incorrect 2FA code is entered.', + timeout: 10000 + }); + }); + p_before_button.parentNode.insertBefore(button, p_before_button.nextSibling); + + const p_after_button = document.createElement('p'); + p_after_button.textContent = 'If you haven\'t received a notification, try turning off do not disturb mode.'; + button.parentNode.insertBefore(p_after_button, button.nextSibling); + + const p_congratulation = document.createElement('p'); + p_congratulation.textContent = 'Otherwise, welcome to the world of saved time 😉'; + p_after_button.parentNode.insertBefore(p_congratulation, p_after_button.nextSibling); + document.getElementById("notinstalled").remove(); document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; @@ -120,8 +195,9 @@ password: document.getElementById("password").value, secret: secretInput }); - GM_setValue("stats", null); - alert("Configuration updated!") + GM_setValue("stats", 0); + alert("Is your code from app: " + getOTP(secretInput) + "?\nIf not, please check your token"); + alert("Configuration updated!"); }); } else if (isOpalLoginPage || isTudExamLoginPage) { const mainPageLoginBtn = document.querySelector("button[name=shibLogin]"); @@ -134,6 +210,7 @@ if (contentPageLoginBtn) { contentPageLoginBtn.click(); + GM_setValue("stats", GM_getValue('stats') + 1); // Wait for Login Prompt to appear while (loginSelector == null) { loginSelector = document.querySelector("select[name$='wayfselection']"); @@ -152,8 +229,12 @@ // Press Login Button document.querySelector("button[name$='shibLogin']").click(); + GM_setValue("stats", GM_getValue('stats') + 1); } - } else if (isTudLoginPage || isTudIdp) { + } + else if (isTudLoginPage || isTudIdp) { + // Add dark theme + darkThemeIDP(); // We are on the TUD I2DP Page const hasLoginField = (document.getElementById("username") != undefined); const hasSecretField = (document.getElementById("fudis_otp_input") != undefined); @@ -163,22 +244,16 @@ document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; if (credentialsAvailable) { + document.getElementsByName("_eventId_proceed")[0].click(); GM_setValue("isSecretEntered", false); GM_setValue("stats", GM_getValue("stats") + 1); - document.getElementsByName("_eventId_proceed")[0].click(); } } if (hasSecretField) { - document.getElementById("fudis_otp_input").value = getOTP(tud.secret); - await sleep(100); - if (secretIsAvailable && !GM_getValue("isSecretEntered")) { - GM_setValue("isSecretEntered", true); // this is for insert the code only once - document.getElementsByName("_eventId_proceed")[0].click(); - } else { - alert("Something went wrong with your 2FA code"); - } + enterSecret(); } - } else if (isJExam) { + } + else if (isJExam) { // AutoLogin for JExam 5 if (window.location.pathname == "/") { window.location = "https://jexam.inf.tu-dresden.de/de.jexam.web.v5/" @@ -187,75 +262,136 @@ document.getElementById("username").value = tud.username; document.getElementById("password").value = tud.password; if (credentialsAvailable) { - GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); + GM_setValue("stats", GM_getValue('stats') + 1); } } - } else if (isSelma) { + } + else if (isSelma) { // AutoLogin for selma if (document.getElementById("field_user") != undefined) { document.getElementById("field_user").value = tud.username; document.getElementById("field_pass").value = tud.password; if (credentialsAvailable) { document.getElementById("logIn_btn").click(); + GM_setValue("stats", GM_getValue('stats') + 1); } } - } else if (isMoodle) { + } + else if (isMoodle) { // AutoLogin for Moodle if (window.location.pathname == "/moodle2/login/index.php") { // Check if we are on the login page document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/auth/shibboleth/index.php"]').click(); + GM_setValue("stats", GM_getValue('stats') + 1); } else { //Go to the login page if we need to login const loginBtn = document.querySelector('[href="https://tud.uni-leipzig.de/moodle2/login/index.php"]'); if (loginBtn) { loginBtn.click(); + GM_setValue("stats", GM_getValue('stats') + 1); } } - } else if (isQisServer) { + } + else if (isQisServer) { //AutoLogin for QISServer if (document.getElementsByClassName("loginuser").length >= 1) { document.getElementsByClassName("loginuser")[0].value = tud.username; document.getElementsByClassName("loginpass")[0].value = tud.password; if (credentialsAvailable) { - GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("submit")[0].click(); + GM_setValue("stats", GM_getValue('stats') + 1); } } - } else if (isOWA) { + } + else if (isOWA) { //AutoLogin for OWA document.getElementById('username').value = tud.username; document.getElementById('password').value = tud.password; if (credentialsAvailable) { - GM_setValue("stats", GM_getValue('stats') + 1); document.getElementsByClassName("signinbutton")[0].click(); + GM_setValue("stats", GM_getValue('stats') + 1); } - } else if (isLskOnline) { + } + else if (isLskOnline) { //AutoLogin for LSKOnline document.getElementsByName('j_username')[0].value = tud.username; document.getElementsByName('j_password')[0].value = tud.password; if (credentialsAvailable) { document.getElementsByName('submit')[0].click(); + GM_setValue("stats", GM_getValue('stats') + 1); } - } else if (isShareLatex) { + } + else if (isShareLatex) { //AutoLogin for ShareLaTeX if (window.location.pathname == "/saml/login") { // Check if we are on the login page document.querySelector('[href="/saml/login/go"]').click(); + GM_setValue("stats", GM_getValue('stats') + 1); } else { //Go to the login page if we need to login const loginBtn = document.querySelector('[href="/login"]'); if (loginBtn) { loginBtn.click(); + GM_setValue("stats", GM_getValue('stats') + 1); } } } + + function enterSecret(pinInput) { + document.getElementById("fudis_otp_input").focus(); + var otp_input = getOTP(tud.secret); + sleep(200); + if (!GM_getValue("isSecretEntered")) { + document.getElementById("fudis_otp_input").value = otp_input; + GM_setValue("isSecretEntered", true); // this is for insert the code only once + //document.getElementsByName("_eventId_proceed")[0].click(); + GM_setValue("stats", GM_getValue('stats') + 1); + } else { + const clicked = showNotification(otp_input); + } + } + + function showNotification(myCode) { + GM_notification({ + text: "Your 2FA code was incorrect \nClick to show a new code, check it against the application or close this notification", + title: "TUD Autologin", + url: 'https:/google.com/', + silent: true, + timeout: 10000, + onclick: (event) => { + // The userscript is still running, so don't open example.com + event.preventDefault(); + GM_notification({ + title: "TUD Autologin", + text: "New Code is: " + myCode + "\nClick to enter the new code", + silent: true, + onclick: (event) => { + document.getElementById("fudis_otp_input").value = myCode; + } + }); + } + }); + } + })(); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +function darkThemeIDP() { + const background = document.querySelector('body'); + const box = document.querySelector('.box'); + const inputs = document.querySelectorAll('input'); + background.style.backgroundColor = '#232323'; + box.style.backgroundColor = '#696969'; + inputs.forEach(input => { + input.style.backgroundColor = '#696969'; + input.style.color = '#fff'; + }); +} + // TOTP Code modified from https://github.com/tkooda/totp.info and fixed base32tohex function without check the correctness the secret set function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); } From 55da3365df68ae7d7b721f9e8c4ae0b2abb800ab Mon Sep 17 00:00:00 2001 From: FurTactics Date: Tue, 18 Jun 2024 14:15:23 +0200 Subject: [PATCH 07/10] updated description of new features and added FAQ --- README.md | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f178727..5a54a25 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is a browser script, that performs an automatic login for the following TU - [LSKOnline](https://lskonline.tu-dresden.de) - [ShareLaTeX](https://tex.zih.tu-dresden.de) - [Moodle](https://tud.uni-leipzig.de/moodle2/) -- and any other service with the TUD Login Page +- and any other service with the TUD Login Page (maybe something new comes soon). ## Installation for use with an application or for manual input 1. Install [Tampermonkey](https://www.tampermonkey.net) for your browser @@ -20,14 +20,15 @@ This is a browser script, that performs an automatic login for the following TU 5. (optional) You can use [2FAS Browser Extension](https://2fas.com/browser-extension/) to automate the entry, but confirmation on your phone will be required each time 6. Enjoy :partying_face: -## Installation for those who forever want to forget about the existence of login pages (nothing manual) +## Installation for those who want to forever forget about the existence of login pages (nothing manual) 1. Install [Tampermonkey](https://www.tampermonkey.net) for your browser 2. Download the [TUD-AutoLogin UserScript](https://raw.githubusercontent.com/FurTactics/TUD-AutoLogin/master/script_only_for_Geeks.user.js) 3. Click the install button to complete the installation 4. Visit the [configuration page](https://tud-autologin.spyfly.xyz/configuration) after installing the script, to add your login credentials -> You will need a 32-digit secret code (Geheimschlüssel), which can be found in the 2FAS app or by decoding the QR code +> You need a 32-digit secret code (Geheimschlüssel), which can be found in the [2FAS](https://2fas.com/) app or by decoding the QR code. 5. Enjoy more :partying_face: +**else:** check [Known Issues](https://github.com/FurTactics/TUD-AutoLogin/tree/master?tab=readme-ov-file#known-issues-and-faq) or open a [new issue](https://github.com/FurTactics/TUD-AutoLogin/issues). ## Compatibility This script is compatible with any browser capable of supporting [Tampermonkey](https://www.tampermonkey.net) or [Greasemonkey (Firefox only)](https://addons.mozilla.org/de/firefox/addon/greasemonkey/) @@ -43,5 +44,32 @@ Visit your favorite TUD websites and enjoy automatic login. On iOS/iPadOS you will have to run the Custom Shortcut via the Share Menu in Safari. -## Future development and issues +## Future Development and Issues If you have improvement ideas or encounter issues with the script don't hesitate to open a [new issue](https://github.com/FurTactics/TUD-AutoLogin/issues). This could be a request for adding more supported services or reporting a bug. + + +## Known Issues and FAQ +
+No notifications +

+You don't see notifications from the configuration site after clicking the test button. +

+Windows +

+Try turning off do not disturb mode and allowing browser notifications. +

+Mac OS +

+(unclear) Try turning on notifications in your browser settings (System Preferences > Notifications > Chrome or another Browswer) +check screenshot. +

+
+
+Where is my 32-digit secret code? +

The best and most secure way to find a token is to look it up in the 2FAS app. Long hold the TU Dresden card, then edit. You need a 32-digit key that needs to be copied. +

+
+
+If I don't use 2FAS? +

If you do not use 2FAS, then all similar applications should have a function for viewing the secret code. If not, you can install the 2FAS app and export your TU Dresden entry there.

+
\ No newline at end of file From 7ca84dbea16f85c19d398d05feb86656c4ae4d5a Mon Sep 17 00:00:00 2001 From: Danila <70521655+FurTactics@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:01:56 +0200 Subject: [PATCH 08/10] Uncomment line to press "submit" button --- script_only_for_Geeks.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script_only_for_Geeks.user.js b/script_only_for_Geeks.user.js index 910a47b..ca62efd 100644 --- a/script_only_for_Geeks.user.js +++ b/script_only_for_Geeks.user.js @@ -345,7 +345,7 @@ if (!GM_getValue("isSecretEntered")) { document.getElementById("fudis_otp_input").value = otp_input; GM_setValue("isSecretEntered", true); // this is for insert the code only once - //document.getElementsByName("_eventId_proceed")[0].click(); + document.getElementsByName("_eventId_proceed")[0].click(); GM_setValue("stats", GM_getValue('stats') + 1); } else { const clicked = showNotification(otp_input); @@ -448,4 +448,4 @@ function getOTP(secret) { } else { return undefined; } -}; \ No newline at end of file +}; From fff0e93f7afafbe37c9e8e3beefd287256201f75 Mon Sep 17 00:00:00 2001 From: Danila <70521655+FurTactics@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:35:55 +0200 Subject: [PATCH 09/10] Update because of renovation of IDP login page Dark theme was changed --- script_only_for_Geeks.user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script_only_for_Geeks.user.js b/script_only_for_Geeks.user.js index ca62efd..1511d5a 100644 --- a/script_only_for_Geeks.user.js +++ b/script_only_for_Geeks.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TUD AutoLogin with generating the 2FA // @namespace https://tud-autologin.spyfly.xyz/ -// @version 0.4.3 +// @version 0.4.4 // @description Stop wasting your time entering login credentials or pressing useless buttons! The script allows you to fully automate the entry of all login details, including two-factor authentication. (updated from spyfly) // @description:de Verschwende keine Zeit mehr mit dem Eingeben von Anmeldedaten oder dem Drücken sinnloser Tasten! Mit dem Skript kann man die Eingabe aller Anmeldedaten, einschließlich der Zwei-Faktor-Authentifizierung, vollständig automatisieren. // @description:ru Перестань тратить время на ввод данных или кликанье бесполезных кнопок! Скрипт позволяет полностью автоматизировать ввод всех данных, включая двухфакторную аутентификацию. @@ -382,10 +382,10 @@ function sleep(ms) { function darkThemeIDP() { const background = document.querySelector('body'); - const box = document.querySelector('.box'); + const mainBox = document.querySelector('main'); const inputs = document.querySelectorAll('input'); background.style.backgroundColor = '#232323'; - box.style.backgroundColor = '#696969'; + mainBox.style.backgroundColor = '#696969'; inputs.forEach(input => { input.style.backgroundColor = '#696969'; input.style.color = '#fff'; From 17a16777e7136f9b9c58f9a4b60274aca63c4151 Mon Sep 17 00:00:00 2001 From: Danila <70521655+FurTactics@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:09:19 +0200 Subject: [PATCH 10/10] Handle undefined values of credentials --- script_only_for_Geeks.user.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/script_only_for_Geeks.user.js b/script_only_for_Geeks.user.js index 1511d5a..2c19292 100644 --- a/script_only_for_Geeks.user.js +++ b/script_only_for_Geeks.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TUD AutoLogin with generating the 2FA // @namespace https://tud-autologin.spyfly.xyz/ -// @version 0.4.4 +// @version 0.4.5 // @description Stop wasting your time entering login credentials or pressing useless buttons! The script allows you to fully automate the entry of all login details, including two-factor authentication. (updated from spyfly) // @description:de Verschwende keine Zeit mehr mit dem Eingeben von Anmeldedaten oder dem Drücken sinnloser Tasten! Mit dem Skript kann man die Eingabe aller Anmeldedaten, einschließlich der Zwei-Faktor-Authentifizierung, vollständig automatisieren. // @description:ru Перестань тратить время на ввод данных или кликанье бесполезных кнопок! Скрипт позволяет полностью автоматизировать ввод всех данных, включая двухфакторную аутентификацию. @@ -108,8 +108,8 @@ const isMoodle = (window.location.host == "tud.uni-leipzig.de"); const credentialsAvailable = (tud.username.length > 0 && tud.password.length > 0); - const secretIsAvailable = tud.secret.length > 5; - const stat = GM_getValue("stats"); + const secretIsAvailable = tud.secret.length == 32; + const stat = GM_getValue("stats") == undefined ? 0 : GM_getValue("stats"); const menu_command_id_1 = GM_registerMenuCommand("You saved " + stat + " clicks 👍", function (event) { console.clear; console.log("You are awesome. " + stat + " clicks is more than " + Math.round(stat / 60) + " minutes. If you want, you can support me on GitHub https://github.com/FurTactics/TUD-AutoLogin/") }, { autoClose: false }); const menu_command_id_2 = GM_registerMenuCommand("Delete all your saved data", function (event) { const keys = GM_listValues(); @@ -249,7 +249,7 @@ GM_setValue("stats", GM_getValue("stats") + 1); } } - if (hasSecretField) { + if (hasSecretField && secretIsAvailable) { enterSecret(); } }