From 42079b9207f8e9459d4e267a41bbe502dcbe7a68 Mon Sep 17 00:00:00 2001 From: platinorum <69057107+platinorum@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:29:57 +0100 Subject: [PATCH 1/2] Fix main login and add Denki Quiz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated match patterns, login execution and added auto-login for Denki-Schlüsselbegriffquiz --- tuwien-autologin.user.js | 43 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/tuwien-autologin.user.js b/tuwien-autologin.user.js index 05e424a..c2f204c 100644 --- a/tuwien-autologin.user.js +++ b/tuwien-autologin.user.js @@ -4,12 +4,14 @@ // @include https://tiss.tuwien.ac.at/* // @include https://tuwel.tuwien.ac.at/* // @include https://oc-presentation.ltcc.tuwien.ac.at/* -// @match https://idp.zid.tuwien.ac.at/simplesaml/module.php/core/loginuserpass.php +// @match https://idp.zid.tuwien.ac.at/simplesaml/module.php/core/loginuserpass* // @match https://toss.fsinf.at/ +// @match https://memory.iguw.tuwien.ac.at/login // @grant none -// @version 1.7 +// @version 1.8 // @downloadURL https://fsinf.at/userscripts/tuwien-autologin.user.js // @updateURL https://fsinf.at/userscripts/tuwien-autologin.user.js +// @description Note: you need to have password auto fill-in enabled in your browser // ==/UserScript== function tuwelRefreshSession() { @@ -32,9 +34,16 @@ async function openCastAutoLogin(){ switch(location.host){ case 'idp.zid.tuwien.ac.at': - if (document.querySelector('input[name="password"]').value) - document.querySelector('input[name="password"]').form.submit() - break; + setTimeout(function() { + if (document.querySelector('input[name="password"]').value) { + document.querySelector('input[name="password"]').form.submit() + } else { + console.warn("Autologin-Script: PW field is empty. Is auto-fill enabled in browser?"); + } + }, 500); + + break; + case 'tiss.tuwien.ac.at': if (document.getElementsByClassName("loading").length > 0) { @@ -49,8 +58,13 @@ switch(location.host){ break; case 'tuwel.tuwien.ac.at': - if (location.pathname == "/theme/university_boost/login/index.php") { - document.querySelector("a[title='TU Wien Login']").click(); + if (location.pathname == "/login/index.php") { + document.querySelectorAll('a').forEach(a => { + if (a.textContent.trim() === 'TU Wien Login') { + a.click(); + } + }); + } else { tuwelRefreshSession(); } @@ -69,4 +83,17 @@ switch(location.host){ case 'toss.fsinf.at': hasTES = true; break; -} + + // Denki Schlüsselbegriff-Quiz + case 'memory.iguw.tuwien.ac.at': + if (location.pathname == "/login") { + setTimeout(function() { + document.querySelectorAll('button').forEach(button => { + if (button.textContent.trim() === 'Mit TU Wien SSO anmelden') { + button.click(); + } + }); + }, 1000); + } + break; + } From ff7686e3d6b400cb374444a032b700a628bdb8a2 Mon Sep 17 00:00:00 2001 From: platinorum <69057107+platinorum@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:23:18 +0100 Subject: [PATCH 2/2] Update TU Wien Autologin script for TOTP support --- tuwien-autologin.user.js | 87 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/tuwien-autologin.user.js b/tuwien-autologin.user.js index c2f204c..52e1e51 100644 --- a/tuwien-autologin.user.js +++ b/tuwien-autologin.user.js @@ -1,5 +1,5 @@ // ==UserScript== -// @name Autologin for TU Wien SSO, TISS, TUWEL and OpenCast +// @name TU Wien Autologin // @namespace https://vowi.fsinf.at/ // @include https://tiss.tuwien.ac.at/* // @include https://tuwel.tuwien.ac.at/* @@ -7,13 +7,35 @@ // @match https://idp.zid.tuwien.ac.at/simplesaml/module.php/core/loginuserpass* // @match https://toss.fsinf.at/ // @match https://memory.iguw.tuwien.ac.at/login -// @grant none -// @version 1.8 +// @grant GM_getValue +// @grant GM_setValue +// @version 1.9 // @downloadURL https://fsinf.at/userscripts/tuwien-autologin.user.js // @updateURL https://fsinf.at/userscripts/tuwien-autologin.user.js // @description Note: you need to have password auto fill-in enabled in your browser // ==/UserScript== + +(async () => { + + const DO_AUTO_TOTP = true; + + const TOTP_SECRET = GM_getValue('tuwien_totp_secret', null); + + // promt user to enter topt secret if not set + if (DO_AUTO_TOTP && !TOTP_SECRET) { + const secret = prompt('[AutoLogin] Enter your TOTP Base32 secret. Then reload.'); + if (secret?.trim()) { + GM_setValue('tuwien_totp_secret', secret.trim()); + console.log('[AutoLogin] Secret saved! Reload the page.'); + } else { + console.warn('[AutoLogin] No secret entered, aborting.'); + } + return; + } + + + function tuwelRefreshSession() { setTimeout(function() { fetch("https://tuwel.tuwien.ac.at/my/", {method: "HEAD"}); @@ -34,9 +56,19 @@ async function openCastAutoLogin(){ switch(location.host){ case 'idp.zid.tuwien.ac.at': - setTimeout(function() { + setTimeout(async function() { if (document.querySelector('input[name="password"]').value) { + + if (DO_AUTO_TOTP) { + var totp_field = document.querySelector('input#totp'); + + const totp_code = await generateTOTP(TOTP_SECRET); + + totp_field.value = totp_code; + } + document.querySelector('input[name="password"]').form.submit() + } else { console.warn("Autologin-Script: PW field is empty. Is auto-fill enabled in browser?"); } @@ -97,3 +129,50 @@ switch(location.host){ } break; } + + +async function generateTOTP(base32Secret) { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + const clean = base32Secret.toUpperCase().replace(/[\s=]+/g, ''); + + // Decode Base32 to raw bytes + let bits = ''; + for (const char of clean) { + const val = alphabet.indexOf(char); + if (val === -1) throw new Error('Invalid Base32 character: ' + char); + bits += val.toString(2).padStart(5, '0'); + } + const keyBytes = new Uint8Array( + Array.from({ length: Math.floor(bits.length / 8) }, (_, i) => + parseInt(bits.slice(i * 8, i * 8 + 8), 2) + ) + ); + + // Counter = floor(unixTime / 30) + const counter = Math.floor(Date.now() / 1000 / 30); + const counterBytes = new Uint8Array(8); + let tmp = counter; + for (let i = 7; i >= 0; i--) { counterBytes[i] = tmp & 0xff; tmp = Math.floor(tmp / 256); } + + // HMAC-SHA1 + const cryptoKey = await crypto.subtle.importKey( + 'raw', keyBytes, + { name: 'HMAC', hash: 'SHA-1' }, + false, ['sign'] + ); + const hmac = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, counterBytes)); + + // Dynamic truncation + const offset = hmac[hmac.length - 1] & 0x0f; + const code = ( + ((hmac[offset] & 0x7f) << 24) | + ((hmac[offset + 1] & 0xff) << 16) | + ((hmac[offset + 2] & 0xff) << 8) | + (hmac[offset + 3] & 0xff) + ) % 1_000_000; + + return code.toString().padStart(6, '0'); +} + + +})();