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:
parent
7b112c5376
commit
cbf54eaa17
@ -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>
|
||||||
|
81
core/auth.js
81
core/auth.js
@ -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);
|
||||||
|
@ -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));
|
||||||
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url()));
|
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()));
|
||||||
|
}
|
||||||
} 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();
|
||||||
|
11
core/core.js
11
core/core.js
@ -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
61
core/http.js
Normal 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;
|
@ -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">
|
||||||
|
@ -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") {
|
||||||
|
@ -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) {
|
||||||
settings[key] = value;
|
if (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) {
|
||||||
|
@ -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,18 +134,19 @@ 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");
|
terminal.clear();
|
||||||
terminal.clear();
|
terminal.print({style: "font-width: x-large", value: message.message.title});
|
||||||
terminal.print({style: "font-width: x-large", value: message.message.title});
|
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",
|
||||||
|
Loading…
Reference in New Issue
Block a user