diff --git a/components/layout/full/vertical-header/ProfileDD.vue b/components/layout/full/vertical-header/ProfileDD.vue index f19e6ac..cf18f75 100644 --- a/components/layout/full/vertical-header/ProfileDD.vue +++ b/components/layout/full/vertical-header/ProfileDD.vue @@ -41,7 +41,7 @@ const displayInfo = computed(() => { return { name: user.name || user.preferred_username || "User", email: user.email || "No email", - role: auth.hasRole('admin') ? 'Admin' : 'User' + role: user.client_roles }; }); @@ -69,16 +69,13 @@ const sessionInfo = computed(() => ({
User Profile
-
+
{{ displayInfo.name }}
- - {{ displayInfo.role }} - -
+
({ {{ displayInfo.email }}
- - -
- - {{ sessionInfo.isAuthenticated ? "Active" : "Inactive" }} - -
+ + {{ role }} +
diff --git a/components/layout/full/vertical-sidebar/sidebarItem.ts b/components/layout/full/vertical-sidebar/sidebarItem.ts index 85d6f04..71c92d4 100644 --- a/components/layout/full/vertical-sidebar/sidebarItem.ts +++ b/components/layout/full/vertical-sidebar/sidebarItem.ts @@ -54,9 +54,9 @@ const sidebarItem: menu[] = [ id: 1, children: [ { - title: 'Hak Akses', + title: 'Halaman', icon: 'shield-user-line-duotone', - to: '/setting/hak-akses', + to: '/setting/halaman', }, { title: 'User', diff --git a/composables/useAuth.ts b/composables/useAuth.ts index 1c61300..723f6b5 100644 --- a/composables/useAuth.ts +++ b/composables/useAuth.ts @@ -144,24 +144,6 @@ export const useAuth = () => { return !!userData } - // Helper function to check if user has specific role - const hasRole = (role: string): boolean => { - if (!user.value) return false - - // Check in roles array - if (user.value.roles?.includes(role)) return true - - // Check in realm_access.roles - if (user.value.realm_access?.roles?.includes(role)) return true - - return false - } - - // Helper function to check if user has any of the specified roles - const hasAnyRole = (roles: string[]): boolean => { - return roles.some(role => hasRole(role)) - } - return { // State user: readonly(user), @@ -177,8 +159,5 @@ export const useAuth = () => { refreshUser, clearError, - // Utilities - hasRole, - hasAnyRole } } \ No newline at end of file diff --git a/composables/useKeycloakRoles.ts b/composables/useKeycloakRoles.ts new file mode 100644 index 0000000..81941ef --- /dev/null +++ b/composables/useKeycloakRoles.ts @@ -0,0 +1,87 @@ +import { ref } from 'vue'; + +interface KeycloakRole { + id: string; + name: string; + description: string; + composite: boolean; + clientRole: boolean; + containerId: string; +} + +export const useKeycloakRoles = () => { + const roles = ref([]); + const loading = ref(false); + const error = ref(null); + const config = useRuntimeConfig(); + + const fetchRoles = async () => { + loading.value = true; + error.value = null; + + try { + // Get access token from localStorage or session + const accessToken = localStorage.getItem('accessToken'); + + if (!accessToken) { + throw new Error('Access token not found'); + } + + if(!config.public.keycloakClientUuid) { + throw new Error('Keycloak client UUID is not configured'); + } + + if(!config.public.keycloakAdminRealmUrl) { + throw new Error('Keycloak admin realm URL is not configured'); + } + + const response = await fetch( + `${config.public.keycloakAdminRealmUrl}/clients/${config.public.keycloakClientUuid}/roles`, + { + method: 'GET', + headers: { + 'accept': 'application/json, text/plain, */*', + 'authorization': `Bearer ${accessToken}`, + 'content-type': 'application/json' + } + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch roles: ${response.statusText}`); + } + + const data = await response.json(); + roles.value = data; + + return data; + } catch (err) { + error.value = err instanceof Error ? err.message : 'Unknown error'; + console.error('Error fetching Keycloak roles:', err); + return []; + } finally { + loading.value = false; + } + }; + + const getRoleNames = (): string[] => { + return roles.value.map(role => role.name); + }; + + const getRoleOptions = () => { + return roles.value.map(role => ({ + value: role.name, + title: role.name, + description: role.description + })); + }; + + return { + roles, + loading, + error, + fetchRoles, + getRoleNames, + getRoleOptions + }; +}; diff --git a/data/mock/hakAkses.json b/data/mock/hakAkses.json index 0706710..a2ce62a 100644 --- a/data/mock/hakAkses.json +++ b/data/mock/hakAkses.json @@ -35,5 +35,17 @@ "namaHakAkses": "view-profile", "status": "tidak aktif", "pages": [] + }, + { + "id": "d86a0a46-28ec-48bc-ae51-2679a61cbd83", + "namaHakAkses": "superadmin", + "status": "tidak aktif", + "pages": [] + }, + { + "id": "c0b20f5e-6b31-4134-8dbb-cc831cda6fb0", + "namaHakAkses": "Writer", + "status": "tidak aktif", + "pages": [] } ] \ No newline at end of file diff --git a/data/mock/halaman.json b/data/mock/halaman.json new file mode 100644 index 0000000..1666c8e --- /dev/null +++ b/data/mock/halaman.json @@ -0,0 +1,94 @@ +[ + { + "id": 1, + "name": "Dashboards", + "url": "#", + "level": 1, + "icon": "", + "role": [] + }, + { + "id": 2, + "name": "Dashboard", + "url": "/dashboard", + "level": 2, + "parent": 1, + "icon": "widget-add-line-duotone", + "role": [ + "Writer", + "superadmin" + ] + }, + { + "id": 3, + "name": "Antrean", + "url": "#", + "level": 1, + "icon": "", + "role": [] + }, + { + "id": 4, + "name": "Semua", + "url": "/antrean/all", + "level": 2, + "parent": 3, + "icon": "list-check-line-duotone", + "role": [ + "admin", + "Writer" + ] + }, + { + "id": 5, + "name": "Kategori", + "url": "/antrean/list-kategori", + "level": 2, + "parent": 3, + "icon": "layers-minimalistic-line-duotone", + "role": [ + "Writer" + ] + }, + { + "id": 6, + "name": "Spesialis", + "url": "/antrean/list-spesialis", + "level": 2, + "parent": 3, + "icon": "users-group-rounded-line-duotone", + "role": [ + "Writer", + "superadmin" + ] + }, + { + "id": 7, + "name": "pengaturan", + "url": "#", + "level": 1, + "role": [] + }, + { + "id": 8, + "name": "Halaman", + "url": "/setting/halaman", + "level": 2, + "parent": 7, + "icon": "shield-user-line-duotone", + "role": [ + "superadmin" + ] + }, + { + "id": 9, + "name": "User", + "url": "/setting/user", + "level": 2, + "parent": 7, + "icon": "user-id-line-duotone", + "role": [ + "superadmin" + ] + } +] \ No newline at end of file diff --git a/data/mock/users.json b/data/mock/users.json index a2c7da6..980c994 100644 --- a/data/mock/users.json +++ b/data/mock/users.json @@ -4,9 +4,26 @@ "namaUser": "Akbar Attallah", "email": "akbarantrean@gmail.com", "hakAkses": [ - "manage-account", - "manage-account-links", - "view-profile" + "superadmin" + ], + "status": "aktif" + }, + { + "id": "31e5227f-78d2-4b4d-b255-afcc5ece6b27", + "namaUser": "renaldy", + "email": "renaldy.brada@gmail.com", + "hakAkses": [ + "Writer" + ], + "status": "aktif" + }, + { + "id": "d6621539-9e8e-4937-ba9a-fca68f625e39", + "namaUser": "yusron", + "email": "yusron.sandbox@gmai.com", + "hakAkses": [ + "superadmin", + "Writer" ], "status": "aktif" } diff --git a/nuxt.config.ts b/nuxt.config.ts index 1e48f6b..ee798d5 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -105,6 +105,8 @@ export default defineNuxtConfig({ keycloakUrl: process.env.KEYCLOAK_ISSUER ? `${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token` : 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/token', keycloakClientId: process.env.KEYCLOAK_CLIENT_ID || 'akbar-test', keycloakClientSecret: process.env.KEYCLOAK_CLIENT_SECRET || 'FDyv3UYMgJOYPnvzXVVv6diRtcgEevKg', + keycloakClientUuid: process.env.KEYCLOAK_CLIENT_UUID, + keycloakAdminRealmUrl: process.env.KEYCLOAK_ADMIN_REALM_URL }, }, // auth: { diff --git a/pages/setting/halaman/form.vue b/pages/setting/halaman/form.vue new file mode 100644 index 0000000..faacde0 --- /dev/null +++ b/pages/setting/halaman/form.vue @@ -0,0 +1,356 @@ + + + \ No newline at end of file diff --git a/pages/setting/halaman/index.vue b/pages/setting/halaman/index.vue new file mode 100644 index 0000000..dcd4e1c --- /dev/null +++ b/pages/setting/halaman/index.vue @@ -0,0 +1,257 @@ + + \ No newline at end of file diff --git a/server/api/auth/keycloak-callback.get.ts b/server/api/auth/keycloak-callback.get.ts index 8805bc3..1cb5acf 100644 --- a/server/api/auth/keycloak-callback.get.ts +++ b/server/api/auth/keycloak-callback.get.ts @@ -145,7 +145,7 @@ export default defineEventHandler(async (event) => { // Extract roles from Keycloak token // Keycloak stores roles in different places depending on configuration const realmRoles = accessTokenPayload.realm_access?.roles || []; - const clientRoles = accessTokenPayload.resource_access?.account?.roles || []; + const clientRoles = accessTokenPayload.resource_access?.[config.keycloakClientId]?.roles || []; const allRoles = [...new Set([...realmRoles, ...clientRoles])]; // Remove duplicates console.log('👥 User Roles Extracted:'); diff --git a/server/api/halaman/[id].ts b/server/api/halaman/[id].ts new file mode 100644 index 0000000..4c3a4e4 --- /dev/null +++ b/server/api/halaman/[id].ts @@ -0,0 +1,121 @@ +import fs from 'fs'; +import path from 'path'; + +interface Halaman { + id: number; + name: string; + url: string; + level: number; + parent?: number; + icon?: string; + role: string[]; +} + +const filePath = path.resolve('data/mock/halaman.json'); + +// Helper to read JSON file +const readData = (): Halaman[] => { + try { + const data = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading halaman.json:', error); + return []; + } +}; + +// Helper to write JSON file +const writeData = (data: Halaman[]): boolean => { + try { + fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8'); + return true; + } catch (error) { + console.error('Error writing halaman.json:', error); + return false; + } +}; + +export default defineEventHandler(async (event) => { + const method = event.method; + const id = event.context.params?.id; + + if (!id) { + return { + success: false, + message: 'Invalid ID' + }; + } + + const numericId = Number(id); + + // GET - Get single halaman by ID + if (method === 'GET') { + try { + const data = readData(); + const halaman = data.find(item => item.id === numericId); + + if (halaman) { + return { + success: true, + data: halaman + }; + } else { + return { + success: false, + message: 'Halaman tidak ditemukan' + }; + } + } catch (error) { + return { + success: false, + message: 'Gagal memuat data halaman', + error: error instanceof Error ? error.message : 'Unknown error' + }; + } + } + + // PUT - Update halaman role + if (method === 'PUT') { + try { + const body = await readBody(event); + const data = readData(); + const index = data.findIndex(item => item.id === numericId); + + if (index === -1) { + return { + success: false, + message: 'Halaman tidak ditemukan' + }; + } + + // Update only the role field + data[index] = { + ...data[index], + role: body.role || [] + }; + + const success = writeData(data); + + if (success) { + return { + success: true, + message: 'Role halaman berhasil diupdate', + data: data[index] + }; + } else { + throw new Error('Failed to save data'); + } + } catch (error) { + return { + success: false, + message: 'Gagal mengupdate role halaman', + error: error instanceof Error ? error.message : 'Unknown error' + }; + } + } + + return { + success: false, + message: 'Method not allowed' + }; +}); diff --git a/server/api/halaman/index.ts b/server/api/halaman/index.ts new file mode 100644 index 0000000..0bd8014 --- /dev/null +++ b/server/api/halaman/index.ts @@ -0,0 +1,51 @@ +import fs from 'fs'; +import path from 'path'; + +interface Halaman { + id: number; + name: string; + url: string; + level: number; + parent?: number; + icon?: string; + role: string[]; +} + +const filePath = path.resolve('data/mock/halaman.json'); + +// Helper to read JSON file +const readData = (): Halaman[] => { + try { + const data = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading halaman.json:', error); + return []; + } +}; + +export default defineEventHandler(async (event) => { + const method = event.method; + + // GET - Get all halaman + if (method === 'GET') { + try { + const data = readData(); + return { + success: true, + data + }; + } catch (error) { + return { + success: false, + message: 'Gagal memuat data halaman', + error: error instanceof Error ? error.message : 'Unknown error' + }; + } + } + + return { + success: false, + message: 'Method not allowed' + }; +});