Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move common Auth3000 functions to own file #46

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ <h3>Go Check Your Email</h3>
</form>

<script src="./js/env.js"></script>
<script src="./js/auth3000.js"></script>
<script src="./js/main.js"></script>
<script src="./js/magic-link.js"></script>
</body>
Expand Down
117 changes: 117 additions & 0 deletions public/js/auth3000.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
var Auth3000 = {};

(async function () {
Auth3000._querystringify = function (options) {
return Object.keys(options)
.filter(function (key) {
return (
"undefined" !== typeof options[key] &&
null !== options[key] &&
"" !== options[key]
);
})
.map(function (key) {
// the values must be URI-encoded (the %20s and such)
return key + "=" + encodeURIComponent(options[key]);
})
.join("&");
};

Auth3000.parseQuerystring = function (querystring) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new URLSearchParams(querystring)

var query = {};
querystring.split("&").forEach(function (pairstring) {
var pair = pairstring.split("=");
var key = pair[0];
var value = decodeURIComponent(pair[1]);

query[key] = value;
});
return query;
};

Auth3000.parseJwt = async function (jwt) {
var parts = jwt.split(".");
var jws = {
protected: parts[0],
payload: parts[1],
signature: parts[2],
};
jws.header = Auth3000._urlBase64ToJson(jws.protected);
jws.claims = Auth3000._urlBase64ToJson(jws.payload);
return jws;
};

Auth3000.generateOidcUrl = function (
oidcBaseUrl,
client_id,
redirect_uri,
scope,
login_hint
) {
// a secure-enough random state value
// (all modern browsers use crypto random Math.random, not that it much matters for a client-side state cache)
var rnd = Math.random().toString();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var rnd = window.crypto.randomUUID();

// transform from 0.1234... to hexidecimal
var state = parseInt(rnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
// response_type=id_token requires a nonce (one-time use random value)
// response_type=token (access token) does not
var nonceRnd = Math.random().toString();
var nonce = parseInt(nonceRnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
var options = { state, client_id, redirect_uri, scope, login_hint, nonce };
// transform from object to 'param1=escaped1&param2=escaped2...'
var params = Auth3000._querystringify(options);
return oidcBaseUrl + "?response_type=id_token&access_type=online&" + params;
};

Auth3000.generateOauth2Url = function (
authorize_url,
client_id,
redirect_uri,
scopes,
login_hint
) {
// <!-- redirect_uri=encodeURIComponent("https://beyondcode.duckdns.org/api/webhooks/oauth2/github") -->
// <!-- scope=encodeURIComponent("read:user%20user:email") -->
// <a href="https://github.com/login/oauth/authorize?client_id=0b7cdfa2fde2f019a3b5&redirect_uri=https%3A%2F%2Fbeyondcode.duckdns.org%2Fapi%2Fwebhooks%2Foauth2%2Fgithub&scope=read:user%20user:email&state=1234567890&allow_signup=true&login=">

// a secure-enough random state value
// (all modern browsers use crypto random Math.random, not that it much matters for a client-side state cache)
var rnd = Math.random().toString();
// transform from 0.1234... to hexidecimal
var state = parseInt(rnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
var login = login_hint;
var scope = scopes.join(" ");
var options = {
state,
client_id,
redirect_uri,
scope,
login: login_hint,
};
var params = Auth3000._querystringify(options);
return authorize_url + "?allow_signup=true&" + params;
};

// because JavaScript's Base64 implementation isn't URL-safe
Auth3000._urlBase64ToBase64 = function (str) {
var r = str % 4;
if (2 === r) {
str += "==";
} else if (3 === r) {
str += "=";
}
return str.replace(/-/g, "+").replace(/_/g, "/");
};

Auth3000._urlBase64ToJson = function (u64) {
var b64 = Auth3000._urlBase64ToBase64(u64);
var str = atob(b64);
return JSON.parse(str);
};
})();
122 changes: 4 additions & 118 deletions public/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,96 +26,6 @@
throw err;
}

function querystringify(options) {
return Object.keys(options)
.filter(function (key) {
return (
"undefined" !== typeof options[key] &&
null !== options[key] &&
"" !== options[key]
);
})
.map(function (key) {
// the values must be URI-encoded (the %20s and such)
return key + "=" + encodeURIComponent(options[key]);
})
.join("&");
}

function generateOidcUrl(
oidcBaseUrl,
client_id,
redirect_uri,
scope,
login_hint
) {
// a secure-enough random state value
// (all modern browsers use crypto random Math.random, not that it much matters for a client-side state cache)
var rnd = Math.random().toString();
// transform from 0.1234... to hexidecimal
var state = parseInt(rnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
// response_type=id_token requires a nonce (one-time use random value)
// response_type=token (access token) does not
var nonceRnd = Math.random().toString();
var nonce = parseInt(nonceRnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
var options = { state, client_id, redirect_uri, scope, login_hint, nonce };
// transform from object to 'param1=escaped1&param2=escaped2...'
var params = querystringify(options);
return oidcBaseUrl + "?response_type=id_token&access_type=online&" + params;
}

function generateOauth2Url(
authorize_url,
client_id,
redirect_uri,
scopes,
login_hint
) {
// <!-- redirect_uri=encodeURIComponent("https://beyondcode.duckdns.org/api/webhooks/oauth2/github") -->
// <!-- scope=encodeURIComponent("read:user%20user:email") -->
// <a href="https://github.com/login/oauth/authorize?client_id=0b7cdfa2fde2f019a3b5&redirect_uri=https%3A%2F%2Fbeyondcode.duckdns.org%2Fapi%2Fwebhooks%2Foauth2%2Fgithub&scope=read:user%20user:email&state=1234567890&allow_signup=true&login=">

// a secure-enough random state value
// (all modern browsers use crypto random Math.random, not that it much matters for a client-side state cache)
var rnd = Math.random().toString();
// transform from 0.1234... to hexidecimal
var state = parseInt(rnd.slice(2).padEnd(16, "0"), 10)
.toString(16)
.padStart(14, "0");
var login = login_hint;
var scope = scopes.join(" ");
var options = {
state,
client_id,
redirect_uri,
scope,
login: login_hint,
};
var params = querystringify(options);
return authorize_url + "?allow_signup=true&" + params;
}

// because JavaScript's Base64 implementation isn't URL-safe
function urlBase64ToBase64(str) {
var r = str % 4;
if (2 === r) {
str += "==";
} else if (3 === r) {
str += "=";
}
return str.replace(/-/g, "+").replace(/_/g, "/");
}

function urlBase64ToJson(u64) {
var b64 = urlBase64ToBase64(u64);
var str = atob(b64);
return JSON.parse(str);
}

async function attemptRefresh() {
let resp = await window
.fetch(baseUrl + "/api/authn/refresh", { method: "POST" })
Expand Down Expand Up @@ -149,36 +59,12 @@
console.info(dummies);
}

function parseQuerystring(querystring) {
var query = {};
querystring.split("&").forEach(function (pairstring) {
var pair = pairstring.split("=");
var key = pair[0];
var value = decodeURIComponent(pair[1]);

query[key] = value;
});
return query;
}

async function parseJwt(jwt) {
var parts = jwt.split(".");
var jws = {
protected: parts[0],
payload: parts[1],
signature: parts[2],
};
jws.header = urlBase64ToJson(jws.protected);
jws.claims = urlBase64ToJson(jws.payload);
return jws;
}

async function init() {
$(".js-logout").hidden = true;
$(".js-social-login").hidden = true;

// TODO rename to google
var googleSignInUrl = generateOidcUrl(
var googleSignInUrl = Auth3000.generateOidcUrl(
"https://accounts.google.com/o/oauth2/v2/auth",
ENV.GOOGLE_CLIENT_ID,
ENV.GOOGLE_REDIRECT_URI,
Expand All @@ -187,7 +73,7 @@
);
$(".js-google-oidc-url").href = googleSignInUrl;

var githubSignInUrl = generateOauth2Url(
var githubSignInUrl = Auth3000.generateOauth2Url(
"https://github.com/login/oauth/authorize",
ENV.GITHUB_CLIENT_ID,
ENV.GITHUB_REDIRECT_URI,
Expand All @@ -210,7 +96,7 @@
});

var querystring = document.location.hash.slice(1);
var query = parseQuerystring(querystring);
var query = Auth3000.parseQuerystring(querystring);
if (query.id_token) {
completeOidcSignIn(query);
return;
Expand Down Expand Up @@ -281,7 +167,7 @@
// Show the token for easy capture
console.info("id_token", query.id_token);

let jws = await parseJwt(query.id_token).catch(die);
let jws = await Auth3000.parseJwt(query.id_token).catch(die);

if ("https://accounts.google.com" === jws.claims.iss) {
// TODO make sure we've got the right options for fetch !!!
Expand Down