Created
December 8, 2020 17:07
-
-
Save pozil/252d4b04ec7308723a88ed4632b24c2c to your computer and use it in GitHub Desktop.
GitHub Auto SSO script for Tampermonkey
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name GitHub Auto SSO | |
// @namespace http://pozil.github.io | |
// @version 1.0 | |
// @description Adds a button that lets you automatically signs-in to all orgs that requires SSO login (instead of clicking 3x per org) | |
// @author pozil | |
// @match https://github.com/* | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const GITHUB_HOME_URL = 'https://github.com/'; | |
const SSO_URL_REGEX = /https:\/\/github\.com\/orgs\/([a-z\-]+)\/sso/; | |
const SAML_INITIATE_URL_REGEX = /https:\/\/github\.com\/orgs\/[a-z\-]+\/saml\/initiate/; | |
const COOKIE_DURATION = 180000; // 3 minutes | |
const COOKIE_KEY = 'gh-auto-sso'; | |
var context = loadRuntimeContext(); | |
const url = window.location.href; | |
if (context.isRunning) { // SSO started | |
if (url === GITHUB_HOME_URL) { // Homepage | |
continueSsoFromHome(); | |
} else if (SSO_URL_REGEX.test(url)) { // SSO start confirmation page | |
retry(continueFromSsoPage, 10); | |
} else if (SAML_INITIATE_URL_REGEX.test(url)) { // SSO end confirmation page | |
retry(skipSsoOnSamlError, 10); | |
} | |
} else if (url === GITHUB_HOME_URL) { // SSO hasn't started yet and we're on home page | |
retry(addGlobalSsoButton, 10); | |
} | |
/** | |
* Adds a button to start the multi org SSO script | |
*/ | |
function addGlobalSsoButton() { | |
// Get orgs that require SSO | |
const ssoOrgs = getSsoOrgs(); | |
if (ssoOrgs.length === 0) { | |
return false; | |
} | |
context.ssoOrgs = ssoOrgs; | |
saveRuntimeContext(context); | |
// Add sign in button to header | |
const button = document.createElement('button'); | |
button.type = 'button'; | |
button.innerHTML = `Sign in ${ssoOrgs.length} organizations`; | |
button.className = 'btn btn-sm btn-primary text-white ml-2'; | |
button.onclick = startSsoFromHome; | |
const header = document.querySelector('.news>h2:first-of-type'); | |
header.appendChild(button); | |
return true; | |
} | |
/** | |
* Remove global SSO button and start multi org SSO process | |
*/ | |
function startSsoFromHome(event) { | |
event.target.remove(); | |
context.isRunning = true; | |
saveRuntimeContext(context); | |
continueSsoFromHome(); | |
} | |
/** | |
* Resume/start SSO process from the GitHub home page | |
*/ | |
function continueSsoFromHome() { | |
if (context.isSigningIn) { | |
// SSO just succeeded | |
const org = context.ssoOrgs.shift(); | |
context.ssoSuccessOrgs.push(org); | |
context.isSigningIn = false; | |
saveRuntimeContext(context); | |
} | |
// Look at the next org | |
if (context.ssoOrgs.length === 0) { | |
// Done with all orgs | |
context.isRunning = false; | |
saveRuntimeContext(context); | |
} else { | |
// Sign in next org | |
const org = context.ssoOrgs[0]; | |
window.location = `https://github.com/orgs/${org}/sso?return_to=%2F`; | |
} | |
return true; | |
} | |
/** | |
* Automatically click on Continue button in SSO transition page | |
*/ | |
function continueFromSsoPage() { | |
const continueButton = document.querySelector('button[type=submit]'); | |
if (continueButton) { | |
context.isSigningIn = true; | |
saveRuntimeContext(context); | |
continueButton.click(); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Skip org sign in if a warning is displayed (eg: unauthorized IP) | |
*/ | |
function skipSsoOnSamlError() { | |
const warning = document.querySelector('#js-pjax-container svg.octicon-alert'); | |
if (warning) { | |
// SSO failed | |
const org = context.ssoOrgs.shift(); | |
context.ssoFailedOrgs.push(org); | |
context.isSigningIn = false; | |
saveRuntimeContext(context); | |
window.location = GITHUB_HOME_URL; | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Runs a function a number of times till it reports success. | |
* Each attempt is separated by a small break. | |
* @param callFunction {function} pointer to a function that will be retried. Function must return a boolean that indicates success. | |
* @param retries {number} number of retries left till we abort | |
*/ | |
function retry(callFunction, retries) { | |
const success = callFunction(); | |
if (!success) { | |
const newRetries = retries -1; | |
if (newRetries === 0) { | |
log('Giving up after several retries'); | |
} else { | |
setTimeout(() => { | |
retry(callFunction, newRetries); | |
}, 250); | |
} | |
} | |
} | |
/** | |
* Parse the list of orgs that require SSO | |
* @returns [string] list of org names that require SSO | |
*/ | |
function getSsoOrgs() { | |
const ssoOrgs = []; | |
const links = document.querySelectorAll('.js-recent-activity-container a'); | |
links.forEach(link => { | |
if (link.textContent === 'Single sign-on') { | |
const url = link.href; | |
const orgNameMatches = url.match(SSO_URL_REGEX); | |
ssoOrgs.push(orgNameMatches[1]); | |
} | |
}); | |
return ssoOrgs; | |
} | |
/** | |
* Save script runtime context to a cookie | |
*/ | |
function saveRuntimeContext(context) { | |
const d = new Date(); | |
d.setTime(d.getTime() + COOKIE_DURATION); | |
document.cookie = `${COOKIE_KEY}=${escape( | |
JSON.stringify(context) | |
)}; expires=${d.toUTCString()}; path=/`; | |
}; | |
/** | |
* Load script runtime context from cookie | |
*/ | |
function loadRuntimeContext() { | |
const name = `${COOKIE_KEY}=`; | |
const ca = document.cookie.split(';'); | |
for (let i = 0; i < ca.length; i++) { | |
let c = ca[i]; | |
while (c.charAt(0) === ' ') { | |
c = c.substring(1); | |
} | |
if (c.indexOf(name) === 0) { | |
return JSON.parse(unescape(c.substring(name.length, c.length))); | |
} | |
} | |
return { | |
isRunning: false, | |
isSigningIn: false, | |
ssoOrgs: [], | |
ssoSuccessOrgs: [], | |
ssoFailedOrgs: [] | |
}; | |
}; | |
/** | |
* Logs a message to the console | |
* @param message {String} | |
*/ | |
function log(message) { | |
console.log(`GitHub auto SSO: ${message}`); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment