From 8a8e87c0d400b3ad1508ad33480298be322f2e4e Mon Sep 17 00:00:00 2001 From: John Shaver Date: Thu, 5 Oct 2017 22:26:57 -0700 Subject: [PATCH] Refactored into it's own library. --- static/authenticator.js | 209 ++++++++++++++++++++++++++++++++++++++++ static/index.html | 10 +- static/loadfeed.js | 119 +++++++++-------------- 3 files changed, 262 insertions(+), 76 deletions(-) create mode 100644 static/authenticator.js diff --git a/static/authenticator.js b/static/authenticator.js new file mode 100644 index 0000000..886511c --- /dev/null +++ b/static/authenticator.js @@ -0,0 +1,209 @@ +(function(){ + 'use strict'; + //This is how the world accesses it! + var Auth = window.$Auth = {}; + var error; + var expireListeners = []; + + //Init should be called before any other functions are used + //settings: { + // clientID: client_id for oauth request + // redirectURI: where redirect to with the token after authorizing + // authEndpoint: OAuth endpoint to retrieve token + // requiredPerms: what permissions to request from the user + //} + Auth.init = function(settings) { + var isValid = Auth.verifySettings(settings); + if(!isValid) { + console.error("authenticator($Auth) initialized with invalid settings! \ + There may be problems..."); + } + Auth.settings = {}; + Auth.settings.clientID = settings.clientID; + Auth.settings.redirectURI = settings.redirectURI; + Auth.settings.authEndpoint = settings.authEndpoint; + Auth.settings.requiredPerms = settings.requiredPerms; + + if(Auth.getToken()) { + if(Date.now() >= Auth.getToken().expiresTime) { + Auth.removeToken(); + } else { + window.setTimeout(Auth.expire, Auth.getToken().expiresTime - Date.now()); + } + } + + if(window.location.hash !== "") { + var hashParams = Auth.parseParams(window.location.hash); + + if(hashParams.error_reason) { + error = error_reason; + window.location.hash = ""; + } + if(hashParams.access_token) { + if(hashParams.state === Auth.getState()) { + Auth.setToken(hashParams.access_token, hashParams.expires_in); + } else { + console.error("Invalid state! Something fishy here. Ignoring token..."); + console.error("Our state: ", hashParams.state, " Received state: ", hashParams.state); + } + clearHash(); + } + } + + return isValid; + }; + + //Used to verify a settings object for init. Also checked before each call + //that uses the settings. + Auth.verifySettings = function(settings) { + return ( + settings && + settings.clientID && + settings.redirectURI && + settings.authEndpoint && + Object.prototype.toString.call(settings.requiredPerms) === '[object Array]' + ); + }; + + //Test if the current $Auth.settings are valid. + Auth.isInit = function(){ + return Auth.verifySettings(Auth.settings); + }; + + Auth.isAuthed = function() { + return Auth.getToken(); + }; + + Auth.wasError = function() { + return error; + }; + + //Redirect to the auth endpoint and request an implicit authorization + // according to the current $Auth.settings. + Auth.gotoAuth = function() { + if(!Auth.isInit()) { + console.error( + "Error! Please configure with $Auth.init() before using $Auth" + ); + } + redirectToAuthEndpoint(); + + }; + + //User needs to know when things have expires so they can react to it. + + + Auth.addExpireListener = function(listener) { + expireListeners.push (listener); + }; + + Auth.removeExpireListener = function(listener) { + expireListeners.forEach(function(element, i) { + if(element === listener) { + expireListeners[i] = undefined; + } + }); + }; + + Auth.addExpireListener = function(listener) { + expireListeners.push (listener); + }; + + Auth.removeExpireListener = function(listener) { + expireListeners.forEach(function(element, i) { + if(element === listener) { + expireListeners[i] = undefined; + } + }); + }; + Auth.expire = function() { + Auth.removeToken(); + expireListeners.forEach(function(listener) { + if(typeof listener === "function") { + listener(); + } + }); + }; + + Auth.parseParams = function(params) { + var result = params.slice(1).split("&").reduce(function(obj, param) { + var parsed = param.split("="); + obj[parsed[0]] = decodeURIComponent(parsed[1]); + return obj; + }, {}); + return result; + }; + + Auth.encodeParams = function(paramObj) { + return Object.keys(paramObj).map(function(key) { + return key + "=" + encodeURIComponent(paramObj[key]); + }).join("&"); + + }; + + Auth.getToken = function() { + try { + return JSON.parse(window.localStorage.getItem("$Auth_token")); + } catch (e) { + console.error("Error parsing token string from storage.") + throw e; + } + }; + + Auth.setToken = function(value, expires) { + var token = { + value: value, + expiresTime: Date.now() + expires * 1000 + }; + window.setTimeout(Auth.expire, expires * 1000 - 60000); + + window.localStorage.setItem("$Auth_token", JSON.stringify(token)); + }; + + Auth.removeToken = function() { + window.localStorage.removeItem("$Auth_token"); + }; + + Auth.getState = function() { + if(!window.localStorage.getItem("$Auth_state")) { + window.localStorage.setItem("$Auth_state", get15RandomSafeChars()); + } + return window.localStorage.getItem("$Auth_state"); + }; + + function redirectToAuthEndpoint(perms) { + var payload = { + client_id: Auth.settings.clientID, + redirect_uri: Auth.settings.redirectURI, + state: Auth.getState(), + response_type: "token", + scope: Auth.settings.requiredPerms.join(" ") + } + + window.location = Auth.settings.authEndpoint + "?" + Auth.encodeParams(payload); + } + + function get15RandomSafeChars() { + var characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; + var string = ""; + var numbers = [0,1,2].map(function() { + return Math.floor(Math.random() * Math.pow(2,32)); + }); + numbers.forEach(function(num) { + var bits = num; + for(var i = 0; i < 5; ++i) { + string += characters[bits & 0x3f]; + bits = bits >> 6; + } + }); + return string; + } + function clearHash() { + if(window.history && typeof window.history.replaceState !== "undefined") { + history.replaceState({}, "", window.location.pathname + window.location.search); + } else { + window.location.hash = ""; + } + } + +})(); diff --git a/static/index.html b/static/index.html index 57a0c75..8840191 100644 --- a/static/index.html +++ b/static/index.html @@ -3,11 +3,17 @@ Redundant Feed! + -
- ...loading... +
+ +
+ Your feed is empty. You must be really boring... +
diff --git a/static/loadfeed.js b/static/loadfeed.js index c2a5468..d80f7c1 100644 --- a/static/loadfeed.js +++ b/static/loadfeed.js @@ -1,87 +1,58 @@ (function(){ 'use strict'; - var CLIENT_ID = "1944365805820399"; - var REDIRECT_URI = "http://localhost:3000/"; - var AUTH_ENDPOINT = "https://www.facebook.com/v2.10/dialog/oauth"; - var PERMS = "user_posts"; - var token = window.localStorage.getItem("token"); - var state = window.localStorage.getItem("state"); - if (!state) { - state = get15RandomSafeChars(); - window.localStorage.setItem("state", state); - } - console.log("state: ", state); - - if(window.location.hash !== "") { - var hashParams = window.location.hash.slice(1).split("&"); - hashParams = hashParams.reduce(function(obj, param) { - var parsed = param.split("="); - obj[parsed[0]] = decodeURIComponent(parsed[1]); - return obj; - }, {}); - - if(hashParams.access_token) { - if(hashParams.state === state) { - token = hashParams.access_token; - window.localStorage.setItem("token", token); - } else { - console.log("Invalid state! Something fishy here. Ignoring token..."); - console.log("Our state: ", state, " Received state: ", hashParams.state); - } - } - } - if(!token) { - console.log("NOT AUTHED!"); - } else { - console.log("Probably authed!"); + var AUTH_SETTINGS = { + clientID: "1944365805820399", + redirectURI: "http://localhost:3000/", + authEndpoint: "https://www.facebook.com/v2.10/dialog/oauth", + requiredPerms: ["user_posts"] } - document.addEventListener('DOMContentLoaded', function() { + var store = { + status: "unauthenticated", + userName: "", + feed: [] + } + + $Auth.init(AUTH_SETTINGS); + + if(document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", onload); + } else { + onload(); + } + + function onload(){ var feed = document.getElementById("feed"); - if(!token) { - feed.textContent = "First you need to authorize Facebook to allow me to spy you."; - var button = document.createElement("Button"); - button.onclick = function(){redirectToAuthEndpoint(PERMS);}; - button.value = "authorize"; - button.type = "button"; - button.textContent = "AUTHORIZE"; - feed.appendChild(button); + var header = document.getElementById("header"); + if(!$Auth.isAuthed()) { + if($Auth.wasError()){ + header.textContent = "It won't work if I can't spy on you. Are you \ + sure you don't want to authorize it?"; + header.appendChild(createAuthButton("TRY AGAIN")); + } else { + header.textContent = "First you need to authorize Facebook to allow me \ + to spy on you."; + header.appendChild(createAuthButton("AUTHORIZE")); + } } else { - feed.textContent = "Hello person."; + $Auth.addExpireListener(onload); + header.textContent = "Hello person."; } - }); - - function redirectToAuthEndpoint(perms) { - var payload = { - client_id: CLIENT_ID, - redirect_uri: REDIRECT_URI, - state: state, - response_type: "token", - scope: perms - }; - - var params = Object.keys(payload).map(function(key) { - return key + "=" + encodeURIComponent(payload[key]); - }).join("&"); - - window.location = AUTH_ENDPOINT + "?" + params; } - function get15RandomSafeChars() { - var characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; - var string = ""; - var numbers = [0,1,2].map(function() { - return Math.floor(Math.random() * Math.pow(2,32)); - }); - numbers.forEach(function(num) { - var bits = num; - for(var i = 0; i < 5; ++i) { - string += characters[bits & 0x3f]; - bits = bits >> 6; - } - }); - return string; + function renderFeed() { + } + + function createAuthButton(buttonText) { + var button = document.createElement("Button"); + button.onclick = function(){$Auth.gotoAuth();}; + button.value = "authorize"; + button.type = "button"; + button.textContent = buttonText; + return button; + } + })();