From fb51755e325d96446b516cf416621c7799abc233 Mon Sep 17 00:00:00 2001 From: Felipe Martin <812088+fmartingr@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:51:45 +0100 Subject: [PATCH] refactor: move login.html into a login component (#1017) * feat: Add login component JavaScript file * feat: Create login component and refactor login view * refactor: Convert login to single-page application with dynamic component rendering * feat: Enhance session validation and login form display logic * fix: Resolve Vue app mounting and method duplication issues * fix: Prevent null reference error when focusing username input * fix: Initialize `isLoggedIn` to true to show login form during async check * refactor: Improve session validation and login flow logic * fix: Adjust login component visibility and initial login state * feat: Add login form template to login component * feat: Update login template to match original login.html design * fix: Resolve login view rendering and state management issues * refactor: Remove login route from frontend routes * refactor: Remove login-footer from login component template * fix: Modify logout to show login form without redirecting * refactor: Remove /login route test for SPA architecture * refactor: delete login.html file * style: Remove extra blank line in frontend_test.go * chore: run make style changes --- internal/http/routes/frontend.go | 6 - internal/http/routes/frontend_test.go | 7 - internal/view/assets/js/component/login.js | 152 +++++++++++++++++++ internal/view/index.html | 76 ++++++++-- internal/view/login.html | 163 --------------------- 5 files changed, 217 insertions(+), 187 deletions(-) create mode 100644 internal/view/assets/js/component/login.js delete mode 100644 internal/view/login.html diff --git a/internal/http/routes/frontend.go b/internal/http/routes/frontend.go index 364a5d2a4..5947518cd 100644 --- a/internal/http/routes/frontend.go +++ b/internal/http/routes/frontend.go @@ -48,12 +48,6 @@ type FrontendRoutes struct { func (r *FrontendRoutes) Setup(e *gin.Engine) { group := e.Group("/") - group.GET("/login", func(ctx *gin.Context) { - ctx.HTML(http.StatusOK, "login.html", gin.H{ - "RootPath": r.cfg.Http.RootPath, - "Version": model.BuildVersion, - }) - }) group.GET("/", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{ "RootPath": r.cfg.Http.RootPath, diff --git a/internal/http/routes/frontend_test.go b/internal/http/routes/frontend_test.go index 8ba4f1ad7..80934db8d 100644 --- a/internal/http/routes/frontend_test.go +++ b/internal/http/routes/frontend_test.go @@ -31,13 +31,6 @@ func TestFrontendRoutes(t *testing.T) { require.Equal(t, 200, w.Code) }) - t.Run("/login", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) - g.ServeHTTP(w, req) - require.Equal(t, 200, w.Code) - }) - t.Run("/css/style.css", func(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/assets/css/style.css", nil) diff --git a/internal/view/assets/js/component/login.js b/internal/view/assets/js/component/login.js new file mode 100644 index 000000000..9150f0ee9 --- /dev/null +++ b/internal/view/assets/js/component/login.js @@ -0,0 +1,152 @@ +const template = ` +
+

{{error}}

+
+
+
+ +

simple bookmark manager

+
+
+ + + + + +
+ +
+
+
+`; + +export default { + name: "login-view", + template, + data() { + return { + error: "", + loading: false, + username: "", + password: "", + remember: false, + }; + }, + emits: ["login-success"], + methods: { + async getErrorMessage(err) { + switch (err.constructor) { + case Error: + return err.message; + case Response: + var text = await err.text(); + + // Handle new error messages + if (text[0] == "{") { + var json = JSON.parse(text); + return json.message; + } + return `${text} (${err.status})`; + default: + return err; + } + }, + parseJWT(token) { + try { + return JSON.parse(atob(token.split(".")[1])); + } catch (e) { + return null; + } + }, + login() { + // Get values directly from the form + const usernameInput = document.querySelector("#username"); + const passwordInput = document.querySelector("#password"); + this.username = usernameInput ? usernameInput.value : this.username; + this.password = passwordInput ? passwordInput.value : this.password; + + // Validate input + if (this.username === "") { + this.error = "Username must not empty"; + return; + } + + // Remove old cookie + document.cookie = `session-id=; Path=${ + new URL(document.baseURI).pathname + }; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`; + + // Send request + this.loading = true; + + fetch(new URL("api/v1/auth/login", document.baseURI), { + method: "post", + body: JSON.stringify({ + username: this.username, + password: this.password, + remember_me: this.remember == 1 ? true : false, + }), + headers: { "Content-Type": "application/json" }, + }) + .then((response) => { + if (!response.ok) throw response; + return response.json(); + }) + .then((json) => { + // Save session id + document.cookie = `session-id=${json.message.session}; Path=${ + new URL(document.baseURI).pathname + }; Expires=${json.message.expires}`; + document.cookie = `token=${json.message.token}; Path=${ + new URL(document.baseURI).pathname + }; Expires=${json.message.expires}`; + + // Save account data + localStorage.setItem("shiori-token", json.message.token); + localStorage.setItem( + "shiori-account", + JSON.stringify(this.parseJWT(json.message.token).account), + ); + + this.visible = false; + this.$emit("login-success"); + }) + .catch((err) => { + this.loading = false; + this.getErrorMessage(err).then((msg) => { + this.error = msg; + }); + }); + }, + }, + mounted() { + // Clear any existing cookies + document.cookie = `session-id=; Path=${ + new URL(document.baseURI).pathname + }; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`; + document.cookie = `token=; Path=${ + new URL(document.baseURI).pathname + }; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`; + + // Clear local storage + localStorage.removeItem("shiori-account"); + localStorage.removeItem("shiori-token"); + + // wasn't working all the time, so I'm putting this here as a fallback + this.$nextTick(() => { + const usernameInput = document.querySelector("#username"); + if (usernameInput) { + usernameInput.focus(); + } + }); + }, +}; diff --git a/internal/view/index.html b/internal/view/index.html index ab6695573..e8f0be25d 100644 --- a/internal/view/index.html +++ b/internal/view/index.html @@ -21,7 +21,9 @@ -
+
+ + - - - - - - - - - -