Added support for Google Sign-In, optional in every way.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3187 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2016-04-03 19:31:03 +00:00
parent 7b112c5376
commit cbf54eaa17
9 changed files with 199 additions and 18 deletions

View File

@ -9,9 +9,10 @@
<link type="text/css" rel="stylesheet" href="/terminal/style.css"></link> <link type="text/css" rel="stylesheet" href="/terminal/style.css"></link>
<link type="image/png" rel="shortcut icon" href="/terminal/favicon.png"></link> <link type="image/png" rel="shortcut icon" href="/terminal/favicon.png"></link>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!--HEAD-->
</head> </head>
<body> <body>
<h1>Login</h1> <h1>Login</h1>
<div id="content">$(SESSION)</div> <div id="content"><!--SESSION--></div>
</body> </body>
</html> </html>

View File

@ -8,6 +8,7 @@ var bCryptLib = require('bCrypt');
bCrypt = new bCryptLib.bCrypt(); bCrypt = new bCryptLib.bCrypt();
var form = require('form'); var form = require('form');
var http = require('http');
File.makeDirectory("data"); File.makeDirectory("data");
File.makeDirectory("data/auth"); File.makeDirectory("data/auth");
@ -108,6 +109,7 @@ function authHandler(request, response) {
} }
} else { } else {
if (gAccounts[formData.name] && if (gAccounts[formData.name] &&
gAccounts[formData.name].password &&
verifyPassword(formData.password, gAccounts[formData.name].password)) { verifyPassword(formData.password, gAccounts[formData.name].password)) {
writeSession(session, {name: formData.name}); writeSession(session, {name: formData.name});
if (noAdministrator()) { if (noAdministrator()) {
@ -140,6 +142,44 @@ function authHandler(request, response) {
} }
contents += '<div><a href="/login/logout">Logout</a></div>\n'; contents += '<div><a href="/login/logout">Logout</a></div>\n';
} else { } else {
if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) {
html = html.replace("<!--HEAD-->", `
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="${gGlobalSettings['google-signin-client_id']}">
<script>
function onGoogleSignIn(user) {
var token = user.getAuthResponse().id_token;
var xhr = new XMLHttpRequest();
xhr.open("POST", "/login/google");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onload = function() {
if (xhr.status == 200) {
var redirected = false;
if (window.location.search.length) {
var query = window.location.search.substring(1);
var parts = query.split("&");
for (var i = 0; i < parts.length; i++) {
var part = decodeURIComponent(parts[i]);
var key = part.substring(0, part.indexOf('='));
var value = part.substring(part.indexOf('=') + 1);
if (key == "return") {
redirected = true;
window.location.href = value;
}
}
}
if (!redirected) {
window.location.path = "/";
}
} else {
alert(xhr.response);
}
};
xhr.send('token=' + token);
}
</script>
`);
}
contents += '<form method="POST">\n'; contents += '<form method="POST">\n';
if (loginError) { if (loginError) {
contents += "<p>" + loginError + "</p>\n"; contents += "<p>" + loginError + "</p>\n";
@ -157,13 +197,17 @@ function authHandler(request, response) {
contents += '<div><input id="loginButton" type="submit" name="submit" value="Login"></input></div>\n'; contents += '<div><input id="loginButton" type="submit" name="submit" value="Login"></input></div>\n';
contents += '</div>'; contents += '</div>';
contents += '<div id="auth_or"> - or - </div>'; contents += '<div id="auth_or"> - or - </div>';
if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) {
contents += '<div class="g-signin2" data-onsuccess="onGoogleSignIn" data-scope="profile"></div>';
contents += '<div id="auth_or"> - or - </div>';
}
contents += '<div id="auth_guest">\n'; contents += '<div id="auth_guest">\n';
contents += '<input id="guestButton" type="submit" name="submit" value="Proceeed as Guest"></input>\n'; contents += '<input id="guestButton" type="submit" name="submit" value="Proceeed as Guest"></input>\n';
contents += '</div>\n'; contents += '</div>\n';
contents += '</div>\n'; contents += '</div>\n';
contents += '</form>'; contents += '</form>';
} }
var text = html.replace("$(SESSION)", contents); var text = html.replace("<!--SESSION-->", contents);
response.writeHead(200, {"Content-Type": "text/html; charset=utf-6", "Set-Cookie": cookie, "Content-Length": text.length}); response.writeHead(200, {"Content-Type": "text/html; charset=utf-6", "Set-Cookie": cookie, "Content-Length": text.length});
response.end(text); response.end(text);
} }
@ -171,12 +215,47 @@ function authHandler(request, response) {
removeSession(session); removeSession(session);
response.writeHead(303, {"Set-Cookie": "session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT", "Location": "/login" + (request.query ? "?" + request.query : "")}); response.writeHead(303, {"Set-Cookie": "session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT", "Location": "/login" + (request.query ? "?" + request.query : "")});
response.end(); response.end();
} else if (request.uri == "/login/google") {
var formData = form.decodeForm(request.query, form.decodeForm(request.body));
return verifyGoogleToken(formData.token).then(function(user) {
if (user && user.aud == gGlobalSettings['google-signin-client_id']) {
session = newSession();
var userId = user.name;
if (gAccounts[userId] && !gAccounts[userId].google) {
response.writeHead(500, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
response.end("Account already exists and is not a Google account.");
} else {
if (!gAccounts[userId]) {
gAccounts[userId] = {google: true};
File.writeFile(kAccountsFile, JSON.stringify(gAccounts));
if (noAdministrator()) {
makeAdministrator(userId);
}
}
writeSession(session, {name: userId, google: true});
var cookie = "session=" + session + "; path=/; Max-Age=604800";
response.writeHead(200, {"Content-Type": "text/json; charset=utf-8", "Connection": "close", "Set-Cookie": cookie});
response.end(JSON.stringify(user));
}
} else {
response.writeHead(500, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
response.end();
}
});
} else { } else {
response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"}); response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
response.end("Hello, " + request.client.peerName + "."); response.end("Hello, " + request.client.peerName + ".");
} }
} }
function verifyGoogleToken(token) {
return http.get("https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" + token).then(function(response) {
return JSON.parse(response.body);
});
}
function getPermissions(session) { function getPermissions(session) {
var permissions; var permissions;
var entry = readSession(session); var entry = readSession(session);

View File

@ -266,7 +266,15 @@ function updateLogin() {
var a = document.createElement("a"); var a = document.createElement("a");
if (gCredentials && gCredentials.session) { if (gCredentials && gCredentials.session) {
a.appendChild(document.createTextNode("logout " + gCredentials.session.name)); a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
if (gCredentials.session.google) {
gapi.load("auth2", function() {
gapi.auth2.init();
});
a.setAttribute("onclick", "logoutGoogle()");
a.setAttribute("href", "#");
} else {
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url())); a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url()));
}
} else if (window.location.href.indexOf("?guest=1") != -1) { } else if (window.location.href.indexOf("?guest=1") != -1) {
window.location.href = "/login?submit=Proceed+as+Guest&return=" + encodeURIComponent(url()); window.location.href = "/login?submit=Proceed+as+Guest&return=" + encodeURIComponent(url());
} else { } else {
@ -275,6 +283,12 @@ function updateLogin() {
login.appendChild(a); login.appendChild(a);
} }
function logoutGoogle() {
gapi.auth2.getAuthInstance().signOut().then(function() {
window.location.href = "/login/logout?return=" + encodeURIComponent(url());
});
}
var gOriginalInput; var gOriginalInput;
function dragHover(event) { function dragHover(event) {
event.stopPropagation(); event.stopPropagation();

View File

@ -279,6 +279,17 @@ function getProcess(packageOwner, packageName, key, options) {
} }
process.eventHandlers[eventName].push(handler); process.eventHandlers[eventName].push(handler);
}, },
'unregister': function(eventHandle, handler) {
if (process.eventHandlers(eventName)) {
let index = process.eventHandlers[eventName].indexOf(handler);
if (index != -1) {
process.eventHandlers[eventName].splice(index, 1);
}
if (process.eventHandlers[eventName].length == 0) {
delete process.eventHandlers[eventName];
}
}
},
'getUser': getUser.bind(null, process, process), 'getUser': getUser.bind(null, process, process),
'user': getUser(process, process), 'user': getUser(process, process),
}, },

