update hak akses dan perbaikan api pembuatan tiket

This commit is contained in:
Fanrouver
2026-04-20 09:42:03 +07:00
parent 94ff9f55d9
commit 2bf00eff3c
38 changed files with 2054 additions and 2552 deletions

No files matched your search

+17 -2
View File
@@ -153,6 +153,7 @@ export default defineEventHandler(async (event) => {
// Parse token payloads for immediate availability
let accessTokenPayload;
let idTokenPayloadFull;
let refreshTokenPayload;
try {
accessTokenPayload = JSON.parse(
Buffer.from(tokens.access_token.split(".")[1], "base64").toString(),
@@ -160,12 +161,26 @@ export default defineEventHandler(async (event) => {
idTokenPayloadFull = JSON.parse(
Buffer.from(tokens.id_token.split(".")[1], "base64").toString(),
);
if (tokens.refresh_token) {
refreshTokenPayload = JSON.parse(
Buffer.from(tokens.refresh_token.split(".")[1], "base64").toString(),
);
}
} catch (parseError) {
console.error("❌ Failed to parse token payloads:", parseError);
const errorMsg = encodeURIComponent("Invalid token format");
return sendRedirect(event, `/LoginPage?error=${errorMsg}`);
}
// Determine session expiration from refresh token (preferred) or default
const expiresAt = refreshTokenPayload?.exp
? refreshTokenPayload.exp * 1000
: Date.now() + SESSION_DURATION * 1000;
const createdAt = refreshTokenPayload?.iat
? refreshTokenPayload.iat * 1000
: Date.now();
// Create session data
const sessionData = {
user: {
@@ -177,8 +192,8 @@ export default defineEventHandler(async (event) => {
accessToken: tokens.access_token,
idToken: tokens.id_token,
refreshToken: tokens.refresh_token,
expiresAt: Date.now() + SESSION_DURATION * 1000,
createdAt: Date.now(),
expiresAt: expiresAt,
createdAt: createdAt,
scope: accessTokenPayload.scope || "openid email profile",
status: "authenticated",
};
+13
View File
@@ -34,6 +34,7 @@ export default defineEventHandler(async (event) => {
// Parse token payloads on-demand from tokens
let accessTokenPayload = null;
let idTokenPayload = null;
let refreshTokenPayload = null;
try {
if (session.accessToken) {
@@ -49,10 +50,22 @@ export default defineEventHandler(async (event) => {
idTokenPayload = JSON.parse(Buffer.from(idParts[1], 'base64').toString());
}
}
if (session.refreshToken) {
const refreshParts = session.refreshToken.split('.');
if (refreshParts.length >= 2) {
refreshTokenPayload = JSON.parse(Buffer.from(refreshParts[1], 'base64').toString());
}
}
} catch (parseError) {
console.warn('⚠️ Failed to parse token payloads:', parseError);
}
// Use dynamic expiration from refresh token if available
if (refreshTokenPayload?.exp) {
session.expiresAt = refreshTokenPayload.exp * 1000;
}
const isExpired = Date.now() > session.expiresAt;
console.log(' Is Expired:', isExpired);
+71
View File
@@ -0,0 +1,71 @@
// server/api/auth/session.patch.ts
export default defineEventHandler(async (event) => {
console.log("🔄 Session update endpoint called");
const sessionId = getCookie(event, "user_session");
if (!sessionId) {
console.log("❌ No session cookie found");
throw createError({
statusCode: 401,
statusMessage: "No session cookie found",
});
}
try {
// Get the update data from request body
const body = await readBody(event);
const { accessToken, idToken, refreshToken, expiresAt } = body;
// Validate that at least one token is provided
if (!accessToken && !idToken && !refreshToken) {
throw createError({
statusCode: 400,
statusMessage: "At least one token must be provided",
});
}
// Get session store functions
const { getSession, updateSession } = await import('~/server/utils/sessionStore');
// Verify session exists
const session = getSession(sessionId);
if (!session) {
console.log("❌ Session not found");
deleteCookie(event, "user_session");
throw createError({
statusCode: 401,
statusMessage: "Session not found or expired",
});
}
// Prepare updates object
const updates: any = {};
if (accessToken) updates.accessToken = accessToken;
if (idToken) updates.idToken = idToken;
if (refreshToken) updates.refreshToken = refreshToken;
if (expiresAt) updates.expiresAt = expiresAt;
// Update the session
const updated = updateSession(sessionId, updates);
if (!updated) {
throw createError({
statusCode: 500,
statusMessage: "Failed to update session",
});
}
console.log("✅ Session updated successfully");
return {
success: true,
message: "Session updated successfully",
};
} catch (error: any) {
console.error("❌ Failed to update session:", error);
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || "Failed to update session",
});
}
});
+78
View File
@@ -0,0 +1,78 @@
// server/api/hak-akses/entities.get.ts
import Database from 'better-sqlite3';
import { join } from 'path';
import { existsSync } from 'fs';
// Helper to get database path
const getDbPath = () => {
return join(process.cwd(), 'data', 'users.db');
};
export default defineEventHandler(async (event) => {
console.log("📥 Fetching entities from local database (users.db)");
try {
const dbPath = getDbPath();
if (!existsSync(dbPath)) {
return {
success: true,
data: { roles: [], groups: [], users: [] }
};
}
const db = new Database(dbPath);
// 1. Get all users
const users = db.prepare('SELECT id, namaLengkap, namaUser, email, realmRoles, groups FROM users ORDER BY namaLengkap ASC').all() as any[];
const allRoles = new Set<string>();
const allGroups = new Set<string>();
const formattedUsers: any[] = [];
users.forEach(user => {
// Collect unique roles
const roles = JSON.parse(user.realmRoles || '[]');
roles.forEach((r: string) => allRoles.add(r));
// Collect unique groups
const groups = JSON.parse(user.groups || '[]');
groups.forEach((g: string) => allGroups.add(g));
// Format individual user
formattedUsers.push({
id: user.id,
name: user.namaLengkap,
username: user.namaUser,
email: user.email,
type: 'user'
});
});
db.close();
return {
success: true,
data: {
roles: Array.from(allRoles).sort().map(name => ({
id: name,
name: name,
type: 'role'
})),
groups: Array.from(allGroups).sort().map(name => ({
id: name,
name: name,
type: 'group'
})),
users: formattedUsers
}
};
} catch (error: any) {
console.error("❌ Error fetching local entities:", error);
throw createError({
statusCode: 500,
statusMessage: error.message || "Failed to fetch local entities",
});
}
});
+104
View File
@@ -0,0 +1,104 @@
import fs from 'fs';
import path from 'path';
import type { HakAkses } from '~/types/setting';
import { randomUUID } from 'node:crypto'; // Use standard node crypto
const filePath = path.resolve('data/mock/hakAkses.json');
// Helper to read JSON file
const readData = (): HakAkses[] => {
try {
if (!fs.existsSync(filePath)) {
// Ensure directory exists
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, '[]', 'utf-8');
return [];
}
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 or Update hak akses
if (method === 'POST') {
try {
const body = await readBody(event);
const data = readData();
if (body.id) {
// Update existing
const index = data.findIndex(h => h.id === body.id);
if (index !== -1) {
data[index] = {
...data[index],
...body
};
} else {
data.push(body);
}
} else {
// Create new
const newId = randomUUID();
const newHakAkses: HakAkses = {
id: newId,
namaHakAkses: body.namaHakAkses,
status: body.status || 'aktif',
pages: body.pages || []
};
data.push(newHakAkses);
}
const success = writeData(data);
if (success) {
return {
success: true,
message: 'Hak akses berhasil disimpan',
data: body.id ? body : data[data.length - 1]
};
} else {
throw new Error('Failed to save data');
}
} catch (error) {
return {
success: false,
message: 'Gagal menyimpan hak akses',
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
return {
success: false,
message: 'Method not allowed'
};
});
+66
View File
@@ -0,0 +1,66 @@
// server/api/hak-akses/members.get.ts
import Database from 'better-sqlite3';
import { join } from 'path';
import { existsSync } from 'fs';
// Helper to get database path
const getDbPath = () => {
return join(process.cwd(), 'data', 'users.db');
};
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { type, name, id } = query;
if (!type || (!name && !id)) {
throw createError({
statusCode: 400,
statusMessage: "Type and (Name or ID) are required",
});
}
try {
const dbPath = getDbPath();
if (!existsSync(dbPath)) {
return { success: true, data: [] };
}
const db = new Database(dbPath);
const users = db.prepare('SELECT id, namaLengkap, namaUser, email, realmRoles, groups FROM users').all() as any[];
db.close();
let filteredUsers = [];
if (type === 'role') {
filteredUsers = users.filter(user => {
const roles = JSON.parse(user.realmRoles || '[]');
return roles.includes(name);
});
} else if (type === 'group') {
filteredUsers = users.filter(user => {
const groups = JSON.parse(user.groups || '[]');
return groups.includes(name);
});
} else if (type === 'user') {
// For type 'user', name is the username
filteredUsers = users.filter(user => user.namaUser === name);
}
return {
success: true,
data: filteredUsers.map((m: any) => ({
id: m.id,
username: m.namaUser,
email: m.email,
name: m.namaLengkap
}))
};
} catch (error: any) {
console.error("❌ Error fetching local members:", error);
throw createError({
statusCode: 500,
statusMessage: error.message || "Failed to fetch local members",
});
}
});
+9 -7
View File
@@ -107,8 +107,10 @@ const normalizeGroup = (group: string): string => {
};
// Normalize role name
const normalizeRole = (role: string): string => {
const normalized = role.toLowerCase().trim();
const normalizeRole = async (role: string): Promise<string> => {
const { slugify } = await import('~/server/utils/userSync');
const normalized = slugify(role);
// Mapping khusus untuk role default
if (normalized === 'default-roles-sandbox') {
return 'superadmin';
@@ -117,8 +119,8 @@ const normalizeRole = (role: string): string => {
};
// Get placeholder data for a role+group combination
const getPlaceholderData = (role: string, group: string): any | null => {
const normalizedRole = normalizeRole(role);
const getPlaceholderData = async (role: string, group: string): Promise<any | null> => {
const normalizedRole = await normalizeRole(role);
const normalizedGroup = normalizeGroup(group);
const key = `${normalizedRole}_${normalizedGroup}`;
@@ -166,14 +168,14 @@ export default defineEventHandler(async (event) => {
let primaryGroup = groupsArray[0] || '';
// Normalize role and group
primaryRole = normalizeRole(primaryRole);
primaryRole = await normalizeRole(primaryRole);
primaryGroup = normalizeGroup(primaryGroup);
console.log(`📋 Normalized params - roles: ${primaryRole}, groups: ${primaryGroup}`);
// Check for placeholder data first if placeholder mode is forced
if (forcePlaceholder && !disablePlaceholder) {
const placeholderData = getPlaceholderData(primaryRole, primaryGroup);
const placeholderData = await getPlaceholderData(primaryRole, primaryGroup);
if (placeholderData) {
console.log(`📦 Using placeholder data (forced) for role: ${primaryRole}, group: ${primaryGroup}`);
return placeholderData;
@@ -226,7 +228,7 @@ export default defineEventHandler(async (event) => {
});
// Fallback to placeholder data if available
const placeholderData = getPlaceholderData(primaryRole, primaryGroup);
const placeholderData = await getPlaceholderData(primaryRole, primaryGroup);
if (placeholderData) {
console.log(`📦 Falling back to placeholder data for role: ${primaryRole}, group: ${primaryGroup}`);
return placeholderData;
+33 -7
View File
@@ -73,21 +73,41 @@ export default defineEventHandler(async (event) => {
idTokenPayload?.groups ||
[];
// Determine tipeUser from groups or roles if possible
// You can customize this mapping based on your business logic
// Build user data object
let tipeUser = '';
if (Array.isArray(groups) && groups.length > 0) {
// Extract tipeUser from groups path (e.g., "/Instalasi STIM/Devops/Superadmin" -> "Superadmin")
// Check if tipeUser exists in database
try {
const Database = (await import('better-sqlite3')).default;
const { join } = await import('path');
const { existsSync } = await import('fs');
const dbPath = join(process.cwd(), 'data', 'users.db');
if (existsSync(dbPath)) {
const db = new Database(dbPath);
const dbUser = db.prepare('SELECT tipeUser FROM users WHERE id = ?').get(idTokenPayload?.sub || session.user?.id) as any;
if (dbUser && dbUser.tipeUser) {
tipeUser = dbUser.tipeUser;
console.log(`✅ Using tipeUser from database: ${tipeUser}`);
}
db.close();
}
} catch (dbError) {
console.warn('⚠️ Failed to fetch tipeUser from database:', dbError);
}
// Fallback to groups if not in database
if (!tipeUser && Array.isArray(groups) && groups.length > 0) {
const { slugify } = await import('~/server/utils/userSync');
const lastGroup = groups[groups.length - 1];
if (typeof lastGroup === 'string') {
const parts = lastGroup.split('/').filter(Boolean);
if (parts.length > 0) {
tipeUser = parts[parts.length - 1]; // Get last part of path
tipeUser = slugify(parts[parts.length - 1]);
}
}
}
// Build user data object
const userData = {
id: idTokenPayload?.sub || session.user?.id,
namaLengkap: idTokenPayload?.name ||
@@ -105,13 +125,19 @@ export default defineEventHandler(async (event) => {
accountRoles: Array.isArray(accountRoles) ? accountRoles : [],
resourceRoles: Array.isArray(resourceRoles) ? resourceRoles : [],
groups: Array.isArray(groups) ? groups : [],
tipeUser: tipeUser, // Extracted from groups or empty
tipeUser: tipeUser,
lastLogin: null, // Will be set on sync
// Include full token payloads for reference
idTokenPayload,
accessTokenPayload,
};
// IMPORTANT: Inject tipeUser as a role so checkPageAccess/useHakAkses can find it
if (tipeUser && !userData.roles.includes(tipeUser)) {
userData.roles.push(tipeUser);
console.log(`🛡️ Injected tipeUser '${tipeUser}' as a role for access control`);
}
console.log("✅ Current user data extracted from JWT");
return userData;
} catch (parseError: any) {