adding https and wikiDomains for login, plus GitHub and Google login, and switching to using winchan for communication between windows
This commit is contained in:
parent
f02c0e6334
commit
a370af2f5f
|
@ -2,3 +2,6 @@ node_modules
|
|||
*.log
|
||||
/client/*.js
|
||||
/client/*.map
|
||||
|
||||
# don't ignore winchan.js
|
||||
!/client/winchan.js
|
||||
|
|
|
@ -819,6 +819,35 @@ section>.contents {
|
|||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Icon (github) */
|
||||
.github-button span:after{
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAABBNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPnhtcC5kaWQ6RERCMUIwOUY4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDY8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6RTUxNzhBMkE5OUEwMTFFMjlBMTVCQzEwNDZBODkwNEQ8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6RTUxNzhBMkI5OUEwMTFFMjlBMTVCQzEwNDZBODkwNEQ8L3N0UmVmOmRvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6RERCMUIwOUU4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDY8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgqBOjEgAAADH0lEQVRIDZWVzUuVQRTGu6kFJlFRBmKYfRhu+7CVVtCiNi6CPiDalVGgG1v0QdEfEi3bhLgIiigiqo0WbYI+FokStVAoWoVW3n6/cY5Nt0vYA897zpx5zjkz7zt3bmVZDarVakOlUvlpGL8Tsw8ehjvhJrgSzsIP8CW8Bx+TM4k1ZzHf8V9AsNwgthVeg+/hUqBOfWvOT3XqNahkQS/i8aLyfOHXc8t583pznVRvsRETsYN+/Jmi0nf8n3lssVo65fycTob5/bnRwo4INORAH340mMX/BgPRKMZhy7h684R1+nLdVN9vsBG+gAG3fRQOw1cRxH6Bn7KNsPMXoPryNVtvo40afYDzcBecgyvgOKflNtYFjGL2whk4DT15rs6PvAGOoZ3AqvUk7oHWsd45eN2JrXASini3N/EbYP1TQmYJdVlvnog61t3iTvbDDliFZdF5VmiCq3buz9NCAKQ4Or9NOW8d5zrgAXcyYiUQH3EaP45h2RR9faBPOvOg+SLqjdhkIoWqVY+ruGMpbLmy+tWLaOjNhyLqTbiC9qyNVX+sGRel/ulG/qea/HYnmnLQdyhqxwvRpT9r85ts4mUnokm6fxZC//WMfI+1iPGsTaZS6Pfp6eJ9dnJi5rHxO8qS+kZd1nei2FFTb8omYzmob/cueNoYiT8o4G+g7iEw7rw69eAM3A7nofXEuKfoBBSuXApPxmW41J00ob0C474ra52wSRuM++kW/jD8DMUzeBEehM0uC5t2hV0FD8GrcAwGbBC/Eeu2mWfiYFZ8xR6HXvklnjJYm7Vxa68j9qgU4Vu8bDKUGuTEFibv5oQp7GZ4DD6Bz+GRrPMbpO+Qx5cYC694i4v4Ed7Hb0lNcGJl3fivobiRizTjr07C4kEscs4qBh4Qm2jFO9idayStryudBOxu+BYKX9EQ9L+iJyfE94gm55gT7iTwBsfrfrGufgIT0cirfzQysh1UhB/Fw56v0XnZbsvaOMKp/uIDQTRqxD8JH0AxkBOjeNiBhenqQ+wpmK4UbP0G0akU4K+BPXB9bhKvK+x65vbCdPKy5q8GvwD3BqLoPnaIbQAAAABJRU5ErkJggg==) 4px center no-repeat;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 31px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -3px;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Icon (google) */
|
||||
.google-button span:after{
|
||||
background: url(data:image/png;base64,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) 4px center no-repeat;
|
||||
background-size: 18px;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 31px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Icon background */
|
||||
.scheme-button span:before{
|
||||
content: '';
|
||||
|
@ -846,6 +875,10 @@ section>.contents {
|
|||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.google-button span:before{
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Triangle */
|
||||
.scheme-button:before{
|
||||
background: #42a9dd;
|
||||
|
@ -879,6 +912,11 @@ section>.contents {
|
|||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.google-button:before{
|
||||
background: white;
|
||||
|
||||
}
|
||||
|
||||
/* Inset shadow (required here because the icon background clips it when on the `a` element) */
|
||||
.scheme-button:after{
|
||||
content: '';
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script>
|
||||
function doPost(msg, origin) {
|
||||
window.parent.postMessage(msg, origin);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
###
|
||||
|
||||
settings = {}
|
||||
|
||||
claim_wiki = () ->
|
||||
# we want to initiate a claim on a wiki
|
||||
#
|
||||
|
@ -31,7 +33,6 @@ claim_wiki = () ->
|
|||
response.json().then (json) ->
|
||||
ownerName = json.ownerName
|
||||
update_footer ownerName, true, true
|
||||
console.log 'owner: ', json.ownerName, ' : ', ownerName
|
||||
else
|
||||
console.log 'Attempt to claim site failed', response
|
||||
|
||||
|
@ -72,21 +73,53 @@ update_footer = (ownerName, isAuthenticated, isOwner) ->
|
|||
$('footer > #security').append "<a href='#' id='show-security-dialog' class='footer-item' title='#{signonTitle}'><i class='fa fa-lock fa-lg fa-fw'></i></a>"
|
||||
$('footer > #security > #show-security-dialog').click (e) ->
|
||||
e.preventDefault()
|
||||
securityDialog = window.open(
|
||||
"/auth/loginDialog",
|
||||
"_blank",
|
||||
"width=700, height=375, menubar=no, location=no, chrome=yes, centerscreen")
|
||||
securityDialog.window.focus()
|
||||
|
||||
w = WinChan.open({
|
||||
url: settings.dialogURL
|
||||
relay_url: settings.relayURL
|
||||
window_features: "menubar=0, location=0, resizable=0, scrollbars=0, status=0, dialog=1, width=700, height=375"
|
||||
params: {}
|
||||
}, (err, r) ->
|
||||
if err
|
||||
console.log err
|
||||
else if !isClaimed
|
||||
claim_wiki()
|
||||
else
|
||||
update_footer ownerName, true)
|
||||
|
||||
|
||||
|
||||
setup = (user) ->
|
||||
|
||||
# we will replace font-awesome with a small number of svg icons at a later date...
|
||||
if (!$("link[href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css']").length)
|
||||
$('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">').appendTo("head")
|
||||
wiki.getScript '/security/winchan.js'
|
||||
if (!$("link[href='/security/style.css']").length)
|
||||
$('<link rel="stylesheet" href="/security/style.css">').appendTo("head")
|
||||
myInit = {
|
||||
method: 'GET'
|
||||
cache: 'no-cache'
|
||||
mode: 'same-origin'
|
||||
}
|
||||
fetch '/auth/client-settings.json', myInit
|
||||
.then (response) ->
|
||||
if response.ok
|
||||
response.json().then (json) ->
|
||||
settings = json
|
||||
if settings.useHttps
|
||||
dialogProtocol = 'https:'
|
||||
else
|
||||
dialogProtocol = window.location.protocol
|
||||
if settings.wikiHost
|
||||
dialogHost = settings.wikiHost
|
||||
else
|
||||
dialogHost = window.location.host
|
||||
settings.dialogURL = dialogProtocol + '//' + dialogHost + '/auth/loginDialog'
|
||||
settings.relayURL = dialogProtocol + '//' + dialogHost + '/auth/relay.html'
|
||||
|
||||
update_footer ownerName, isAuthenticated, isOwner
|
||||
update_footer ownerName, isAuthenticated, isOwner
|
||||
else
|
||||
console.log 'Unable to fetch client settings: ', response
|
||||
|
||||
window.plugins.security = {setup, claim_wiki, update_footer}
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
var WinChan = (function() {
|
||||
var RELAY_FRAME_NAME = "__winchan_relay_frame";
|
||||
var CLOSE_CMD = "die";
|
||||
|
||||
// a portable addListener implementation
|
||||
function addListener(w, event, cb) {
|
||||
if(w.attachEvent) w.attachEvent('on' + event, cb);
|
||||
else if (w.addEventListener) w.addEventListener(event, cb, false);
|
||||
}
|
||||
|
||||
// a portable removeListener implementation
|
||||
function removeListener(w, event, cb) {
|
||||
if(w.detachEvent) w.detachEvent('on' + event, cb);
|
||||
else if (w.removeEventListener) w.removeEventListener(event, cb, false);
|
||||
}
|
||||
|
||||
|
||||
// checking for IE8 or above
|
||||
function isInternetExplorer() {
|
||||
var rv = -1; // Return value assumes failure.
|
||||
var ua = navigator.userAgent;
|
||||
if (navigator.appName === 'Microsoft Internet Explorer') {
|
||||
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
|
||||
if (re.exec(ua) != null)
|
||||
rv = parseFloat(RegExp.$1);
|
||||
}
|
||||
// IE > 11
|
||||
else if (ua.indexOf("Trident") > -1) {
|
||||
var re = new RegExp("rv:([0-9]{2,2}[\.0-9]{0,})");
|
||||
if (re.exec(ua) !== null) {
|
||||
rv = parseFloat(RegExp.$1);
|
||||
}
|
||||
}
|
||||
|
||||
return rv >= 8;
|
||||
}
|
||||
|
||||
// checking Mobile Firefox (Fennec)
|
||||
function isFennec() {
|
||||
try {
|
||||
// We must check for both XUL and Java versions of Fennec. Both have
|
||||
// distinct UA strings.
|
||||
var userAgent = navigator.userAgent;
|
||||
return (userAgent.indexOf('Fennec/') != -1) || // XUL
|
||||
(userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1); // Java
|
||||
} catch(e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
// feature checking to see if this platform is supported at all
|
||||
function isSupported() {
|
||||
return (window.JSON && window.JSON.stringify &&
|
||||
window.JSON.parse && window.postMessage);
|
||||
}
|
||||
|
||||
// given a URL, extract the origin. Taken from: https://github.com/firebase/firebase-simple-login/blob/d2cb95b9f812d8488bdbfba51c3a7c153ba1a074/js/src/simple-login/transports/WinChan.js#L25-L30
|
||||
function extractOrigin(url) {
|
||||
if (!/^https?:\/\//.test(url)) url = window.location.href;
|
||||
var m = /^(https?:\/\/[\-_a-zA-Z\.0-9:]+)/.exec(url);
|
||||
if (m) return m[1];
|
||||
return url;
|
||||
}
|
||||
|
||||
// find the relay iframe in the opener
|
||||
function findRelay() {
|
||||
var loc = window.location;
|
||||
var frames = window.opener.frames;
|
||||
for (var i = frames.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
if (frames[i].location.protocol === window.location.protocol &&
|
||||
frames[i].location.host === window.location.host &&
|
||||
frames[i].name === RELAY_FRAME_NAME)
|
||||
{
|
||||
return frames[i];
|
||||
}
|
||||
} catch(e) { }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var isIE = isInternetExplorer();
|
||||
|
||||
if (isSupported()) {
|
||||
/* General flow:
|
||||
* 0. user clicks
|
||||
* (IE SPECIFIC) 1. caller adds relay iframe (served from trusted domain) to DOM
|
||||
* 2. caller opens window (with content from trusted domain)
|
||||
* 3. window on opening adds a listener to 'message'
|
||||
* (IE SPECIFIC) 4. window on opening finds iframe
|
||||
* 5. window checks if iframe is "loaded" - has a 'doPost' function yet
|
||||
* (IE SPECIFIC5) 5a. if iframe.doPost exists, window uses it to send ready event to caller
|
||||
* (IE SPECIFIC5) 5b. if iframe.doPost doesn't exist, window waits for frame ready
|
||||
* (IE SPECIFIC5) 5bi. once ready, window calls iframe.doPost to send ready event
|
||||
* 6. caller upon reciept of 'ready', sends args
|
||||
*/
|
||||
return {
|
||||
open: function(opts, cb) {
|
||||
if (!cb) throw "missing required callback argument";
|
||||
|
||||
// test required options
|
||||
var err;
|
||||
if (!opts.url) err = "missing required 'url' parameter";
|
||||
if (!opts.relay_url) err = "missing required 'relay_url' parameter";
|
||||
if (err) setTimeout(function() { cb(err); }, 0);
|
||||
|
||||
// supply default options
|
||||
if (!opts.window_name) opts.window_name = null;
|
||||
if (!opts.window_features || isFennec()) opts.window_features = undefined;
|
||||
|
||||
// opts.params may be undefined
|
||||
|
||||
var iframe;
|
||||
|
||||
// sanity check, are url and relay_url the same origin?
|
||||
var origin = extractOrigin(opts.url);
|
||||
if (origin !== extractOrigin(opts.relay_url)) {
|
||||
return setTimeout(function() {
|
||||
cb('invalid arguments: origin of url and relay_url must match');
|
||||
}, 0);
|
||||
}
|
||||
|
||||
var messageTarget;
|
||||
|
||||
if (isIE) {
|
||||
// first we need to add a "relay" iframe to the document that's served
|
||||
// from the target domain. We can postmessage into a iframe, but not a
|
||||
// window
|
||||
iframe = document.createElement("iframe");
|
||||
// iframe.setAttribute('name', framename);
|
||||
iframe.setAttribute('src', opts.relay_url);
|
||||
iframe.style.display = "none";
|
||||
iframe.setAttribute('name', RELAY_FRAME_NAME);
|
||||
document.body.appendChild(iframe);
|
||||
messageTarget = iframe.contentWindow;
|
||||
}
|
||||
|
||||
var w = opts.popup || window.open(opts.url, opts.window_name, opts.window_features);
|
||||
if (opts.popup) {
|
||||
w.location.href = opts.url;
|
||||
}
|
||||
|
||||
if (!messageTarget) messageTarget = w;
|
||||
|
||||
// lets listen in case the window blows up before telling us
|
||||
var closeInterval = setInterval(function() {
|
||||
if (w && w.closed) {
|
||||
cleanup();
|
||||
if (cb) {
|
||||
cb('User closed the popup window');
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
var req = JSON.stringify({a: 'request', d: opts.params});
|
||||
|
||||
// cleanup on unload
|
||||
function cleanup() {
|
||||
if (iframe) document.body.removeChild(iframe);
|
||||
iframe = undefined;
|
||||
if (closeInterval) closeInterval = clearInterval(closeInterval);
|
||||
removeListener(window, 'message', onMessage);
|
||||
removeListener(window, 'unload', cleanup);
|
||||
if (w) {
|
||||
try {
|
||||
w.close();
|
||||
} catch (securityViolation) {
|
||||
// This happens in Opera 12 sometimes
|
||||
// see https://github.com/mozilla/browserid/issues/1844
|
||||
messageTarget.postMessage(CLOSE_CMD, origin);
|
||||
}
|
||||
}
|
||||
w = messageTarget = undefined;
|
||||
}
|
||||
|
||||
addListener(window, 'unload', cleanup);
|
||||
|
||||
function onMessage(e) {
|
||||
if (e.origin !== origin) { return; }
|
||||
try {
|
||||
var d = JSON.parse(e.data);
|
||||
if (d.a === 'ready') messageTarget.postMessage(req, origin);
|
||||
else if (d.a === 'error') {
|
||||
cleanup();
|
||||
if (cb) {
|
||||
cb(d.d);
|
||||
cb = null;
|
||||
}
|
||||
} else if (d.a === 'response') {
|
||||
cleanup();
|
||||
if (cb) {
|
||||
cb(null, d.d);
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
} catch(err) { }
|
||||
}
|
||||
|
||||
addListener(window, 'message', onMessage);
|
||||
|
||||
return {
|
||||
close: cleanup,
|
||||
focus: function() {
|
||||
if (w) {
|
||||
try {
|
||||
w.focus();
|
||||
} catch (e) {
|
||||
// IE7 blows up here, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
onOpen: function(cb) {
|
||||
var o = "*";
|
||||
var msgTarget = isIE ? findRelay() : window.opener;
|
||||
if (!msgTarget) throw "can't find relay frame";
|
||||
function doPost(msg) {
|
||||
msg = JSON.stringify(msg);
|
||||
if (isIE) msgTarget.doPost(msg, o);
|
||||
else msgTarget.postMessage(msg, o);
|
||||
}
|
||||
|
||||
function onMessage(e) {
|
||||
// only one message gets through, but let's make sure it's actually
|
||||
// the message we're looking for (other code may be using
|
||||
// postmessage) - we do this by ensuring the payload can
|
||||
// be parsed, and it's got an 'a' (action) value of 'request'.
|
||||
var d;
|
||||
try {
|
||||
d = JSON.parse(e.data);
|
||||
} catch(err) { }
|
||||
if (!d || d.a !== 'request') return;
|
||||
removeListener(window, 'message', onMessage);
|
||||
o = e.origin;
|
||||
if (cb) {
|
||||
// this setTimeout is critically important for IE8 -
|
||||
// in ie8 sometimes addListener for 'message' can synchronously
|
||||
// cause your callback to be invoked. awesome.
|
||||
setTimeout(function() {
|
||||
cb(o, d.d, function(r) {
|
||||
cb = undefined;
|
||||
doPost({a: 'response', d: r});
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function onDie(e) {
|
||||
if (e.data === CLOSE_CMD) {
|
||||
try { window.close(); } catch (o_O) {}
|
||||
}
|
||||
}
|
||||
addListener(isIE ? msgTarget : window, 'message', onMessage);
|
||||
addListener(isIE ? msgTarget : window, 'message', onDie);
|
||||
|
||||
// we cannot post to our parent that we're ready before the iframe
|
||||
// is loaded. (IE specific possible failure)
|
||||
try {
|
||||
doPost({a: "ready"});
|
||||
} catch(e) {
|
||||
// this code should never be exectued outside IE
|
||||
addListener(msgTarget, 'load', function(e) {
|
||||
doPost({a: "ready"});
|
||||
});
|
||||
}
|
||||
|
||||
// if window is unloaded and the client hasn't called cb, it's an error
|
||||
var onUnload = function() {
|
||||
try {
|
||||
// IE8 doesn't like this...
|
||||
removeListener(isIE ? msgTarget : window, 'message', onDie);
|
||||
} catch (ohWell) { }
|
||||
if (cb) doPost({ a: 'error', d: 'client closed window' });
|
||||
cb = undefined;
|
||||
// explicitly close the window, in case the client is trying to reload or nav
|
||||
try { window.close(); } catch (e) { }
|
||||
};
|
||||
addListener(window, 'unload', onUnload);
|
||||
return {
|
||||
detach: function() {
|
||||
removeListener(window, 'unload', onUnload);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
open: function(url, winopts, arg, cb) {
|
||||
setTimeout(function() { cb("unsupported browser"); }, 0);
|
||||
},
|
||||
onOpen: function(cb) {
|
||||
setTimeout(function() { cb("unsupported browser"); }, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = WinChan;
|
||||
}
|
|
@ -9,8 +9,11 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"coffee-script": "1.10",
|
||||
"lodash": "4",
|
||||
"passport": "^0.3.2",
|
||||
"passport-twitter": "*",
|
||||
"passport-github": "*",
|
||||
"passport-google-oauth20": "*",
|
||||
"qs": "6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -13,6 +13,10 @@ path = require 'path'
|
|||
https = require 'https'
|
||||
qs = require 'qs'
|
||||
|
||||
url = require 'url'
|
||||
|
||||
_ = require('lodash')
|
||||
|
||||
passport = require 'passport'
|
||||
|
||||
|
||||
|
@ -27,6 +31,7 @@ module.exports = exports = (log, loga, argv) ->
|
|||
ownerName = ''
|
||||
user = {}
|
||||
wikiName = argv.url
|
||||
wikiHost = argv.wiki_domain
|
||||
|
||||
admin = argv.admin
|
||||
|
||||
|
@ -39,9 +44,19 @@ module.exports = exports = (log, loga, argv) ->
|
|||
personaIDFile = argv.id
|
||||
usingPersona = false
|
||||
|
||||
ids = {}
|
||||
if argv.security_useHttps
|
||||
useHttps = true
|
||||
callbackProtocol = "https:"
|
||||
else
|
||||
useHttps = false
|
||||
callbackProtocol = url.parse(argv.url).protocol
|
||||
|
||||
schemes = {}
|
||||
if wikiHost
|
||||
callbackHost = wikiHost
|
||||
else
|
||||
callbackHost = url.parse(argv.url).host
|
||||
|
||||
ids = []
|
||||
|
||||
# Mozilla Persona service closes on
|
||||
personaEnd = new Date('2016-11-30')
|
||||
|
@ -114,13 +129,12 @@ module.exports = exports = (log, loga, argv) ->
|
|||
# site not claimed?
|
||||
return true
|
||||
else
|
||||
authorized = false
|
||||
try
|
||||
authorized = switch req.session.passport.user.provider
|
||||
when "twitter"
|
||||
if owner.twitter.id is req.session.passport.user.id
|
||||
true
|
||||
return authorized
|
||||
if owner[req.session.passport.user.provider].id is req.session.passport.user.id
|
||||
return true
|
||||
else
|
||||
return false
|
||||
return false
|
||||
|
||||
|
||||
security.isAdmin = (req) ->
|
||||
|
@ -128,13 +142,12 @@ module.exports = exports = (log, loga, argv) ->
|
|||
# not added legacy support yet, so...
|
||||
return false
|
||||
else
|
||||
admin = false
|
||||
try
|
||||
admin = switch req.session.passport.user.provider
|
||||
when "twitter"
|
||||
if admin is req.session.passport.user.id
|
||||
true
|
||||
return admin
|
||||
if admin is req.session.passport.user.id
|
||||
return true
|
||||
else
|
||||
return false
|
||||
return false
|
||||
|
||||
security.login = (updateOwner) ->
|
||||
console.log "Login...."
|
||||
|
@ -151,111 +164,115 @@ module.exports = exports = (log, loga, argv) ->
|
|||
passport.deserializeUser = (obj, req, done) ->
|
||||
done(null, obj)
|
||||
|
||||
|
||||
###
|
||||
# Github Strategy
|
||||
if argv.github_clientID? and argv.github_clientSecret?
|
||||
github = {}
|
||||
github['clientID'] = argv.github_clientID
|
||||
github['clientSecret'] = argv.github_clientSecret
|
||||
ids['github'] = github
|
||||
|
||||
ids.push('github')
|
||||
GithubStrategy = require('passport-github').Strategy
|
||||
|
||||
passport.use(new GithubStrategy({
|
||||
clientID: ids['github'].clientID
|
||||
clientSecret: ids['github'].clientSecret
|
||||
# this is not going to work - callback must equal that specified won github
|
||||
# when the application was setup - it can't be dynamic....
|
||||
callbackURL: 'http://localhost:3000/auth/github/callback'
|
||||
}, (accessToken, refreshToken, profile, cb) ->
|
||||
User.findOrCreate({githubID: profile.id}, (err, user) ->
|
||||
return cb(err, user))))
|
||||
###
|
||||
|
||||
if argv.twitter_consumerKey? and argv.twitter_consumerSecret?
|
||||
schemes['twitter'] = true
|
||||
twitter = {}
|
||||
twitter['consumerKey'] = argv.twitter_consumerKey
|
||||
twitter['consumerSecret'] = argv.twitter_consumerSecret
|
||||
ids['twitter'] = twitter
|
||||
|
||||
TwitterStrategy = require('passport-twitter').Strategy
|
||||
|
||||
passport.use(new TwitterStrategy({
|
||||
consumerKey: ids['twitter'].consumerKey
|
||||
consumerSecret: ids['twitter'].consumerSecret
|
||||
callbackURL: '/auth/twitter/callback'
|
||||
clientID: argv.github_clientID
|
||||
clientSecret: argv.github_clientSecret
|
||||
scope: 'user:emails'
|
||||
# callbackURL is optional, and if it exists must match that given in
|
||||
# the OAuth application settings - so we don't specify it.
|
||||
}, (accessToken, refreshToken, profile, cb) ->
|
||||
user = {
|
||||
"provider": 'twitter',
|
||||
"id": profile.id,
|
||||
"username": profile.username,
|
||||
"displayName": profile.displayName
|
||||
provider: 'github',
|
||||
id: profile.id
|
||||
username: profile.username
|
||||
displayName: profile.displayName
|
||||
email: profile.emails[0].value
|
||||
}
|
||||
cb(null, user)))
|
||||
|
||||
###
|
||||
if argv.google_clientID? and argv.google_clientSecret?
|
||||
google = {}
|
||||
google['clientID'] = argv.google_clientID
|
||||
google['clientSecret'] = argv.google_clientSecret
|
||||
ids['google'] = google
|
||||
# Twitter Strategy
|
||||
if argv.twitter_consumerKey? and argv.twitter_consumerSecret?
|
||||
ids.push('twitter')
|
||||
TwitterStrategy = require('passport-twitter').Strategy
|
||||
|
||||
passport.use(new TwitterStrategy({
|
||||
consumerKey: argv.twitter_consumerKey
|
||||
consumerSecret: argv.twitter_consumerSecret
|
||||
callbackURL: callbackProtocol + '//' + callbackHost + '/auth/twitter/callback'
|
||||
}, (accessToken, refreshToken, profile, cb) ->
|
||||
user = {
|
||||
provider: 'twitter',
|
||||
id: profile.id,
|
||||
username: profile.username,
|
||||
displayName: profile.displayName
|
||||
}
|
||||
cb(null, user)))
|
||||
|
||||
# Google Strategy
|
||||
if argv.google_clientID? and argv.google_clientSecret?
|
||||
ids.push('google')
|
||||
GoogleStrategy = require('passport-google-oauth20').Strategy
|
||||
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: ids['google'].clientID
|
||||
clientSecret: ids['google'].clientSecret
|
||||
callbackURL: 'http://localhost:3000/auth/google/callback'
|
||||
clientID: argv.google_clientID
|
||||
clientSecret: argv.google_clientSecret
|
||||
callbackURL: callbackProtocol + '//' + callbackHost + '/auth/google/callback'
|
||||
}, (accessToken, refreshToken, profile, cb) ->
|
||||
user = {
|
||||
provider: "google"
|
||||
id: profile.id
|
||||
displayName: profile.displayName
|
||||
emails: profile.emails
|
||||
}
|
||||
cb(null, profile)))
|
||||
###
|
||||
|
||||
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
### Github
|
||||
app.get('/auth/github', passport.authenticate('github'), (req, res) -> )
|
||||
# Github
|
||||
app.get('/auth/github', passport.authenticate('github', {scope: 'user:email'}), (req, res) -> )
|
||||
app.get('/auth/github/callback',
|
||||
passport.authenticate('github', { failureRedirect: '/'}), (req, res) ->
|
||||
# do what ever happens on login
|
||||
)
|
||||
###
|
||||
passport.authenticate('github', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
|
||||
|
||||
# Twitter
|
||||
app.get('/auth/twitter', passport.authenticate('twitter'), (req, res) -> )
|
||||
app.get('/auth/twitter/callback',
|
||||
passport.authenticate('twitter', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
|
||||
|
||||
|
||||
|
||||
|
||||
### Google
|
||||
# Google
|
||||
app.get('/auth/google', passport.authenticate('google', { scope: [
|
||||
'https://www.googleapis.com/auth/plus.profile.emails.read'
|
||||
]}))
|
||||
app.get('/auth/google/callback',
|
||||
passport.authenticate('google', {failureRedirect: '/'}), (req, res) ->
|
||||
console.log 'google logged in!!!!'
|
||||
res.redirect('/view/welcome-visitors'))
|
||||
###
|
||||
passport.authenticate('google', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
|
||||
|
||||
|
||||
app.get '/auth/client-settings.json', (req, res) ->
|
||||
# the client needs some information to configure itself
|
||||
settings = {
|
||||
useHttps: useHttps
|
||||
}
|
||||
if wikiHost
|
||||
settings.wikiHost = wikiHost
|
||||
res.json settings
|
||||
|
||||
app.get '/auth/loginDialog', (req, res) ->
|
||||
referer = req.headers.referer
|
||||
console.log "logging into: ", url.parse(referer).hostname
|
||||
|
||||
console.log 'owner: ', owner
|
||||
schemeButtons = []
|
||||
_(ids).forEach (scheme) ->
|
||||
console.log "Scheme: ", scheme
|
||||
switch scheme
|
||||
when "twitter" then schemeButtons.push({button: "<a href='/auth/twitter' class='scheme-button twitter-button'><span>Twitter</span></a>"})
|
||||
when "github" then schemeButtons.push({button: "<a href='/auth/github' class='scheme-button github-button'><span>Github</span></a>"})
|
||||
when "google" then schemeButtons.push({button: "<a href='/auth/google' class='scheme-button google-button'><span>Google</span></a>"})
|
||||
|
||||
info = {
|
||||
wikiName: req.hostname
|
||||
wikiHostName: "a federated wiki site"
|
||||
title: if owner
|
||||
"Federated Wiki: Site Owner Sign-on"
|
||||
wikiName: url.parse(referer).hostname
|
||||
wikiHostName: if wikiHost
|
||||
"part of " + req.hostname + " wiki farm"
|
||||
else
|
||||
"Federated Wiki: Claim Wiki Site"
|
||||
loginText: if owner
|
||||
"Sign in to"
|
||||
else
|
||||
"Claim wiki"
|
||||
schemes: "<a href='/auth/twitter' class='scheme-button twitter-button'><span>Twitter</span></a>"
|
||||
"a federated wiki site"
|
||||
title: "Federated Wiki: Site Owner Sign-on"
|
||||
loginText: "Sign in to"
|
||||
schemes: schemeButtons
|
||||
}
|
||||
res.render(path.join(__dirname, '..', 'views', 'securityDialog.html'), info)
|
||||
|
||||
|
@ -276,6 +293,7 @@ module.exports = exports = (log, loga, argv) ->
|
|||
res.sendStatus(403)
|
||||
else
|
||||
user = req.session.passport.user
|
||||
console.log "Claim: user = ", user
|
||||
id = switch user.provider
|
||||
when "twitter" then {
|
||||
name: user.displayName
|
||||
|
@ -284,10 +302,25 @@ module.exports = exports = (log, loga, argv) ->
|
|||
username: user.username
|
||||
}
|
||||
}
|
||||
when "github" then {
|
||||
name: user.displayName
|
||||
github: {
|
||||
id: user.id
|
||||
username: user.username
|
||||
email: user.email
|
||||
}
|
||||
}
|
||||
when "google" then {
|
||||
name: user.displayName
|
||||
google: {
|
||||
id: user.id
|
||||
emails: user.emails
|
||||
}
|
||||
}
|
||||
|
||||
setOwner id, (err) ->
|
||||
if err
|
||||
console.log 'Failed to claim wiki ', req.hostname, ' for ', id
|
||||
console.log 'Failed to claim wiki ', req.hostname, ' for ', JSON.stringify(id)
|
||||
res.sendStatus(500)
|
||||
updateOwner getOwner()
|
||||
res.json({
|
||||
|
@ -301,8 +334,4 @@ module.exports = exports = (log, loga, argv) ->
|
|||
req.logout()
|
||||
res.send("OK")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
security
|
||||
|
|
|
@ -4,13 +4,6 @@
|
|||
<title>Federated Wiki: Sign-on</title>
|
||||
<link id='favicon' href='/favicon.png' rel='icon' type='image/png'>
|
||||
<link rel="stylesheet" href="/security/style.css">
|
||||
<script language='javascript' type='text/javascript'>
|
||||
if (window.opener.isClaimed == false)
|
||||
window.opener.plugins.security.claim_wiki()
|
||||
else
|
||||
window.opener.plugins.security.update_footer(window.opener.ownerName, true)
|
||||
window.close()
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
@ -22,4 +15,14 @@
|
|||
{{{authMessage}}}
|
||||
</div>
|
||||
</body>
|
||||
<script src="/security/winchan.js"></script>
|
||||
<script>
|
||||
var wc = WinChan.onOpen(function(origin, r, cb) {
|
||||
cb({
|
||||
done: true,
|
||||
timestamp: new Date().toString()
|
||||
});
|
||||
window.close();
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="wikiinfo">
|
||||
<div class="table">
|
||||
<div class="vertical">
|
||||
<img id="wiki_logo" src="/favicon.png" width="32px" height="32px"></img>
|
||||
<img id="wiki_logo" src="//{{wikiName}}/favicon.png" width="32px" height="32px"></img>
|
||||
<h2 id=wiki_name>{{wikiName}}</h2>
|
||||
<h3 id="wiki_hostname">{{wikiHostName}}</h3>
|
||||
</div>
|
||||
|
@ -24,7 +24,9 @@
|
|||
<div class="contents">
|
||||
<div class="scheme_section vcenter" style="width: 249px;">
|
||||
<h2>{{loginText}} {{wikiName}} with...</h2>
|
||||
{{{schemes}}}
|
||||
{{#schemes}}
|
||||
<p>{{{button}}}</p>
|
||||
{{/schemes}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue