feat(FE) : json role

This commit is contained in:
Yusron alamsyah
2026-02-18 08:21:58 +07:00
parent 6c6b842d3b
commit 466dc076c3
29 changed files with 1539 additions and 172 deletions

No files matched your search

+38 -6
View File
@@ -128,15 +128,30 @@ export default defineEventHandler(async (event) => {
const tokens = await tokenResponse.json();
let idTokenPayload;
let accessTokenPayload;
try {
idTokenPayload = JSON.parse(
Buffer.from(tokens.id_token.split('.')[1], 'base64').toString()
);
accessTokenPayload = JSON.parse(
Buffer.from(tokens.access_token.split('.')[1], 'base64').toString()
);
} catch (decodeError) {
console.error('❌ Failed to decode ID token:', decodeError);
const errorMsg = encodeURIComponent('Invalid ID token format');
return sendRedirect(event, `/auth/login?error=${errorMsg}`);
}
// 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 allRoles = [...new Set([...realmRoles, ...clientRoles])]; // Remove duplicates
console.log('👥 User Roles Extracted:');
console.log(' - Realm Roles:', realmRoles);
console.log(' - Client Roles:', clientRoles);
console.log(' - All Roles:', allRoles);
// Store minimal session data in cookie to reduce size
// The ID token contains user info, so we can decode it when needed
@@ -147,6 +162,9 @@ export default defineEventHandler(async (event) => {
email: idTokenPayload.email,
name: idTokenPayload.name || idTokenPayload.preferred_username,
preferred_username: idTokenPayload.preferred_username,
roles: allRoles, // All user roles combined
realm_roles: realmRoles, // Realm-specific roles
client_roles: clientRoles, // Client-specific roles
},
// Store tokens - these are necessary for API calls
// Note: These JWT tokens are large, but necessary for authentication
@@ -210,15 +228,29 @@ export default defineEventHandler(async (event) => {
console.log(' - MaxAge:', SESSION_DURATION, 'seconds');
console.log(' - Host:', event.node.req.headers.host);
// Auto-sync user data to database (first time login check)
// Pass session createdAt as loginTime to sync function
// Auto-sync hakAkses and user data after successful login
try {
const { syncUserFromTokens } = await import('~/server/utils/userSync');
const result = syncUserFromTokens(tokens.id_token, tokens.access_token, sessionData.createdAt);
console.log(`✅ User auto-sync on login: ${result.action} - ${result.message}`);
console.log('🔄 Starting auto-sync process...');
// 1. Sync hakAkses (roles) to hakAkses.json
const { syncHakAksesFromRoles } = await import('~/server/utils/hakAksesSync');
const hakAksesResult = syncHakAksesFromRoles(clientRoles);
console.log(`📋 HakAkses sync result:`, hakAksesResult);
// 2. Sync user data to users.json
const { syncUserData } = await import('~/server/utils/userDataSync');
const userResult = syncUserData({
id: sessionData.user.id,
name: sessionData.user.name,
email: sessionData.user.email,
roles: clientRoles
});
console.log(`👤 User sync result: ${userResult.action} - ${userResult.message}`);
console.log('✅ Auto-sync process completed successfully');
} catch (syncError: any) {
// Don't fail the login if sync fails, just log it
console.error('⚠️ Failed to auto-sync user on login:', syncError);
console.error('⚠️ Failed to auto-sync data on login:', syncError);
}
// IMPORTANT: Ensure cookie is set before redirect
+140
View File
@@ -0,0 +1,140 @@
import fs from 'fs';
import path from 'path';
import type { HakAkses } from '~/types/setting';
const filePath = path.resolve('data/mock/hakAkses.json');
// Helper to read JSON file
const readData = (): HakAkses[] => {
try {
const data = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading hakAkses.json:', error);
return [];
}
};
// Helper to write JSON file
const writeData = (data: HakAkses[]): boolean => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8');
return true;
} catch (error) {
console.error('Error writing hakAkses.json:', error);
return false;
}
};
export default defineEventHandler(async (event) => {
const method = event.method;
const id = event.context.params?.id || '0';
if (!id) {
return {
success: false,
message: 'Invalid ID'
};
}
// GET - Get single hak akses by ID
if (method === 'GET') {
const data = readData();
const hakAkses = data.find(item => item.id === id);
if (hakAkses) {
return {
success: true,
data: hakAkses
};
} else {
return {
success: false,
message: 'Hak akses tidak ditemukan'
};
}
}
// PUT - Update hak akses
if (method === 'PUT') {
try {
const body = await readBody(event);
const data = readData();
const index = data.findIndex(item => item.id === id);
if (index === -1) {
return {
success: false,
message: 'Hak akses tidak ditemukan'
};
}
// Update the item
data[index] = {
id,
namaHakAkses: body.namaHakAkses,
status: body.status,
pages: body.pages || []
};
const success = writeData(data);
if (success) {
return {
success: true,
message: 'Hak akses berhasil diupdate',
data: data[index]
};
} else {
throw new Error('Failed to save data');
}
} catch (error) {
return {
success: false,
message: 'Gagal mengupdate hak akses',
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// DELETE - Delete hak akses
if (method === 'DELETE') {
try {
const data = readData();
const index = data.findIndex(item => item.id === id);
if (index === -1) {
return {
success: false,
message: 'Hak akses tidak ditemukan'
};
}
const deletedItem = data[index];
data.splice(index, 1);
const success = writeData(data);
if (success) {
return {
success: true,
message: 'Hak akses berhasil dihapus',
data: deletedItem
};
} else {
throw new Error('Failed to save data');
}
} catch (error) {
return {
success: false,
message: 'Gagal menghapus hak akses',
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
return {
success: false,
message: 'Method not allowed'
};
});
+84
View File
@@ -0,0 +1,84 @@
import fs from 'fs';
import path from 'path';
import type { HakAkses } from '~/types/setting';
const filePath = path.resolve('data/mock/hakAkses.json');
// Helper to read JSON file
const readData = (): HakAkses[] => {
try {
const data = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading hakAkses.json:', error);
return [];
}
};
// Helper to write JSON file
const writeData = (data: HakAkses[]): boolean => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8');
return true;
} catch (error) {
console.error('Error writing hakAkses.json:', error);
return false;
}
};
export default defineEventHandler(async (event) => {
const method = event.method;
// GET - List all hak akses
if (method === 'GET') {
const data = readData();
return {
success: true,
data
};
}
// POST - Create new hak akses
if (method === 'POST') {
try {
const body = await readBody(event);
const data = readData();
// Generate new ID
//random uuid
const newId = crypto.randomUUID();
const newHakAkses: HakAkses = {
id: newId,
namaHakAkses: body.namaHakAkses,
status: body.status,
pages: body.pages || []
};
data.push(newHakAkses);
const success = writeData(data);
if (success) {
return {
success: true,
message: 'Hak akses berhasil ditambahkan',
data: newHakAkses
};
} else {
throw new Error('Failed to save data');
}
} catch (error) {
return {
success: false,
message: 'Gagal menambahkan hak akses',
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
return {
success: false,
message: 'Method not allowed'
};
});
+78
View File
@@ -0,0 +1,78 @@
import fs from 'fs';
import path from 'path';
import { randomUUID } from 'crypto';
import type { HakAkses } from '~/types/setting';
const filePath = path.resolve('data/mock/hakAkses.json');
// Helper to read JSON file
const readData = (): HakAkses[] => {
try {
const data = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading hakAkses.json:', error);
return [];
}
};
// Helper to write JSON file
const writeData = (data: HakAkses[]): boolean => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8');
return true;
} catch (error) {
console.error('Error writing hakAkses.json:', error);
return false;
}
};
/**
* Sync roles from Keycloak to hakAkses.json
* Add any missing roles with inactive status and empty pages
*/
export const syncHakAksesFromRoles = (roles: string[]): { added: string[], skipped: string[] } => {
const data = readData();
const added: string[] = [];
const skipped: string[] = [];
let hasChanges = false;
for (const role of roles) {
// Check if role already exists
const exists = data.some(item => item.namaHakAkses === role);
if (!exists) {
// Generate new UUID
const newId = randomUUID();
// Add new hak akses
const newHakAkses: HakAkses = {
id: newId,
namaHakAkses: role,
status: 'tidak aktif',
pages: []
};
data.push(newHakAkses);
added.push(role);
hasChanges = true;
console.log(`✅ Added new hak akses: ${role} (id: ${newId})`);
} else {
skipped.push(role);
}
}
// Write back to file if there are changes
if (hasChanges) {
const success = writeData(data);
if (success) {
console.log(`📝 Updated hakAkses.json with ${added.length} new roles`);
} else {
console.error('❌ Failed to update hakAkses.json');
}
}
return { added, skipped };
};
+87
View File
@@ -0,0 +1,87 @@
// server/utils/roleChecker.ts
// Utility functions for role-based access control
import { getSessionFromCookie } from './sessionStore';
/**
* Check if user has a specific role
*/
export async function hasRole(event: any, role: string): Promise<boolean> {
const session = await getSessionFromCookie(event);
if (!session || !session.user) {
return false;
}
const userRoles = session.user.roles || [];
return userRoles.includes(role);
}
/**
* Check if user has any of the specified roles
*/
export async function hasAnyRole(event: any, roles: string[]): Promise<boolean> {
const session = await getSessionFromCookie(event);
if (!session || !session.user) {
return false;
}
const userRoles = session.user.roles || [];
return roles.some(role => userRoles.includes(role));
}
/**
* Check if user has all of the specified roles
*/
export async function hasAllRoles(event: any, roles: string[]): Promise<boolean> {
const session = await getSessionFromCookie(event);
if (!session || !session.user) {
return false;
}
const userRoles = session.user.roles || [];
return roles.every(role => userRoles.includes(role));
}
/**
* Get all user roles
*/
export async function getUserRoles(event: any): Promise<string[]> {
const session = await getSessionFromCookie(event);
if (!session || !session.user) {
return [];
}
return session.user.roles || [];
}
/**
* Middleware helper to require specific role
* Throws error if user doesn't have the required role
*/
export async function requireRole(event: any, role: string): Promise<void> {
const hasRequiredRole = await hasRole(event, role);
if (!hasRequiredRole) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden',
message: `Access denied. Required role: ${role}`
});
}
}
/**
* Middleware helper to require any of the specified roles
* Throws error if user doesn't have any of the required roles
*/
export async function requireAnyRole(event: any, roles: string[]): Promise<void> {
const hasRequiredRole = await hasAnyRole(event, roles);
if (!hasRequiredRole) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden',
message: `Access denied. Required roles: ${roles.join(', ')}`
});
}
}
+107
View File
@@ -0,0 +1,107 @@
import fs from 'fs';
import path from 'path';
interface User {
id: string;
namaUser: string;
email: string;
hakAkses: string[];
status: string;
}
const filePath = path.resolve('data/mock/users.json');
// Helper to read JSON file
const readData = (): User[] => {
try {
const data = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading users.json:', error);
return [];
}
};
// Helper to write JSON file
const writeData = (data: User[]): boolean => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf-8');
return true;
} catch (error) {
console.error('Error writing users.json:', error);
return false;
}
};
/**
* Sync user data from Keycloak to users.json
* Add user if not exists, update hakAkses if exists
*/
export const syncUserData = (userData: {
id: string;
name: string;
email: string;
roles: string[];
}): { action: 'created' | 'updated' | 'skipped', message: string } => {
const data = readData();
// Check if user exists
const existingUserIndex = data.findIndex(user => user.id === userData.id);
if (existingUserIndex === -1) {
// User doesn't exist, create new
const newUser: User = {
id: userData.id,
namaUser: userData.name,
email: userData.email,
hakAkses: userData.roles,
status: 'aktif'
};
data.push(newUser);
const success = writeData(data);
if (success) {
console.log(`✅ Created new user: ${userData.name} (${userData.email})`);
return {
action: 'created',
message: `User ${userData.name} created successfully`
};
} else {
console.error('❌ Failed to create user');
return {
action: 'skipped',
message: 'Failed to write user data'
};
}
} else {
// User exists, update hakAkses if different
const existingUser = data[existingUserIndex];
const rolesChanged = JSON.stringify(existingUser.hakAkses.sort()) !== JSON.stringify(userData.roles.sort());
if (rolesChanged) {
existingUser.hakAkses = userData.roles;
const success = writeData(data);
if (success) {
console.log(`✅ Updated user roles: ${userData.name}`);
return {
action: 'updated',
message: `User ${userData.name} roles updated`
};
} else {
console.error('❌ Failed to update user');
return {
action: 'skipped',
message: 'Failed to write user data'
};
}
} else {
console.log(`️ User ${userData.name} already exists with same roles`);
return {
action: 'skipped',
message: `User ${userData.name} already up to date`
};
}
}
};