diff --git a/app/components/app/patient/entry-form.vue b/app/components/app/patient/entry-form.vue index 89d8b1bf..b75af1c3 100644 --- a/app/components/app/patient/entry-form.vue +++ b/app/components/app/patient/entry-form.vue @@ -1,9 +1,38 @@ - + - entry form + + + + + Tambah Pasien + - - - + + + + + Nama + + + + + + Nomor RM + + + + + + + + + + + diff --git a/app/components/layout/Header.vue b/app/components/layout/Header.vue index 1da0a9ac..369b29ea 100644 --- a/app/components/layout/Header.vue +++ b/app/components/layout/Header.vue @@ -37,7 +37,7 @@ watch( if (val) { links.value = setLinks() } - } + }, ) diff --git a/app/components/pub/form/block.vue b/app/components/pub/form/block.vue index 5f4988d5..27015f7a 100644 --- a/app/components/pub/form/block.vue +++ b/app/components/pub/form/block.vue @@ -5,7 +5,7 @@ defineProps<{ - + diff --git a/app/components/pub/form/field-group.vue b/app/components/pub/form/field-group.vue index 8e901636..784870a7 100644 --- a/app/components/pub/form/field-group.vue +++ b/app/components/pub/form/field-group.vue @@ -34,7 +34,15 @@ const classVal = computed(() => { - + diff --git a/app/components/pub/form/label.vue b/app/components/pub/form/label.vue new file mode 100644 index 00000000..b1ad2d55 --- /dev/null +++ b/app/components/pub/form/label.vue @@ -0,0 +1,72 @@ + + + + + + + + + + + diff --git a/app/components/pub/nav/footer/cs.vue b/app/components/pub/nav/footer/cs.vue index 96725a05..c7786b76 100644 --- a/app/components/pub/nav/footer/cs.vue +++ b/app/components/pub/nav/footer/cs.vue @@ -1,9 +1,9 @@ - + - + Edit diff --git a/app/components/pub/ui/input/Input.vue b/app/components/pub/ui/input/Input.vue index 81140b40..30d3ca1e 100644 --- a/app/components/pub/ui/input/Input.vue +++ b/app/components/pub/ui/input/Input.vue @@ -20,5 +20,13 @@ const modelValue = useVModel(props, 'modelValue', emits, { - + diff --git a/app/composables/useRBAC.ts b/app/composables/useRBAC.ts new file mode 100644 index 00000000..6228f37f --- /dev/null +++ b/app/composables/useRBAC.ts @@ -0,0 +1,48 @@ +import type { Permission, RoleAccess } from '~/models/role' + +/** + * Check if user has access to a page + */ +export function useRBAC() { + // NOTE: this roles was dummy for testing only, it should taken from the user store + // const authStore = useAuthStore() + + const checkRole = (roleAccess: RoleAccess, _userRoles?: string[]): boolean => { + const roles = ['admisi'] + return roles.some((role: string) => role in roleAccess) + } + + const checkPermission = (roleAccess: RoleAccess, permission: Permission, _userRoles?: string[]): boolean => { + const roles = ['admisi'] + return roles.some((role: string) => roleAccess[role]?.includes(permission)) + } + + const getUserPermissions = (roleAccess: RoleAccess, _userRoles?: string[]): Permission[] => { + // const roles = userRoles || authStore.roles + const roles = ['admisi'] + const permissions = new Set() + + roles.forEach((role) => { + if (roleAccess[role]) { + roleAccess[role].forEach((permission) => permissions.add(permission)) + } + }) + + return Array.from(permissions) + } + + const hasCreateAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'C') + const hasReadAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'R') + const hasUpdateAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'U') + const hasDeleteAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'D') + + return { + checkRole, + checkPermission, + getUserPermissions, + hasCreateAccess, + hasReadAccess, + hasUpdateAccess, + hasDeleteAccess, + } +} diff --git a/app/layouts/default.vue b/app/layouts/default.vue index d63792ae..6e6c32ce 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,4 +1,21 @@ - + @@ -6,10 +23,70 @@ - + + + + + + - + diff --git a/app/lib/page-permission.ts b/app/lib/page-permission.ts new file mode 100644 index 00000000..a31edd64 --- /dev/null +++ b/app/lib/page-permission.ts @@ -0,0 +1,10 @@ +export const PAGE_PERMISSIONS = { + '/patient': { + doctor: ['R'], + nurse: ['R'], + admisi: ['C', 'R', 'U', 'D'], + pharmacy: ['R'], + billing: ['R'], + management: ['R'], + }, +} as const diff --git a/app/middleware/auth.global.ts b/app/middleware/auth.global.ts index e3b98900..8beb23c0 100644 --- a/app/middleware/auth.global.ts +++ b/app/middleware/auth.global.ts @@ -1,4 +1,6 @@ export default defineNuxtRouteMiddleware((to) => { + if (to.meta.public) return + const { $pinia } = useNuxtApp() if (import.meta.client) { @@ -10,9 +12,13 @@ export default defineNuxtRouteMiddleware((to) => { return navigateTo('/auth/login') } - const allowedRoles = to.meta.roles as string[] | undefined - if (allowedRoles && !allowedRoles.includes(userStore.userRole)) { - return navigateTo('/unauthorized') - } + // const allowedRoles = to.meta.roles as string[] | undefined + // if (allowedRoles && !allowedRoles.includes(userStore.userRole)) { + // return navigateTo('/unauthorized') + // } + // const allowedRoles = to.meta.roles as string[] | undefined + // if (allowedRoles && !userStore.userRole.some((r) => allowedRoles.includes(r))) { + // return navigateTo('/unauthorized') + // } } }) diff --git a/app/middleware/rbac.ts b/app/middleware/rbac.ts new file mode 100644 index 00000000..08f5c924 --- /dev/null +++ b/app/middleware/rbac.ts @@ -0,0 +1,37 @@ +import { PAGE_PERMISSIONS } from '~/lib/page-permission' + +export default defineNuxtRouteMiddleware((to) => { + if (to.meta.public) return + + const { $pinia } = useNuxtApp() + if (import.meta.server) { + const authStore = useUserStore($pinia) + // Check specific page permissions if defined in config + const pagePermissions = PAGE_PERMISSIONS[to.path as keyof typeof PAGE_PERMISSIONS] + if (pagePermissions) { + const { checkRole } = useRBAC() + if (!checkRole(pagePermissions)) { + throw createError({ + statusCode: 403, + statusMessage: 'Forbidden - Insufficient permissions for this page', + }) + } + } + + // Fallback to meta roles + const requiredRoles = to.meta.roles as string[] + if (requiredRoles && requiredRoles.length > 0) { + // FIXME: change this dummy roles, when api is ready + // const userRoles = authStore.roles + const userRoles = ['admisi'] + const hasRequiredRole = requiredRoles.some((role) => userRoles.includes(role)) + + if (!hasRequiredRole) { + throw createError({ + statusCode: 403, + statusMessage: 'Forbidden - Insufficient role permissions', + }) + } + } + } +}) diff --git a/app/models/role.ts b/app/models/role.ts new file mode 100644 index 00000000..6c6097a9 --- /dev/null +++ b/app/models/role.ts @@ -0,0 +1,22 @@ +import type { PAGE_PERMISSIONS } from '~/lib/page-permission' + +export interface User { + id: string + name: string + email: string +} + +export interface AuthState { + user: User | null + roles: string[] + token: string | null +} + +export type Permission = 'C' | 'R' | 'U' | 'D' + +export interface RoleAccess { + [role: string]: Permission[] +} + +export type PagePath = keyof typeof PAGE_PERMISSIONS +export type PagePermission = (typeof PAGE_PERMISSIONS)[PagePath] diff --git a/app/pages/(features)/patient/add.vue b/app/pages/(features)/patient/add.vue index 12063ef7..f71f712a 100644 --- a/app/pages/(features)/patient/add.vue +++ b/app/pages/(features)/patient/add.vue @@ -1,9 +1,36 @@ - + + + + + You don't have permission to create patient records. + diff --git a/app/pages/(features)/patient/index.vue b/app/pages/(features)/patient/index.vue index f1db5b53..cddf202f 100644 --- a/app/pages/(features)/patient/index.vue +++ b/app/pages/(features)/patient/index.vue @@ -1,11 +1,38 @@ - + + + + + You don't have permission to view patient records. + diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue index eb157c8c..b2e8f89a 100644 --- a/app/pages/auth/login.vue +++ b/app/pages/auth/login.vue @@ -1,6 +1,7 @@
You don't have permission to create patient records.
You don't have permission to view patient records.