update hak akses dan perbaikan api pembuatan tiket
This commit is contained in:
No files matched your search
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
};
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user