diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c64177..9b15ac2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "dotnet.defaultSolution": "FiscalOS.slnx", "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "tsconfig.json": "tsconfig.*.json, env.d.ts, typed-router.d.ts", diff --git a/src/FiscalOS.Web/package-lock.json b/src/FiscalOS.Web/package-lock.json index aa73a7b..bb26642 100644 --- a/src/FiscalOS.Web/package-lock.json +++ b/src/FiscalOS.Web/package-lock.json @@ -8,6 +8,7 @@ "name": "fiscalos-web", "version": "0.0.0", "dependencies": { + "jwt-decode": "^4.0.0", "pinia": "^3.0.4", "vue": "beta", "vue-router": "^5.0.2" @@ -5145,6 +5146,15 @@ "dev": true, "license": "MIT" }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/src/FiscalOS.Web/package.json b/src/FiscalOS.Web/package.json index bf50d1c..d943a11 100644 --- a/src/FiscalOS.Web/package.json +++ b/src/FiscalOS.Web/package.json @@ -17,6 +17,7 @@ "format": "prettier --write --experimental-cli src/" }, "dependencies": { + "jwt-decode": "^4.0.0", "pinia": "^3.0.4", "vue": "beta", "vue-router": "^5.0.2" diff --git a/src/FiscalOS.Web/src/App.vue b/src/FiscalOS.Web/src/App.vue index 289d3c3..ee7c659 100644 --- a/src/FiscalOS.Web/src/App.vue +++ b/src/FiscalOS.Web/src/App.vue @@ -1,8 +1,10 @@ diff --git a/src/FiscalOS.Web/src/router/index.ts b/src/FiscalOS.Web/src/router/index.ts index 6c13dc5..12978a3 100644 --- a/src/FiscalOS.Web/src/router/index.ts +++ b/src/FiscalOS.Web/src/router/index.ts @@ -1,8 +1,49 @@ +import { useUserStore } from "@/stores/userStore"; import { createRouter, createWebHistory } from "vue-router"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), - routes: [], + routes: [ + { + path: '/public', + redirect: '/public/login', + beforeEnter: () => { + const userStore = useUserStore(); + + if (userStore.user) { + return { path: '/' }; + } + + return true; + }, + children: [ + { + path: 'login', + component: () => import('../views/LoginView.vue') + } + ], + }, + { + path: '/', + beforeEnter: () => { + // TODO: Handle expired token + // Try to refresh + // if refresh fails log out + // redirect to login page + // if refresh succeeds + // log user in and allow + // to pass + const userStore = useUserStore(); + + if (userStore.user === null) { + return { path: '/public/login' } + } + + return true; + }, + component: () => import('../views/HomeView.vue'), + } + ], }); export default router; diff --git a/src/FiscalOS.Web/src/stores/userStore.ts b/src/FiscalOS.Web/src/stores/userStore.ts new file mode 100644 index 0000000..e38560c --- /dev/null +++ b/src/FiscalOS.Web/src/stores/userStore.ts @@ -0,0 +1,49 @@ +import { jwtDecode } from 'jwt-decode'; +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +export const USER_KEY = 'onxGraphAuth'; + +export type User = { + id: string; + expiresAtInSeconds: number; + token: string; +}; + +type JwtTokenPayload = { + sub: string; + exp: number; +}; + +function getUserFromLocalStorage(): User | null { + const user = localStorage.getItem(USER_KEY); + return user === null ? null : JSON.parse(user); +} + +export type UserStore = ReturnType; + +export const useUserStore = defineStore('userStore', () => { + const user = ref(getUserFromLocalStorage()); + + function logUserIn(jwtToken: string) { + const { sub, exp } = jwtDecode(jwtToken); + const loggedInUser: User = { + id: sub, + expiresAtInSeconds: exp, + token: jwtToken, + }; + localStorage.setItem(USER_KEY, JSON.stringify(loggedInUser)); + user.value = loggedInUser; + } + + function logUserOut() { + localStorage.removeItem(USER_KEY); + user.value = null; + } + + return { + user: user, + logUserIn, + logUserOut, + }; +}); diff --git a/src/FiscalOS.Web/src/views/HomeView.vue b/src/FiscalOS.Web/src/views/HomeView.vue new file mode 100644 index 0000000..f81ad63 --- /dev/null +++ b/src/FiscalOS.Web/src/views/HomeView.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/FiscalOS.Web/src/views/LoginView.vue b/src/FiscalOS.Web/src/views/LoginView.vue new file mode 100644 index 0000000..806f702 --- /dev/null +++ b/src/FiscalOS.Web/src/views/LoginView.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/FiscalOS.Web/public/favicon.ico b/src/FiscalOS.Web/static/favicon.ico similarity index 100% rename from src/FiscalOS.Web/public/favicon.ico rename to src/FiscalOS.Web/static/favicon.ico diff --git a/src/FiscalOS.Web/vite.config.ts b/src/FiscalOS.Web/vite.config.ts index 9d74b77..2f9dbbd 100644 --- a/src/FiscalOS.Web/vite.config.ts +++ b/src/FiscalOS.Web/vite.config.ts @@ -7,6 +7,7 @@ import vueDevTools from 'vite-plugin-vue-devtools' import mkcert from 'vite-plugin-mkcert'; export default defineConfig({ + publicDir: 'static', plugins: [ vue(), vueJsx(),