From 66db96b9b6e85178c1d9ecb8ccfaaea0c89ca56b Mon Sep 17 00:00:00 2001 From: Abizrh Date: Sun, 10 Aug 2025 16:47:07 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat=20(auth):=20implement=20global?= =?UTF-8?q?=20authentication=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++---- app/middleware/auth.global.ts | 18 +++++++++++ app/middleware/auth.ts | 7 ----- app/pages/(features)/patient/[id]/detail.vue | 6 ++++ app/pages/(features)/patient/[id]/edit.vue | 6 ++++ app/pages/(features)/patient/add.vue | 6 ++++ app/pages/(features)/patient/index.vue | 2 +- app/types/index.d.ts | 16 ++++++++++ index.d.ts | 9 ------ package.json | 1 + pnpm-lock.yaml | 33 ++++++++++++-------- 11 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 app/middleware/auth.global.ts delete mode 100644 app/middleware/auth.ts create mode 100644 app/types/index.d.ts delete mode 100644 index.d.ts diff --git a/README.md b/README.md index 6f61806b..4b3f181a 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ RSSA - Front End ## Directory Structure for `app/` - `app.vue`: Main layout - `components` : Contains all reusable UI components. -- `components/flows` : Entry point for business logic and workflows. Pages or routes call these flow components to handle API requests and process application logic -- `components/app` : View-layer components that manage and present data. These are used within `flows/` to render or handle specific parts of the UI, and return results back to the flow +- `components/flow` : Entry point for business logic and workflows. Pages or routes call these flow components to handle API requests and process application logic +- `components/app` : View-layer components that manage and present data. These are used within `flow/` to render or handle specific parts of the UI, and return results back to the flow - `components/pub` : Public/shared components used across different parts of the app. - `composables` : Contains reusable logic and utility functions (e.g. composables, hooks).. - `layouts` : Reusable UI layout patterns used across pages. ## Directory Structure for `app/pages` - `pages/auth` : Authentication related pages. -- `pages/(features)` : Grouped feature modules that reflect specific business flows or domains. +- `pages/(features)` : Grouped feature modules that reflect specific business flow or domains. ## Directory Structure for `server/` - `server/api` : API or proxy requests @@ -37,13 +37,13 @@ The basic development workflow follows these steps: - Keep components pure, avoid making HTTP requests directly within them. - They receive data via props and emit events upward. -### Business Logic in `components/flows` +### Business Logic in `components/flow` - This layer connects the UI with the logic (API calls, validations, navigation). -- It composes components from `components/app/`, `components/pub/`, and other flows. +- It composes components from `components/app/`, `components/pub/`, and other flow. - Also responsible for managing state, side effects, and interactions. ### Create Pages in `pages/` -- Pages load the appropriate flow from `components/flows/`. +- Pages load the appropriate flow from `components/flow/`. - They do not contain UI or logic directly, just route level layout or guards. ## Git Workflows diff --git a/app/middleware/auth.global.ts b/app/middleware/auth.global.ts new file mode 100644 index 00000000..e3b98900 --- /dev/null +++ b/app/middleware/auth.global.ts @@ -0,0 +1,18 @@ +export default defineNuxtRouteMiddleware((to) => { + const { $pinia } = useNuxtApp() + + if (import.meta.client) { + const userStore = useUserStore($pinia) + + console.log('currRole', userStore.userRole) + console.log('isAuth', userStore.isAuthenticated) + if (!userStore.isAuthenticated) { + return navigateTo('/auth/login') + } + + const allowedRoles = to.meta.roles as string[] | undefined + if (allowedRoles && !allowedRoles.includes(userStore.userRole)) { + return navigateTo('/unauthorized') + } + } +}) diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts deleted file mode 100644 index f9be043d..00000000 --- a/app/middleware/auth.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default defineNuxtRouteMiddleware((to, from) => { - // TODO: change this to actual api - const user = true - if (!user) { - return navigateTo('/auth/login') - } -}) diff --git a/app/pages/(features)/patient/[id]/detail.vue b/app/pages/(features)/patient/[id]/detail.vue index e01c4948..33a36f0f 100644 --- a/app/pages/(features)/patient/[id]/detail.vue +++ b/app/pages/(features)/patient/[id]/detail.vue @@ -1,3 +1,9 @@ + + diff --git a/app/pages/(features)/patient/[id]/edit.vue b/app/pages/(features)/patient/[id]/edit.vue index 84e4fac5..2b7e8a31 100644 --- a/app/pages/(features)/patient/[id]/edit.vue +++ b/app/pages/(features)/patient/[id]/edit.vue @@ -1,3 +1,9 @@ + + diff --git a/app/pages/(features)/patient/add.vue b/app/pages/(features)/patient/add.vue index 56dd4be8..12063ef7 100644 --- a/app/pages/(features)/patient/add.vue +++ b/app/pages/(features)/patient/add.vue @@ -1,3 +1,9 @@ + + diff --git a/app/pages/(features)/patient/index.vue b/app/pages/(features)/patient/index.vue index 6a8b1dab..f1db5b53 100644 --- a/app/pages/(features)/patient/index.vue +++ b/app/pages/(features)/patient/index.vue @@ -1,6 +1,6 @@ diff --git a/app/types/index.d.ts b/app/types/index.d.ts new file mode 100644 index 00000000..4a1d7476 --- /dev/null +++ b/app/types/index.d.ts @@ -0,0 +1,16 @@ +// app/types/index.d.ts +import type { Pinia } from 'pinia' + +declare module '#app' { + interface NuxtApp { + $pinia: Pinia + } +} + +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + $pinia: Pinia + } +} + +export {} diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 7e085abf..00000000 --- a/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module '#app' { - interface NuxtApp {} -} - -declare module 'vue' { - interface ComponentCustomProperties {} -} - -export {} diff --git a/package.json b/package.json index 12c01fcf..13595e88 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "embla-carousel": "^8.5.2", "embla-carousel-vue": "^8.5.2", "h3": "^1.15.4", + "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.4.1", "reka-ui": "^2.4.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c992d6cb..548169a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,12 @@ dependencies: h3: specifier: ^1.15.4 version: 1.15.4 + pinia: + specifier: ^3.0.3 + version: 3.0.3(typescript@5.9.2)(vue@3.5.18) pinia-plugin-persistedstate: specifier: ^4.4.1 - version: 4.4.1(@pinia/nuxt@0.5.5) + version: 4.4.1(@pinia/nuxt@0.5.5)(pinia@3.0.3) reka-ui: specifier: ^2.4.1 version: 2.4.1(typescript@5.9.2)(vue@3.5.18) @@ -4323,7 +4326,6 @@ packages: resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} dependencies: '@vue/devtools-kit': 7.7.7 - dev: true /@vue/devtools-core@7.7.7(vite@7.1.1)(vue@3.5.18): resolution: {integrity: sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==} @@ -4351,13 +4353,11 @@ packages: perfect-debounce: 1.0.0 speakingurl: 14.0.1 superjson: 2.2.2 - dev: true /@vue/devtools-shared@7.7.7: resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} dependencies: rfdc: 1.4.1 - dev: true /@vue/language-core@2.2.12(typescript@5.9.2): resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} @@ -4909,7 +4909,6 @@ packages: /birpc@2.5.0: resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} - dev: true /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -5299,7 +5298,6 @@ packages: engines: {node: '>=12.13'} dependencies: is-what: 4.1.16 - dev: true /copy-file@11.0.0: resolution: {integrity: sha512-mFsNh/DIANLqFt5VHZoGirdg7bK5+oTWlhnGu6tgRhzBlnEKWaPX2xrFaLltii/6rmhqFMJqffUgknuRdpYlHw==} @@ -7342,7 +7340,6 @@ packages: /hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - dev: true /hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} @@ -7621,7 +7618,6 @@ packages: /is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} - dev: true /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} @@ -8558,7 +8554,6 @@ packages: /mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - dev: true /mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} @@ -9369,7 +9364,7 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - /pinia-plugin-persistedstate@4.4.1(@pinia/nuxt@0.5.5): + /pinia-plugin-persistedstate@4.4.1(@pinia/nuxt@0.5.5)(pinia@3.0.3): resolution: {integrity: sha512-lmuMPpXla2zJKjxEq34e1E9P9jxkWEhcVwwioCCE0izG45kkTOvQfCzvwhW3i38cvnaWC7T1eRdkd15Re59ldw==} peerDependencies: '@nuxt/kit': '>=3.0.0' @@ -9387,6 +9382,7 @@ packages: deep-pick-omit: 1.2.1 defu: 6.1.4 destr: 2.0.5 + pinia: 3.0.3(typescript@5.9.2)(vue@3.5.18) dev: false /pinia@2.3.1(typescript@5.9.2)(vue@3.5.18): @@ -9405,6 +9401,20 @@ packages: transitivePeerDependencies: - '@vue/composition-api' + /pinia@3.0.3(typescript@5.9.2)(vue@3.5.18): + resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/devtools-api': 7.7.7 + typescript: 5.9.2 + vue: 3.5.18(typescript@5.9.2) + dev: false + /pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} dependencies: @@ -10151,7 +10161,6 @@ packages: /rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - dev: true /robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -10489,7 +10498,6 @@ packages: /speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} - dev: true /stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} @@ -10626,7 +10634,6 @@ packages: engines: {node: '>=16'} dependencies: copy-anything: 3.0.5 - dev: true /supports-color@10.1.0: resolution: {integrity: sha512-GBuewsPrhJPftT+fqDa9oI/zc5HNsG9nREqwzoSFDOIqf0NggOZbHQj2TE1P1CDJK8ZogFnlZY9hWoUiur7I/A==}