From dc13701d79500f494fb14a74bb0db2cc13a68f9b Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Wed, 3 Apr 2024 21:17:27 +0200 Subject: [PATCH] Config UI: Add authentication (#11732) --- assets/js/api.js | 9 +- assets/js/auth.js | 58 ++++++ assets/js/components/Config/GeneralConfig.vue | 10 +- assets/js/components/GenericModal.vue | 35 ++-- assets/js/components/HelpModal.vue | 2 +- assets/js/components/LoginModal.vue | 98 ++++++++++ assets/js/components/PasswordModal.vue | 177 ++++++++++++++++++ assets/js/components/TopNavigation.vue | 17 ++ assets/js/router.js | 14 +- assets/js/theme.js | 1 - assets/js/views/App.vue | 20 +- cmd/password.go | 14 ++ cmd/password_reset.go | 47 +++++ cmd/password_set.go | 49 +++++ core/keys/auth.go | 6 + core/keys/site.go | 1 + go.mod | 2 +- i18n/de.toml | 21 +++ i18n/en.toml | 22 +++ server/db/settings/api.go | 8 + server/db/settings/mock.go | 66 +++++++ server/db/settings/setting.go | 11 ++ server/http.go | 44 +++-- server/http_auth.go | 138 ++++++++++++++ tests/auth.spec.js | 106 +++++++++++ tests/basics.spec.js | 2 +- tests/config.spec.js | 18 +- tests/evcc.js | 20 +- tests/heating.spec.js | 2 +- tests/limits.spec.js | 2 +- tests/modals.spec.js | 16 +- tests/password.sql | 8 + tests/plan.spec.js | 2 +- tests/sessions.spec.js | 2 +- tests/smart-cost.spec.js | 2 +- tests/statistics.spec.js | 2 +- tests/vehicle-error.spec.js | 2 +- tests/vehicle-settings.spec.js | 2 +- util/auth/auth.go | 160 ++++++++++++++++ util/auth/auth_test.go | 82 ++++++++ 40 files changed, 1241 insertions(+), 57 deletions(-) create mode 100644 assets/js/auth.js create mode 100644 assets/js/components/LoginModal.vue create mode 100644 assets/js/components/PasswordModal.vue create mode 100644 cmd/password.go create mode 100644 cmd/password_reset.go create mode 100644 cmd/password_set.go create mode 100644 core/keys/auth.go create mode 100644 server/db/settings/api.go create mode 100644 server/db/settings/mock.go create mode 100644 server/http_auth.go create mode 100644 tests/auth.spec.js create mode 100644 tests/password.sql create mode 100644 util/auth/auth.go create mode 100644 util/auth/auth_test.go diff --git a/assets/js/api.js b/assets/js/api.js index 9ba3b5b46..45859733f 100644 --- a/assets/js/api.js +++ b/assets/js/api.js @@ -1,4 +1,5 @@ import axios from "axios"; +import { openLoginModal } from "./auth"; const { protocol, hostname, port, pathname } = window.location; @@ -15,13 +16,19 @@ const api = axios.create({ api.interceptors.response.use( (response) => response, (error) => { + // handle unauthorized errors + if (error.response?.status === 401) { + openLoginModal(); + return Promise.reject(error); + } + const message = [`${error.message}.`]; if (error.response?.data?.error) { message.push(`${error.response.data.error}.`); } if (error.config) { const method = error.config.method.toUpperCase(); - const url = error.config.baseURL + error.config.url; + const url = error.request.responseURL; message.push(`${method} ${url}`); } window.app.raise({ message }); diff --git a/assets/js/auth.js b/assets/js/auth.js new file mode 100644 index 000000000..ba85b006d --- /dev/null +++ b/assets/js/auth.js @@ -0,0 +1,58 @@ +import { reactive, watch } from "vue"; +import api from "./api"; +import Modal from "bootstrap/js/dist/modal"; + +const auth = reactive({ + configured: true, + loggedIn: false, +}); + +export async function updateAuthStatus() { + try { + const res = await api.get("/auth/status", { + validateStatus: (code) => [200, 501].includes(code), + }); + if (res.status === 501) { + auth.configured = false; + } + if (res.status === 200) { + auth.configured = true; + auth.loggedIn = res.data === true; + } + } catch (e) { + console.log("unable to fetch auth status", e); + } +} + +export async function logout() { + try { + await api.post("/auth/logout"); + await updateAuthStatus(); + } catch (e) { + console.log("unable to logout", e); + } +} + +export function isLoggedIn() { + return auth.loggedIn; +} + +export function isConfigured() { + return auth.configured; +} + +export function openLoginModal() { + const modal = Modal.getOrCreateInstance(document.getElementById("loginModal")); + modal.show(); +} + +// show/hide password modal based on auth status +watch( + () => auth.configured, + (configured) => { + const modal = Modal.getOrCreateInstance(document.getElementById("passwordModal")); + configured ? modal.hide() : modal.show(); + } +); + +export default auth; diff --git a/assets/js/components/Config/GeneralConfig.vue b/assets/js/components/Config/GeneralConfig.vue index cf9ef3bb6..ee4eba811 100644 --- a/assets/js/components/Config/GeneralConfig.vue +++ b/assets/js/components/Config/GeneralConfig.vue @@ -27,11 +27,17 @@ -
+
Password
******* - edit + + edit
diff --git a/assets/js/components/GenericModal.vue b/assets/js/components/GenericModal.vue index fbd3a0d5c..8e4c0ba0c 100644 --- a/assets/js/components/GenericModal.vue +++ b/assets/js/components/GenericModal.vue @@ -4,10 +4,12 @@ :id="id" ref="modal" class="modal fade text-dark" - data-bs-backdrop="true" + :class="sizeClass" tabindex="-1" role="dialog" aria-hidden="true" + :data-bs-backdrop="uncloseable ? 'static' : 'true'" + :data-bs-keyboard="uncloseable ? 'false' : 'true'" :data-testid="dataTestid" >