61
core/http.js Normal file
View File

@ -0,0 +1,61 @@
"use strict";
function parseUrl(url) {
// XXX: Hack.
var match = url.match(new RegExp("(\\w+)://([^/]+)?(.*)"));
return {
protocol: match[1],
host: match[2],
path: match[3],
port: match[1] == "http" ? 80 : 443,
};
}
function parseResponse(data) {
var firstLine;
var headers = {};
while (true) {
var endLine = data.indexOf("\r\n");
var line = data.substring(0, endLine);
if (!firstLine) {
firstLine = line;
} else if (!line.length) {
break;
} else {
var colon = line.indexOf(":");
headers[line.substring(colon)] = line.substring(colon + 1);
}
data = data.substring(endLine + 2);
}
return {body: data};
}
function get(url) {
var parsed = parseUrl(url);
return new Promise(function(resolve, reject) {
var socket = new Socket();
var buffer = "";
return socket.connect(parsed.host, parsed.port).then(function() {
socket.read(function(data) {
if (data) {
buffer += data;
} else {
resolve(parseResponse(buffer));
}
});
if (parsed.port == 443) {
return socket.startTls();
}
}).then(function() {
socket.write(`GET ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\n\r\n`);
socket.shutdown();
}).catch(function(error) {
reject(error);
});
});
}
exports.get = get;

