From 8f9306c52d312af9e86cdf488bb67e24a2517bfc Mon Sep 17 00:00:00 2001 From: Robert Kaiser Date: Mon, 18 Sep 2017 01:46:10 +0200 Subject: [PATCH] implement logging into the new Lantea backend --- auth-done.html | 9 --- index.html | 6 ++ js/ui.js | 176 ++++++++++++++++++++++++++++++++++++++++++++-- login.html | 28 ++++++++ manifest.appcache | 2 +- style/lantea.css | 26 ++++++- 6 files changed, 230 insertions(+), 17 deletions(-) delete mode 100644 auth-done.html create mode 100644 login.html diff --git a/auth-done.html b/auth-done.html deleted file mode 100644 index 88db9f1..0000000 --- a/auth-done.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/index.html b/index.html index fd1dbe5..53ace7d 100644 --- a/index.html +++ b/index.html @@ -39,6 +39,12 @@

+

+ + + + +

diff --git a/js/ui.js b/js/ui.js index 8dc3f78..5d631ce 100644 --- a/js/ui.js +++ b/js/ui.js @@ -12,6 +12,8 @@ var gUIHideCountdown = 0; var gWaitCounter = 0; var gTrackUpdateInterval; var gAction, gActionLabel; +var gBackendURL = "https://backend.lantea.kairo.at"; +var gAuthClientID = "lantea"; var gOSMAPIURL = "https://api.openstreetmap.org/"; var gOSMOAuthData = { oauth_consumer_key: "6jjWwlbhGqyYeCdlFE1lTGG6IRGOv1yKpFxkcq2z", @@ -21,6 +23,11 @@ var gOSMOAuthData = { } window.onload = function() { + if (/\/login\.html/.test(window.location)) { + // If we are in the login window, call a function to complete the process and don't do anything else here. + completeLoginWindow(); + return; + } gAction = document.getElementById("action"); gActionLabel = document.getElementById("actionlabel"); @@ -54,10 +61,29 @@ window.onload = function() { document.getElementById("saveTrackButton").classList.add("debugHide"); } - // Without OAuth, the login data is useless - //document.getElementById("uploadSettingsArea").classList.remove("debugHide"); - // As login data is useless for now, always enable upload button - document.getElementById("uploadTrackButton").disabled = false; + // Set backend URL in a way that it works for testing on localhost as well as + // both the lantea.kairo.at and lantea-dev.kairo.at deployments. + if (window.location.host == "localhost") { + gBackendURL = window.location.protocol + '//' + window.location.host + "/lantea-backend/"; + } + else { + gBackendURL = window.location.protocol + '//' + "backend." + window.location.host + "/"; + } + // Make sure to use a different login client ID for the -dev setup. + if (/\-dev\./.test(window.location.host)) { + gAuthClientID += "-dev"; + } + + // Set up the login area. + document.getElementById("loginbtn").onclick = startLogin; + document.getElementById("logoutbtn").onclick = doLogout; + prepareLoginButton(function() { + // Anything that needs the backend should only be triggered from in here. + // That makes sure that the first call the the backend is oauth_state and no other is running in parallel. + // If we call multiple backend methods at once and no session is open, we create multiple sessions, which calls for confusion later on. + + // Call any UI preparation that needs the backend. + }); if (gDebug) { // Note that GPX upload returns an error 500 on the dev API right now. @@ -97,6 +123,114 @@ window.onresize = function() { gMap.resizeAndDraw(); } +function startLogin() { + var authURL = authData["url"] + "authorize?response_type=code&client_id=" + gAuthClientID + "&scope=email" + + "&state=" + authData["state"] + "&redirect_uri=" + encodeURIComponent(getRedirectURI()); + if (window.open(authURL, "KaiRoAuth", 'height=450,width=600')) { + console.log("Sign In window open."); + } + else { + console.log("Opening Sign In window failed."); + } +} + +function getRedirectURI() { + return window.location.protocol + '//' + window.location.host + window.location.pathname + "login.html"; +} + +function doLogout() { + fetchBackend("logout", "GET", null, + function(aResult, aStatus) { + if (aStatus < 400) { + prepareLoginButton(); + } + else { + console.log("Backend issue trying to log out."); + } + }, + {} + ); +} + +function prepareLoginButton(aCallback) { + fetchBackend("oauth_state", "GET", null, + function(aResult, aStatus) { + if (aStatus == 200) { + if (aResult["logged_in"]) { + userData = { + "email": aResult["email"], + "permissions": aResult["permissions"], + }; + authData = null; + displayLogin(); + } + else { + authData = {"state": aResult["state"], "url": aResult["url"]}; + userData = null; + displayLogout(); + } + } + else { + console.log("Backend issue fetching OAuth state."); + } + if (aCallback) { aCallback(); } + }, + {} + ); +} + +function completeLoginWindow() { + if (window.opener) { + window.opener.finishLogin(getParameterByName("code"), getParameterByName("state")); + window.close(); + } + else { + document.getElementById("logininfo").textContent = "You have called this document outside of the login flow, which is not supported."; + } +} + +function finishLogin(aCode, aState) { + if (aState == authData["state"]) { + fetchBackend("login?code=" + aCode + "&state=" + aState + "&redirect_uri=" + encodeURIComponent(getRedirectURI()), "GET", null, + function(aResult, aStatus) { + if (aStatus == 200) { + userData = { + "email": aResult["email"], + "permissions": aResult["permissions"], + }; + displayLogin(); + } + else { + console.log("Login error " + aStatus + ": " + aResult["message"]); + prepareLoginButton(); + } + }, + {} + ); + } + else { + console.log("Login state did not match, not continuing with login."); + } +} + +function displayLogin() { + document.getElementById("loginbtn").classList.add("hidden"); + document.getElementById("logindesc").classList.add("hidden"); + document.getElementById("username").classList.remove("hidden"); + document.getElementById("username").textContent = userData.email; + document.getElementById("uploadTrackButton").disabled = false; + document.getElementById("logoutbtn").classList.remove("hidden"); +} + +function displayLogout() { + document.getElementById("logoutbtn").classList.add("hidden"); + document.getElementById("username").classList.add("hidden"); + document.getElementById("username").textContent = ""; + document.getElementById("uploadTrackButton").disabled = true; + document.getElementById("loginbtn").classList.remove("hidden"); + document.getElementById("logindesc").classList.remove("hidden"); +} + function initDB(aEvent) { // Open DB. if (aEvent) @@ -608,3 +742,37 @@ var gTrackStore = { } } }; + +function fetchBackend(aEndpoint, aMethod, aSendData, aCallback, aCallbackForwards) { + var XHR = new XMLHttpRequest(); + XHR.onreadystatechange = function() { + if (XHR.readyState == 4) { + // State says we are fully loaded. + var result = {}; + if (XHR.getResponseHeader("Content-Type") == "application/json") { + // Got a JSON object, see if we have success. + result = JSON.parse(XHR.responseText); + } + else { + result = XHR.responseText; + } + aCallback(result, XHR.status, aCallbackForwards); + } + }; + XHR.open(aMethod, gBackendURL + aEndpoint, true); + //XHR.setRequestHeader("Accept", "application/json"); + try { + XHR.send(aSendData); // Send actual form data. + } + catch (e) { + aCallback(e, 500, aCallbackForwards); + } +} + +function getParameterByName(aName) { + // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript + name = aName.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} diff --git a/login.html b/login.html new file mode 100644 index 0000000..35f5ec4 --- /dev/null +++ b/login.html @@ -0,0 +1,28 @@ + + + + + + + + + Filmingsites Login + + + + + + +

+ +

Lantea Login

+
+ +
+This window should close in a second. +
+ + diff --git a/manifest.appcache b/manifest.appcache index 27fc338..f553e25 100644 --- a/manifest.appcache +++ b/manifest.appcache @@ -1,6 +1,6 @@ CACHE MANIFEST -# 2016-09-04 +# 2017-09-17 index.html auth-done.html manifest.webapp diff --git a/style/lantea.css b/style/lantea.css index f1785fd..c13f814 100644 --- a/style/lantea.css +++ b/style/lantea.css @@ -12,6 +12,10 @@ h1 { display: none; } +.hidden { + display: none; +} + #dialogArea, .overlayArea { position: absolute; @@ -24,6 +28,7 @@ h1 { .overlayArea.hidden { opacity: 0; transition-duration: 1s; + display: block; } #dialogArea:-moz-system-metric(touch-enabled), @@ -33,11 +38,14 @@ h1 { } #dialogArea input[type="button"], +#dialogArea button, #dialogArea input[type="text"], #dialogArea select, .overlayArea input[type="button"], +.overlayArea button, .overlayArea select, .menuDrawer input[type="button"], +.menuDrawer button, .menuDrawer select { font-size: inherit; } @@ -209,7 +217,8 @@ h1 { } /* Custom button design */ -.menuDrawer input[type="button"] { +.menuDrawer input[type="button"], +.menuDrawer button { text-align: center; vertical-align: middle; background-image: none; @@ -220,12 +229,21 @@ h1 { border-radius: 3px; } -.menuDrawer input[type="button"]:hover { +.menuDrawer input[type="button"]:disabled, +.menuDrawer button:disabled { + background-color: rgba(204, 204, 204, .1); + color: rgba(204, 204, 204, .5); + border: 1px solid rgba(255, 255, 255, .5); +} + +.menuDrawer input[type="button"]:not(:disabled):hover, +.menuDrawer button:not(:disabled):hover { background-color: rgba(204, 204, 204, .4); border: 1px solid #FFFFFF; } -.menuDrawer input[type="button"]:active { +.menuDrawer input[type="button"]:not(:disabled):active, +.menuDrawer button:not(:disabled):active { background-color: rgba(204, 204, 255, .3); color: #FFCCAA; padding-top: 2px; @@ -324,6 +342,7 @@ h1 { #copyright.hidden { opacity: 0; + display: block; } #dialogArea { @@ -342,6 +361,7 @@ h1 { #dialogArea.hidden { top: -100%; + display: block; } .dialogTitle { -- 2.43.0