Refactored into it's own library.

This commit is contained in:
John Shaver 2017-10-05 22:26:57 -07:00
parent 1a7c79fb18
commit 8a8e87c0d4
3 changed files with 262 additions and 76 deletions

209
static/authenticator.js Normal file
View file

@ -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 = "";
}
}
})();

View file

@ -3,11 +3,17 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Redundant Feed!</title> <title>Redundant Feed!</title>
<script type="text/javascript" src="authenticator.js"></script>
<script type="text/javascript" src="loadfeed.js"></script> <script type="text/javascript" src="loadfeed.js"></script>
</head> </head>
<body> <body>
<div id="feed"> <div id="app">
<div id="header">
...loading... ...loading...
</div> </div>
<div id="feed">
Your feed is empty. You must be really boring...
</div>
</div>
</body> </body>
</html> </html>

View file

@ -1,87 +1,58 @@
(function(){ (function(){
'use strict'; 'use strict';
var CLIENT_ID = "1944365805820399"; var AUTH_SETTINGS = {
var REDIRECT_URI = "http://localhost:3000/"; clientID: "1944365805820399",
var AUTH_ENDPOINT = "https://www.facebook.com/v2.10/dialog/oauth"; redirectURI: "http://localhost:3000/",
var PERMS = "user_posts"; authEndpoint: "https://www.facebook.com/v2.10/dialog/oauth",
var token = window.localStorage.getItem("token"); requiredPerms: ["user_posts"]
var state = window.localStorage.getItem("state");
if (!state) {
state = get15RandomSafeChars();
window.localStorage.setItem("state", state);
} }
console.log("state: ", state);
if(window.location.hash !== "") { var store = {
var hashParams = window.location.hash.slice(1).split("&"); status: "unauthenticated",
hashParams = hashParams.reduce(function(obj, param) { userName: "",
var parsed = param.split("="); feed: []
obj[parsed[0]] = decodeURIComponent(parsed[1]); }
return obj;
}, {});
if(hashParams.access_token) { $Auth.init(AUTH_SETTINGS);
if(hashParams.state === state) {
token = hashParams.access_token; if(document.readyState === "loading") {
window.localStorage.setItem("token", token); document.addEventListener("DOMContentLoaded", onload);
} else { } else {
console.log("Invalid state! Something fishy here. Ignoring token..."); onload();
console.log("Our state: ", state, " Received state: ", hashParams.state);
}
}
}
if(!token) {
console.log("NOT AUTHED!");
} else {
console.log("Probably authed!");
} }
document.addEventListener('DOMContentLoaded', function() { function onload(){
var feed = document.getElementById("feed"); var feed = document.getElementById("feed");
if(!token) { var header = document.getElementById("header");
feed.textContent = "First you need to authorize Facebook to allow me to spy you."; 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 {
$Auth.addExpireListener(onload);
header.textContent = "Hello person.";
}
}
function renderFeed() {
}
function createAuthButton(buttonText) {
var button = document.createElement("Button"); var button = document.createElement("Button");
button.onclick = function(){redirectToAuthEndpoint(PERMS);}; button.onclick = function(){$Auth.gotoAuth();};
button.value = "authorize"; button.value = "authorize";
button.type = "button"; button.type = "button";
button.textContent = "AUTHORIZE"; button.textContent = buttonText;
feed.appendChild(button); return button;
} else {
feed.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;
}
})(); })();