View File

@ -4,6 +4,7 @@
<link type="text/css" rel="stylesheet" href="/terminal/style.css"></link> <link type="text/css" rel="stylesheet" href="/terminal/style.css"></link>
<link type="image/png" rel="shortcut icon" href="/terminal/favicon.png"></link> <link type="image/png" rel="shortcut icon" href="/terminal/favicon.png"></link>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!--HEAD-->
</head> </head>
<body> <body>
<div id="body"> <div id="body">

View File

@ -178,6 +178,11 @@ function handler(request, response, packageOwner, packageName, uri) {
found = true; found = true;
var data = File.readFile("core/" + kStaticFiles[i].path); var data = File.readFile("core/" + kStaticFiles[i].path);
if (kStaticFiles[i].uri == "") { if (kStaticFiles[i].uri == "") {
if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) {
data = data.replace("<!--HEAD-->", `
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="${gGlobalSettings['google-signin-client_id']}">`);
}
data = data.replace("$(VIEW_SOURCE)", "/~" + packageOwner + "/" + packageName + "/view"); data = data.replace("$(VIEW_SOURCE)", "/~" + packageOwner + "/" + packageName + "/view");
data = data.replace("$(EDIT_SOURCE)", "/~" + packageOwner + "/" + packageName + "/edit"); data = data.replace("$(EDIT_SOURCE)", "/~" + packageOwner + "/" + packageName + "/edit");
} else if (kStaticFiles[i].uri == "/edit") { } else if (kStaticFiles[i].uri == "/edit") {

View File

@ -15,7 +15,7 @@ if (core.user.credentials.permissions &&
], ],
[ [
["set ", {class: "cyan", value: "key value"}], ["set ", {class: "cyan", value: "key value"}],
["Set global setting key to value."], ["Set global setting key to value. Omit value to unset."],
], ],
[ [
"permission list", "permission list",
@ -45,6 +45,7 @@ var kSimpleSettings = [
'httpPort', 'httpPort',
'httpsPort', 'httpsPort',
'index', 'index',
'google-signin-client_id',
]; ];
function printSettings(settings) { function printSettings(settings) {
@ -73,12 +74,16 @@ function onInput(input) {
terminal.print(" ".repeat(16 - s[i].toString().length), s[i].toString(), " ", i); terminal.print(" ".repeat(16 - s[i].toString().length), s[i].toString(), " ", i);
} }
}); });
} else if (match = /^\s*set\s+(\w+)\s+(.*)/.exec(input)) { } else if (match = /^\s*set\s+(\S+)(?:\s+(.*))?/.exec(input)) {
var key = match[1]; var key = match[1];
var value = match[2]; var value = match[2];
administration.getGlobalSettings().then(function(settings) { administration.getGlobalSettings().then(function(settings) {
if (kSimpleSettings.indexOf(key) != -1) { if (kSimpleSettings.indexOf(key) != -1) {
if (value) {
settings[key] = value; settings[key] = value;
} else {
delete settings[key];
}
administration.setGlobalSettings(settings).then(function() { administration.setGlobalSettings(settings).then(function() {
administration.getGlobalSettings().then(printSettings); administration.getGlobalSettings().then(printSettings);
}).catch(function(error) { }).catch(function(error) {

View File

@ -100,7 +100,10 @@ core.register("onInput", function(input) {
if (input == "new post") { if (input == "new post") {
startNewPost(); startNewPost();
} else if (input == "submit") { } else if (input == "submit") {
submitNewPost().then(renderBlog); submitNewPost().then(function() {
core.unregister("onWindowMessage", onWindowMessage);
renderBlog();
});
} }
}); });
@ -131,8 +134,7 @@ function submitNewPost() {
return gBlog.append(gNewPost); return gBlog.append(gNewPost);
} }
function startNewPost() { function onWindowMessage(message) {
core.register("onWindowMessage", function(message) {
gNewPost = message.message; gNewPost = message.message;
terminal.cork(); terminal.cork();
terminal.select("right"); terminal.select("right");
@ -141,8 +143,10 @@ function startNewPost() {
terminal.print(message.message.entry); terminal.print(message.message.entry);
terminal.print({command: "submit"}); terminal.print({command: "submit"});
terminal.uncork(); terminal.uncork();
}); }
function startNewPost() {
core.register("onWindowMessage", onWindowMessage);
terminal.split([ terminal.split([
{ {
type: "horizontal", type: "horizontal",