</template>\r
\r
<script setup>\r
+import { onBeforeMount } from "vue";\r
import { RouterView } from "vue-router";\r
+\r
+import { getConfig } from "./api.js";\r
+import { useGlobalStore } from "./globalStore.js";\r
+\r
+const globalStore = useGlobalStore();\r
+\r
+onBeforeMount(() => {\r
+ getConfig()\r
+ .then((response) => {\r
+ globalStore.authType = response.data.authType;\r
+ })\r
+ .catch(function (error) {\r
+ if (!error.handled) {\r
+ // TODO: Trigger unknown error toast\r
+ console.error(error);\r
+ }\r
+ });\r
+});\r
</script>\r
--- /dev/null
+import * as constants from "./constants.js";
+
+import axios from "axios";
+import { getToken } from "./tokenStorage.js";
+import router from "./router.js";
+
+const api = axios.create();
+
+api.interceptors.request.use(
+ // If the request is not for the token endpoint, add the token to the headers.
+ function (config) {
+ if (config.url !== "/api/token") {
+ const token = getToken();
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ function (error) {
+ return Promise.reject(error);
+ },
+);
+
+api.interceptors.response.use(
+ function (response) {
+ return response;
+ },
+ function (error) {
+ // If the response is a 401 Unauthorized, redirect to the login page.
+ if (
+ error.response?.status === 401 &&
+ router.currentRoute.value.name !== "login"
+ ) {
+ const redirectPath = router.currentRoute.value.fullPath;
+ router.push({
+ name: "login",
+ query: { [constants.params.redirect]: redirectPath },
+ });
+ }
+ return Promise.reject(error);
+ },
+);
+
+export function getConfig() {
+ return api.get("/api/config");
+}
--- /dev/null
+// Params
+export const params = {
+ searchTerm: "term",
+ redirect: "redirect",
+ showHighlights: "showHighlights",
+ sortBy: "sortBy",
+};
+
+// Other
+export const alphabet = [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+];
+
+export const searchSortOptions = { score: 0, title: 1, lastModified: 2 };
+
+export const authTypes = {
+ none: "none",
+ readOnly: "read_only",
+ password: "password",
+ totp: "totp",
+};
--- /dev/null
+import { defineStore } from "pinia";
+import { ref } from "vue";
+
+export const useGlobalStore = defineStore("global", () => {
+ const authType = ref("authType");
+
+ return { authType };
+});
import App from "/App.vue";\r
import { createApp } from "vue";\r
+import { createPinia } from 'pinia';\r
import router from "/router.js";\r
\r
const app = createApp(App);\r
+const pinia = createPinia()\r
+\r
app.use(router);\r
+app.use(pinia)\r
app.mount("#app");\r
--- /dev/null
+const tokenStorageKey = "token";
+
+function getCookieString(token) {
+ return `${tokenStorageKey}=${token}; path=/attachments; SameSite=Strict`;
+}
+
+export function setToken(token, persist = false) {
+ document.cookie = getCookieString(token);
+ sessionStorage.setItem(tokenStorageKey, token);
+ if (persist === true) {
+ localStorage.setItem(tokenStorageKey, token);
+ }
+}
+
+export function getToken() {
+ return sessionStorage.getItem(tokenStorageKey);
+}
+
+export function loadToken() {
+ const token = localStorage.getItem(tokenStorageKey);
+ if (token != null) {
+ setToken(token, false);
+ }
+}
+
+export function clearToken() {
+ sessionStorage.removeItem(tokenStorageKey);
+ localStorage.removeItem(tokenStorageKey);
+ document.cookie =
+ getCookieString() + "; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+}
<form @submit.prevent="login" class="flex max-w-80 flex-col items-center">
<TextInput placeholder="Username" class="mb-1" required />
<TextInput placeholder="Password" type="password" class="mb-1" required />
- <TextInput placeholder="2FA Code" class="mb-1" required />
+ <TextInput
+ v-if="globalStore.authType == authTypes.totp"
+ placeholder="2FA Code"
+ class="mb-1"
+ required
+ />
<div class="mb-4 flex">
<input
type="checkbox"
import CustomButton from "../components/CustomButton.vue";
import Logo from "../components/Logo.vue";
import TextInput from "../components/TextInput.vue";
+import { authTypes } from "../constants.js";
+import { useGlobalStore } from "../globalStore.js";
const router = useRouter();
+const globalStore = useGlobalStore();
const rememberMe = ref(false);
function login() {
"@mdi/light-js": "0.2.63",\r
"axios": "1.6.8",\r
"mousetrap": "1.6.5",\r
+ "pinia": "2.1.7",\r
"vue": "3.4.24",\r
"vue-router": "4.3.2"\r
},\r
"node": ">=0.10.0"\r
}\r
},\r
+ "node_modules/pinia": {\r
+ "version": "2.1.7",\r
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",\r
+ "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",\r
+ "dependencies": {\r
+ "@vue/devtools-api": "^6.5.0",\r
+ "vue-demi": ">=0.14.5"\r
+ },\r
+ "funding": {\r
+ "url": "https://github.com/sponsors/posva"\r
+ },\r
+ "peerDependencies": {\r
+ "@vue/composition-api": "^1.4.0",\r
+ "typescript": ">=4.4.4",\r
+ "vue": "^2.6.14 || ^3.3.0"\r
+ },\r
+ "peerDependenciesMeta": {\r
+ "@vue/composition-api": {\r
+ "optional": true\r
+ },\r
+ "typescript": {\r
+ "optional": true\r
+ }\r
+ }\r
+ },\r
+ "node_modules/pinia/node_modules/vue-demi": {\r
+ "version": "0.14.7",\r
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",\r
+ "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",\r
+ "hasInstallScript": true,\r
+ "bin": {\r
+ "vue-demi-fix": "bin/vue-demi-fix.js",\r
+ "vue-demi-switch": "bin/vue-demi-switch.js"\r
+ },\r
+ "engines": {\r
+ "node": ">=12"\r
+ },\r
+ "funding": {\r
+ "url": "https://github.com/sponsors/antfu"\r
+ },\r
+ "peerDependencies": {\r
+ "@vue/composition-api": "^1.0.0-rc.1",\r
+ "vue": "^3.0.0-0 || ^2.6.0"\r
+ },\r
+ "peerDependenciesMeta": {\r
+ "@vue/composition-api": {\r
+ "optional": true\r
+ }\r
+ }\r
+ },\r
"node_modules/pirates": {\r
"version": "4.0.6",\r
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",\r
"@mdi/light-js": "0.2.63",\r
"axios": "1.6.8",\r
"mousetrap": "1.6.5",\r
+ "pinia": "2.1.7",\r
"vue": "3.4.24",\r
"vue-router": "4.3.2"\r
},\r
import { defineConfig } from "vite";\r
import vue from "@vitejs/plugin-vue";\r
\r
+const devApiUrl = "http://127.0.0.1:8000";\r
+\r
export default defineConfig({\r
plugins: [vue()],\r
root: "client",\r
server: {\r
port: 8080,\r
+ proxy: {\r
+ "/api/": {\r
+ target: devApiUrl,\r
+ changeOrigin: true,\r
+ },\r
+ "/docs": {\r
+ target: devApiUrl,\r
+ changeOrigin: true,\r
+ },\r
+ "/openapi.json": {\r
+ target: devApiUrl,\r
+ changeOrigin: true,\r
+ },\r
+ "/health": {\r
+ target: devApiUrl,\r
+ changeOrigin: true,\r
+ },\r
+ },\r
},\r
});\r