Cleanup auth flow

This commit is contained in:
Pavel Djundik 2019-11-05 21:29:51 +02:00
parent fc1c9568e2
commit a1f183f216
11 changed files with 115 additions and 120 deletions

View file

@ -85,7 +85,7 @@ export default {
storage.set("user", values.user);
socket.emit("auth", values);
socket.emit("auth:perform", values);
},
getStoredUser() {
return storage.get("user");

View file

@ -53,13 +53,13 @@
<div id="loading-status-container">
<img src="img/logo-vertical-transparent-bg.svg" class="logo" alt="The Lounge" width="256" height="170">
<img src="img/logo-vertical-transparent-bg-inverted.svg" class="logo-inverted" alt="The Lounge" width="256" height="170">
<p id="loading-page-message"><a href="https://enable-javascript.com/" target="_blank" rel="noopener">Your JavaScript must be enabled.</a></p>
<p id="loading-page-message">The Lounge requires a modern browser with JavaScript enabled.</p>
</div>
<div id="loading-reload-container">
<p id="loading-slow">This is taking longer than it should, there might be connectivity issues.</p>
<button id="loading-reload" class="btn">Reload page</button>
</div>
<script async src="js/loading-error-handlers.js?v=<%- cacheBust %>"></script>
<script src="js/loading-error-handlers.js?v=<%- cacheBust %>"></script>
</div>
</div>
<div id="viewport"></div>

View file

@ -1,4 +1,4 @@
/* eslint strict: 0, no-var: 0 */
/* eslint strict: 0 */
"use strict";
/*
@ -9,71 +9,66 @@
*/
(function() {
var msg = document.getElementById("loading-page-message");
const msg = document.getElementById("loading-page-message");
msg.textContent = "Loading the app…";
if (msg) {
msg.textContent = "Loading the app…";
document
.getElementById("loading-reload")
.addEventListener("click", () => location.reload(true));
document.getElementById("loading-reload").addEventListener("click", function() {
location.reload(true);
});
}
var displayReload = function displayReload() {
var loadingReload = document.getElementById("loading-reload");
const displayReload = () => {
const loadingReload = document.getElementById("loading-reload");
if (loadingReload) {
loadingReload.style.visibility = "visible";
}
};
var loadingSlowTimeout = setTimeout(function() {
var loadingSlow = document.getElementById("loading-slow");
// The parent element, #loading, is being removed when the app is loaded.
// Since the timer is not cancelled, `loadingSlow` can be not found after
// 5s. Wrap everything in this block to make sure nothing happens if the
// element does not exist (i.e. page has loaded).
if (loadingSlow) {
loadingSlow.style.visibility = "visible";
displayReload();
}
const loadingSlowTimeout = setTimeout(() => {
const loadingSlow = document.getElementById("loading-slow");
loadingSlow.style.visibility = "visible";
displayReload();
}, 5000);
window.g_LoungeErrorHandler = function LoungeErrorHandler(e) {
var message = document.getElementById("loading-page-message");
message.textContent =
"An error has occurred that prevented the client from loading correctly.";
const errorHandler = (e) => {
msg.textContent = "An error has occurred that prevented the client from loading correctly.";
var summary = document.createElement("summary");
const summary = document.createElement("summary");
summary.textContent = "More details";
var data = document.createElement("pre");
const data = document.createElement("pre");
data.textContent = e.message; // e is an ErrorEvent
var info = document.createElement("p");
const info = document.createElement("p");
info.textContent = "Open the developer tools of your browser for more information.";
var details = document.createElement("details");
const details = document.createElement("details");
details.appendChild(summary);
details.appendChild(data);
details.appendChild(info);
message.parentNode.insertBefore(details, message.nextSibling);
msg.parentNode.insertBefore(details, msg.nextSibling);
window.clearTimeout(loadingSlowTimeout);
displayReload();
};
window.addEventListener("error", window.g_LoungeErrorHandler);
window.addEventListener("error", errorHandler);
window.g_TheLoungeRemoveLoading = () => {
delete window.g_TheLoungeRemoveLoading;
window.clearTimeout(loadingSlowTimeout);
window.removeEventListener("error", errorHandler);
document.getElementById("loading").remove();
};
// Trigger early service worker registration
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("service-worker.js");
// Handler for messages coming from the service worker
var messageHandler = function ServiceWorkerMessageHandler(event) {
const messageHandler = (event) => {
if (event.data.type === "fetch-error") {
window.g_LoungeErrorHandler({
errorHandler({
message: `Service worker failed to fetch an url: ${event.data.message}`,
});

View file

@ -25,7 +25,7 @@ const router = new VueRouter({
});
router.afterEach((to) => {
if (router.app.initialized) {
if (store.state.appLoaded) {
router.app.closeSidebarIfNeeded();
}

View file

@ -5,76 +5,89 @@ const socket = require("../socket");
const storage = require("../localStorage");
const {getActiveWindowComponent} = require("../vue");
const store = require("../store").default;
let lastServerHash = -1;
let lastServerHash = null;
socket.on("auth", function(data) {
socket.on("auth:success", function() {
store.commit("currentUserVisibleError", "Loading messages…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
socket.on("auth:failed", function() {
storage.remove("token");
if (store.state.appLoaded) {
return reloadPage("Authentication failed, reloading…");
}
// TODO: This will most likely fail getActiveWindowComponent
showSignIn();
// TODO: getActiveWindowComponent is the SignIn component, find a better way to set this
getActiveWindowComponent().errorShown = true;
getActiveWindowComponent().inFlight = false;
});
socket.on("auth:start", function(serverHash) {
// If we reconnected and serverHash differs, that means the server restarted
// And we will reload the page to grab the latest version
if (lastServerHash > -1 && data.serverHash > -1 && data.serverHash !== lastServerHash) {
socket.disconnect();
store.commit("isConnected", false);
store.commit("currentUserVisibleError", "Server restarted, reloading…");
location.reload(true);
return;
if (lastServerHash && serverHash !== lastServerHash) {
return reloadPage("Server restarted, reloading…");
}
if (data.serverHash > -1) {
lastServerHash = data.serverHash;
} else {
getActiveWindowComponent().inFlight = false;
}
lastServerHash = serverHash;
let token;
const user = storage.get("user");
const token = storage.get("token");
const doFastAuth = user && token;
if (!data.success) {
if (store.state.activeWindow !== "SignIn") {
socket.disconnect();
store.commit("isConnected", false);
store.commit("currentUserVisibleError", "Authentication failed, reloading…");
location.reload();
return;
}
// If we reconnect and no longer have a stored token, reload the page
if (store.state.appLoaded && !doFastAuth) {
return reloadPage("Authentication failed, reloading…");
}
storage.remove("token");
// If we have user and token stored, perform auth without showing sign-in first
if (doFastAuth) {
store.commit("currentUserVisibleError", "Authorizing…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
getActiveWindowComponent().errorShown = true;
} else if (user) {
token = storage.get("token");
let lastMessage = -1;
if (token) {
store.commit("currentUserVisibleError", "Authorizing…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
for (const network of store.state.networks) {
for (const chan of network.channels) {
if (chan.messages.length > 0) {
const id = chan.messages[chan.messages.length - 1].id;
let lastMessage = -1;
for (const network of store.state.networks) {
for (const chan of network.channels) {
if (chan.messages.length > 0) {
const id = chan.messages[chan.messages.length - 1].id;
if (lastMessage < id) {
lastMessage = id;
}
if (lastMessage < id) {
lastMessage = id;
}
}
}
const openChannel =
(store.state.activeChannel && store.state.activeChannel.channel.id) || null;
socket.emit("auth", {user, token, lastMessage, openChannel});
}
const openChannel =
(store.state.activeChannel && store.state.activeChannel.channel.id) || null;
socket.emit("auth:perform", {user, token, lastMessage, openChannel});
} else {
showSignIn();
}
});
function showSignIn() {
// TODO: this flashes grey background because it takes a little time for vue to mount signin
if (window.g_TheLoungeRemoveLoading) {
window.g_TheLoungeRemoveLoading();
}
if (token) {
return;
}
$("#loading").remove();
$("#footer")
.find(".sign-in")
.trigger("click", {
pushState: false,
});
});
}
function reloadPage(message) {
socket.disconnect();
store.commit("currentUserVisibleError", message);
location.reload(true);
}

View file

@ -10,17 +10,14 @@ const router = require("../router");
const store = require("../store").default;
socket.on("init", function(data) {
store.commit("currentUserVisibleError", "Rendering…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
store.commit("networks", mergeNetworkData(data.networks));
store.commit("isConnected", true);
store.commit("currentUserVisibleError", null);
if (!vueApp.initialized) {
if (!store.state.appLoaded) {
router.initialize();
vueApp.onSocketInit();
store.commit("appLoaded");
if (data.token) {
storage.set("token", data.token);
@ -43,12 +40,10 @@ socket.on("init", function(data) {
vueApp.setUserlist(isUserlistOpen === "true");
$(document.body).removeClass("signed-out");
$("#loading").remove();
document.body.classList.remove("signed-out");
if (window.g_LoungeErrorHandler) {
window.removeEventListener("error", window.g_LoungeErrorHandler);
window.g_LoungeErrorHandler = null;
if (window.g_TheLoungeRemoveLoading) {
window.g_TheLoungeRemoveLoading();
}
if (!vueApp.$route.name || vueApp.$route.name === "SignIn") {

View file

@ -38,21 +38,18 @@ socket.on("connect", function() {
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
socket.on("authorized", function() {
store.commit("currentUserVisibleError", "Loading messages…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
function handleDisconnect(data) {
const message = data.message || data;
store.commit("isConnected", false);
store.commit("currentUserVisibleError", `Waiting to reconnect… (${message})`);
$("#loading-page-message").text(store.state.currentUserVisibleError);
// If the server shuts down, socket.io skips reconnection
// and we have to manually call connect to start the process
if (socket.io.skipReconnect) {
// However, do not reconnect if TL client manually closed the connection
if (socket.io.skipReconnect && message !== "io client disconnect") {
requestIdleCallback(() => socket.connect(), 2000);
}
}

View file

@ -11,6 +11,7 @@ const store = new Vuex.Store({
settings,
},
state: {
appLoaded: false,
activeChannel: null,
currentUserVisibleError: null,
desktopNotificationState: "unsupported",
@ -31,6 +32,9 @@ const store = new Vuex.Store({
versionDataExpired: false,
},
mutations: {
appLoaded(state) {
state.appLoaded = true;
},
activeChannel(state, channel) {
state.activeChannel = channel;
},

View file

@ -15,9 +15,6 @@ const appName = document.title;
const vueApp = new Vue({
el: "#viewport",
data: {
initialized: false,
},
router,
mounted() {
if (navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)) {
@ -43,10 +40,6 @@ const vueApp = new Vue({
}, 1);
},
methods: {
onSocketInit() {
this.initialized = true;
this.$store.commit("isConnected", true);
},
setSidebar(state) {
this.$store.commit("sidebarOpen", state);

View file

@ -174,11 +174,8 @@ module.exports = function(options = {}) {
if (Helper.config.public) {
performAuthentication.call(socket, {});
} else {
socket.emit("auth", {
serverHash: serverHash,
success: true,
});
socket.on("auth", performAuthentication);
socket.on("auth:perform", performAuthentication);
socket.emit("auth:start", serverHash);
}
});
@ -337,7 +334,8 @@ function indexRequest(req, res) {
}
function initializeClient(socket, client, token, lastMessage, openChannel) {
socket.emit("authorized");
socket.off("auth:perform", performAuthentication);
socket.emit("auth:success");
client.clientAttach(socket.id, token);
@ -789,7 +787,7 @@ function performAuthentication(data) {
);
}
socket.emit("auth", {success: false});
socket.emit("auth:failed");
return;
}

View file

@ -72,7 +72,7 @@ describe("Server", function() {
});
it("should emit authorized message", (done) => {
client.on("authorized", done);
client.on("auth:success", done);
});
it("should create network", (done) => {