feat: implement role-based access control (RBAC) management system with Keycloak integration and dynamic page permission configuration

This commit is contained in:
Fanrouver
2026-07-14 09:41:55 +07:00
parent a5d7338e58
commit 49e0ee2bc4
14 changed files with 737 additions and 163 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
// server/api/hak-akses/check.ts
export default defineEventHandler(async (event) => {
// This is a mock endpoint to simulate backend checking permission.
// In production, the backend will validate the user's token directly.
const method = event.method;
if (method === 'POST') {
const body = await readBody(event);
const { menuKey, action } = body;
// Simulating some random failure or checking logic
if (!menuKey || !action) {
return createError({ statusCode: 400, statusMessage: 'Bad Request: menuKey and action required' });
}
// Just simulating success for now.
return {
success: true,
message: `Mock check: User has ${action} permission for ${menuKey}`,
allowed: true
}
}
return createError({ statusCode: 405, statusMessage: 'Method Not Allowed' });
});
+30 -14
View File
@@ -1,12 +1,12 @@
import fs from 'fs';
import path from 'path';
import type { HakAkses } from '~/types/setting';
import { randomUUID } from 'node:crypto'; // Use standard node crypto
import { hakAksesPayloadSchema, type HakAksesPayload } from '~/server/utils/schemas/permissionSchema';
const filePath = path.resolve('data/mock/hakAkses.json');
// Helper to read JSON file
const readData = (): HakAkses[] => {
const readData = (): HakAksesPayload[] => {
try {
if (!fs.existsSync(filePath)) {
// Ensure directory exists
@@ -26,7 +26,7 @@ const readData = (): HakAkses[] => {
};
// Helper to write JSON file
const writeData = (data: HakAkses[]): boolean => {
const writeData = (data: HakAksesPayload[]): boolean => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8');
return true;
@@ -51,7 +51,20 @@ export default defineEventHandler(async (event) => {
// POST - Create or Update hak akses
if (method === 'POST') {
try {
const body = await readBody(event);
const rawBody = await readBody(event);
// Validate payload with Zod Schema (Contract testing)
const validationResult = hakAksesPayloadSchema.safeParse(rawBody);
if (!validationResult.success) {
return {
success: false,
message: 'Payload invalid: Gagal menyimpan hak akses',
error: validationResult.error.format()
};
}
const body = validationResult.data;
const data = readData();
if (body.id) {
@@ -63,18 +76,21 @@ export default defineEventHandler(async (event) => {
...body
};
} else {
data.push(body);
data.push(body as HakAksesPayload);
}
} else {
// Create new
const newId = randomUUID();
const newHakAkses: HakAkses = {
id: newId,
namaHakAkses: body.namaHakAkses,
status: body.status || 'aktif',
pages: body.pages || []
};
data.push(newHakAkses);
body.id = randomUUID();
// For backward compatibility with HakAkses.vue UI that expects 'namaHakAkses'
if (!body.namaHakAkses) {
body.namaHakAkses = body.role || body.group || body.namaTipeUser || "Unknown";
}
if (!body.status) {
body.status = 'aktif';
}
data.push(body as HakAksesPayload);
}
const success = writeData(data);
@@ -91,7 +107,7 @@ export default defineEventHandler(async (event) => {
} catch (error) {
return {
success: false,
message: 'Gagal menyimpan hak akses',
message: 'Gagal memproses request',
error: error instanceof Error ? error.message : 'Unknown error'
};
}