Skip to content

Instantly share code, notes, and snippets.

@liujed
Forked from pozil/github-auto-sso.user.js
Last active January 10, 2024 19:02
Show Gist options
  • Save liujed/5ba4449b22dc481824a8aa13978a9de9 to your computer and use it in GitHub Desktop.
Save liujed/5ba4449b22dc481824a8aa13978a9de9 to your computer and use it in GitHub Desktop.
GitHub Auto SSO script for Tampermonkey
// ==UserScript==
// @name Auto-authenticate to organizations on Github
// @namespace https://jedliu.net
// @version 1.0.1
// @description Periodically authenticate to organizations on Github.
// @author Jed Liu
// @match https://github.com/*
// ==/UserScript==
// Adapted from https://gist.github.com/pozil/252d4b04ec7308723a88ed4632b24c2c.
(
function() {
'use strict';
// == BEGIN CONFIGURATION =================================================
// The main page on which this script runs. Must be in the github.com
// domain.
const GITHUB_TRIGGER_URL = 'https://github.com/pulls';
// The Github organizations requiring SSO.
const SSO_ORGS = [
'postman-eng',
'postmanlabs',
];
// How frequently to do SSO, in milliseconds.
const AUTH_INTERVAL_ms = 1000 * 60 * 60 * 23;
// == END CONFIGURATION ===================================================
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_KEY = 'pm-auto-gh-sso';
const url = window.location.href;
var context = loadRuntimeContext();
if (context.isActive) {
if (url === GITHUB_TRIGGER_URL) {
continueSsoFromTriggerPage();
} 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_TRIGGER_URL) {
retry(scheduleNextSsoAttempts, 10);
}
// Resume/start SSO process from the trigger page.
function continueSsoFromTriggerPage() {
if (context.isSigningIn) {
// SSO just succeeded.
context.ssoSuccessOrgs.push(SSO_ORGS[context.orgIdx]);
context.isSigningIn = false;
context.orgIdx++;
saveRuntimeContext(context);
}
if (SSO_ORGS.length === context.orgIdx) {
// Done with all organizations.
context.isActive = false;
context.orgIdx = 0;
context.nextAuthTime = Date.now() + AUTH_INTERVAL_ms;
saveRuntimeContext(context);
retry(scheduleNextSsoAttempts, 10);
return true;
}
// Sign into the next organization.
const org = SSO_ORGS[context.orgIdx];
const returnUrl = encodeURIComponent(GITHUB_TRIGGER_URL);
window.location =
`https://github.com/orgs/${org}/sso?return_to=${returnUrl}`;
return true;
}
// Automatically clicks on the Continue button in an 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;
}
// Skips org sign-in if a warning is displayed (e.g., unauthorized IP
// address).
function skipSsoOnSamlError() {
const warning = document.querySelector('#js-pjax-container svg.octicon-alert');
if (warning) {
// SSO failed.
const org = SSO_ORGS[context.orgIdx];
context.ssoFailedOrgs.push(org);
context.isSigningIn = false;
saveRuntimeContext(context);
window.location = GITHUB_TRIGGER_URL;
return true;
}
return false;
}
// Schedules the next round of SSO attempts according to
// context.nextAuthTime.
function scheduleNextSsoAttempts() {
var delay = context.nextAuthTime - Date.now();
if (delay < 0) {
delay = 0;
}
window.setTimeout(() => {
// Start authenticating.
context.isActive = true;
saveRuntimeContext(context);
retry(continueSsoFromTriggerPage, 10);
}, delay);
return true;
}
// Runs a function a certain number of times until it reports success. Each
// attempt is separated by a small break.
//
// @param f {function} the function to be retried. Must return a boolean
// that indicates success.
// @param retries {number} the number of retries left before we abort.
function retry(f, retries) {
const success = f();
if (success) return;
retries--;
if (retries === 0) {
log('Giving up after several retries');
return;
}
setTimeout(() => {
retry(f, retries);
}, 250);
}
function saveRuntimeContext() {
document.cookie =
`${COOKIE_KEY}=${escape(JSON.stringify(context))}; path=/`;
}
function loadRuntimeContext() {
const name = `${COOKIE_KEY}=`;
const cookieArray = document.cookie.split(';').filter(
function(cookie) {
return cookie.trim().indexOf(name) === 0;
}
);
if (cookieArray.length) {
return JSON.parse(
unescape(cookieArray[0].trim().substring(name.length))
);
}
return {
// Scheduled time for next round of SSO attempts.
nextAuthTime: 0,
// Whether we are actively authenticating to Github organizations.
isActive: false,
// If isActive, this points to the entry in SSO_ORGS to which we
// actively authenticating. Otherwise, points to the entry in SSO_ORGS
// to which we will authenticate next.
orgIdx: 0,
// Whether we have started authentication against a Github
// organization.
isSigningIn: false,
// List of Github organizations for which SSO succeeded.
ssoSuccessOrgs: [],
// List of Github organizations for which SSO failed.
ssoFailedOrgs: [],
};
}
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