feat(FE) : json role
This commit is contained in:
No files matched your search
@@ -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
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
});
|
||||
@@ -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'
|
||||
};
|
||||
});
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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(', ')}`
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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`
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user