diff --git a/core/auth.html b/core/auth.html index 5c52a2f4..df1178dd 100644 --- a/core/auth.html +++ b/core/auth.html @@ -9,9 +9,10 @@ +

Login

-
$(SESSION)
+
diff --git a/core/auth.js b/core/auth.js index b51b64fa..20504f20 100644 --- a/core/auth.js +++ b/core/auth.js @@ -8,6 +8,7 @@ var bCryptLib = require('bCrypt'); bCrypt = new bCryptLib.bCrypt(); var form = require('form'); +var http = require('http'); File.makeDirectory("data"); File.makeDirectory("data/auth"); @@ -108,6 +109,7 @@ function authHandler(request, response) { } } else { if (gAccounts[formData.name] && + gAccounts[formData.name].password && verifyPassword(formData.password, gAccounts[formData.name].password)) { writeSession(session, {name: formData.name}); if (noAdministrator()) { @@ -140,6 +142,44 @@ function authHandler(request, response) { } contents += '
Logout
\n'; } else { + if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) { + html = html.replace("", ` + + + + `); + } contents += '
\n'; if (loginError) { contents += "

" + loginError + "

\n"; @@ -157,13 +197,17 @@ function authHandler(request, response) { contents += '
\n'; contents += ''; contents += '
- or -
'; + if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) { + contents += '
'; + contents += '
- or -
'; + } contents += '
\n'; contents += '\n'; contents += '
\n'; contents += '\n'; contents += '
'; } - var text = html.replace("$(SESSION)", contents); + var text = html.replace("", contents); response.writeHead(200, {"Content-Type": "text/html; charset=utf-6", "Set-Cookie": cookie, "Content-Length": text.length}); response.end(text); } @@ -171,12 +215,47 @@ function authHandler(request, response) { 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.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 { response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"}); 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) { var permissions; var entry = readSession(session); diff --git a/core/client.js b/core/client.js index ad5098ef..4acd34ce 100644 --- a/core/client.js +++ b/core/client.js @@ -266,7 +266,15 @@ function updateLogin() { var a = document.createElement("a"); if (gCredentials && gCredentials.session) { 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) { window.location.href = "/login?submit=Proceed+as+Guest&return=" + encodeURIComponent(url()); } else { @@ -275,6 +283,12 @@ function updateLogin() { login.appendChild(a); } +function logoutGoogle() { + gapi.auth2.getAuthInstance().signOut().then(function() { + window.location.href = "/login/logout?return=" + encodeURIComponent(url()); + }); +} + var gOriginalInput; function dragHover(event) { event.stopPropagation(); diff --git a/core/core.js b/core/core.js index 063161c3..f263c8fe 100644 --- a/core/core.js +++ b/core/core.js @@ -279,6 +279,17 @@ function getProcess(packageOwner, packageName, key, options) { } 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), 'user': getUser(process, process), }, diff --git a/core/http.js b/core/http.js new file mode 100644 index 00000000..aaf92a65 --- /dev/null +++ b/core/http.js @@ -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; diff --git a/core/index.html b/core/index.html index fac5c3ce..63ce3b6f 100644 --- a/core/index.html +++ b/core/index.html @@ -4,6 +4,7 @@ +
diff --git a/core/terminal.js b/core/terminal.js index 8514659d..7e0c2a65 100644 --- a/core/terminal.js +++ b/core/terminal.js @@ -178,6 +178,11 @@ function handler(request, response, packageOwner, packageName, uri) { found = true; var data = File.readFile("core/" + kStaticFiles[i].path); if (kStaticFiles[i].uri == "") { + if (gGlobalSettings && gGlobalSettings['google-signin-client_id']) { + data = data.replace("", ` + + `); + } data = data.replace("$(VIEW_SOURCE)", "/~" + packageOwner + "/" + packageName + "/view"); data = data.replace("$(EDIT_SOURCE)", "/~" + packageOwner + "/" + packageName + "/edit"); } else if (kStaticFiles[i].uri == "/edit") { diff --git a/packages/cory/administration/administration.js b/packages/cory/administration/administration.js index 39a3fc29..99dc9061 100644 --- a/packages/cory/administration/administration.js +++ b/packages/cory/administration/administration.js @@ -15,7 +15,7 @@ if (core.user.credentials.permissions && ], [ ["set ", {class: "cyan", value: "key value"}], - ["Set global setting key to value."], + ["Set global setting key to value. Omit value to unset."], ], [ "permission list", @@ -45,6 +45,7 @@ var kSimpleSettings = [ 'httpPort', 'httpsPort', 'index', + 'google-signin-client_id', ]; function printSettings(settings) { @@ -73,12 +74,16 @@ function onInput(input) { 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 value = match[2]; administration.getGlobalSettings().then(function(settings) { if (kSimpleSettings.indexOf(key) != -1) { - settings[key] = value; + if (value) { + settings[key] = value; + } else { + delete settings[key]; + } administration.setGlobalSettings(settings).then(function() { administration.getGlobalSettings().then(printSettings); }).catch(function(error) { diff --git a/packages/cory/blog/blog.js b/packages/cory/blog/blog.js index 44aaef4e..33515b57 100644 --- a/packages/cory/blog/blog.js +++ b/packages/cory/blog/blog.js @@ -100,7 +100,10 @@ core.register("onInput", function(input) { if (input == "new post") { startNewPost(); } 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); } -function startNewPost() { - core.register("onWindowMessage", function(message) { - gNewPost = message.message; - terminal.cork(); - terminal.select("right"); - terminal.clear(); - terminal.print({style: "font-width: x-large", value: message.message.title}); - terminal.print(message.message.entry); - terminal.print({command: "submit"}); - terminal.uncork(); - }); +function onWindowMessage(message) { + gNewPost = message.message; + terminal.cork(); + terminal.select("right"); + terminal.clear(); + terminal.print({style: "font-width: x-large", value: message.message.title}); + terminal.print(message.message.entry); + terminal.print({command: "submit"}); + terminal.uncork(); +} +function startNewPost() { + core.register("onWindowMessage", onWindowMessage); terminal.split([ { type: "horizontal",