fix(FE) : fix store token
This commit is contained in:
@@ -7,57 +7,9 @@ import { Icon } from "@iconify/vue";
|
||||
import { useRoute } from "vue-router";
|
||||
// MiniSidebar Icons
|
||||
import MiniSideIcons from "./MinIconItems";
|
||||
import { useHakAkses } from "~/composables/useHakAkses";
|
||||
|
||||
const route = useRoute();
|
||||
const { getAllowedPages } = useHakAkses();
|
||||
|
||||
const allowedPages = ref<string[]>([]);
|
||||
const isLoadingAccess = ref(true);
|
||||
|
||||
// Fetch allowed pages on mount
|
||||
onMounted(async () => {
|
||||
try {
|
||||
allowedPages.value = await getAllowedPages();
|
||||
console.log('Allowed pages for user:', allowedPages.value);
|
||||
} catch (error) {
|
||||
console.error('Error loading allowed pages:', error);
|
||||
} finally {
|
||||
isLoadingAccess.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Filter sidebar items based on user's allowed pages
|
||||
const filteredSidebarItems = computed(() => {
|
||||
if (isLoadingAccess.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If no allowed pages, return empty (no access)
|
||||
if (allowedPages.value.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return sidebarItems.map(section => {
|
||||
// Filter children based on allowed pages
|
||||
const filteredChildren = section.children?.filter(child => {
|
||||
if (child.to) {
|
||||
return allowedPages.value.includes(child.to);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Only include section if it has visible children
|
||||
if (filteredChildren && filteredChildren.length > 0) {
|
||||
return {
|
||||
...section,
|
||||
children: filteredChildren
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}).filter(item => item !== null);
|
||||
});
|
||||
|
||||
const findTitleByPath = (items: any, path: any) => {
|
||||
let title = "";
|
||||
|
||||
@@ -1,41 +1,4 @@
|
||||
[
|
||||
{
|
||||
"id": "9ce98d4d-2a5f-4ba4-ab1b-732ed6841bef",
|
||||
"namaHakAkses": "manage-account",
|
||||
"status": "tidak aktif",
|
||||
"pages": [
|
||||
{
|
||||
"id": "1",
|
||||
"namaPage": "Dashboard",
|
||||
"path": "/dashboard",
|
||||
"icon": "solar:home-bold-duotone"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"namaPage": "Pengguna",
|
||||
"path": "/users",
|
||||
"icon": "solar:user-bold-duotone"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"namaPage": "Hak Akses",
|
||||
"path": "/hakAkses",
|
||||
"icon": "solar:lock-bold-duotone"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ceb0e9c4-f377-4060-ad39-800410132f89",
|
||||
"namaHakAkses": "manage-account-links",
|
||||
"status": "tidak aktif",
|
||||
"pages": []
|
||||
},
|
||||
{
|
||||
"id": "8d051ec2-1f42-4750-8ef2-11b2e15f819d",
|
||||
"namaHakAkses": "view-profile",
|
||||
"status": "tidak aktif",
|
||||
"pages": []
|
||||
},
|
||||
{
|
||||
"id": "d86a0a46-28ec-48bc-ae51-2679a61cbd83",
|
||||
"namaHakAkses": "superadmin",
|
||||
|
||||
@@ -129,6 +129,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
let idTokenPayload;
|
||||
let accessTokenPayload;
|
||||
let refreshTokenPayload;
|
||||
try {
|
||||
idTokenPayload = JSON.parse(
|
||||
Buffer.from(tokens.id_token.split('.')[1], 'base64').toString()
|
||||
@@ -136,23 +137,22 @@ export default defineEventHandler(async (event) => {
|
||||
accessTokenPayload = JSON.parse(
|
||||
Buffer.from(tokens.access_token.split('.')[1], 'base64').toString()
|
||||
);
|
||||
refreshTokenPayload = JSON.parse(
|
||||
Buffer.from(tokens.refresh_token.split('.')[1], 'base64').toString()
|
||||
);
|
||||
} catch (decodeError) {
|
||||
console.error('❌ Failed to decode ID token:', decodeError);
|
||||
const errorMsg = encodeURIComponent('Invalid ID token format');
|
||||
console.error('❌ Failed to decode token:', decodeError);
|
||||
const errorMsg = encodeURIComponent('Invalid 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?.[config.keycloakClientId]?.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);
|
||||
|
||||
console.log("refreshTokenPayload.exp:", refreshTokenPayload.exp);
|
||||
console.log("Current time (seconds):", Math.floor(Date.now() / 1000));
|
||||
console.log("Token expires in (seconds):", refreshTokenPayload.exp - Math.floor(Date.now() / 1000));
|
||||
// Store minimal session data in cookie to reduce size
|
||||
// The ID token contains user info, so we can decode it when needed
|
||||
const sessionData = {
|
||||
@@ -162,8 +162,6 @@ 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
|
||||
@@ -172,8 +170,8 @@ export default defineEventHandler(async (event) => {
|
||||
idToken: tokens.id_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
// Session metadata
|
||||
expiresAt: Date.now() + (SESSION_DURATION * 1000),
|
||||
createdAt: Date.now(),
|
||||
expiresAt: refreshTokenPayload.exp * 1000, // Convert to milliseconds
|
||||
createdAt: refreshTokenPayload.iat ? refreshTokenPayload.iat * 1000 : Date.now(),
|
||||
};
|
||||
|
||||
// Determine if we should use secure cookies
|
||||
|
||||
@@ -67,6 +67,7 @@ export default defineEventHandler(async (event) => {
|
||||
// Decode tokens and prepare the enhanced response data
|
||||
const idTokenPayload = decodeTokenPayload(session.idToken);
|
||||
const accessTokenPayload = decodeTokenPayload(session.accessToken);
|
||||
const refreshTokenPayload = decodeTokenPayload(session.refreshToken);
|
||||
|
||||
// Final response object - ensure it matches SessionResponse interface
|
||||
const sessionResponse: SessionResponse & {
|
||||
@@ -85,13 +86,12 @@ export default defineEventHandler(async (event) => {
|
||||
refreshToken: session.refreshToken,
|
||||
|
||||
// Session Timestamps (optional in SessionResponse)
|
||||
expiresAt: session.expiresAt,
|
||||
|
||||
expiresAt: refreshTokenPayload?.exp ? refreshTokenPayload.exp * 1000 : session.expiresAt,
|
||||
// Additional debug fields (not in SessionResponse interface)
|
||||
idToken: session.idToken,
|
||||
idTokenPayload: idTokenPayload,
|
||||
accessTokenPayload: accessTokenPayload,
|
||||
fullSessionObject: session,
|
||||
// fullSessionObject: session,
|
||||
status: "authenticated",
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -52,6 +52,22 @@ export function deleteSession(sessionId: string): void {
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
|
||||
export function updateSession(sessionId: string, updates: Partial<SessionData>): boolean {
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the session with new data
|
||||
const updatedSession = {
|
||||
...session,
|
||||
...updates,
|
||||
};
|
||||
|
||||
sessions.set(sessionId, updatedSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper function to get session from cookie (for use in API handlers)
|
||||
export async function getSessionFromCookie(event: any): Promise<SessionData | null> {
|
||||
const sessionId = getCookie(event, 'user_session');
|
||||
|
||||
@@ -74,6 +74,39 @@ const refreshAccessToken = async (): Promise<string | null> => {
|
||||
localStorage.setItem('refreshToken', data.refresh_token)
|
||||
}
|
||||
|
||||
// let refreshTokenPayload;
|
||||
// try {
|
||||
// refreshTokenPayload = JSON.parse(
|
||||
// Buffer.from(data.refresh_token.split('.')[1], 'base64').toString()
|
||||
// );
|
||||
// } catch (decodeError) {
|
||||
// console.error('❌ Failed to decode refresh token:', decodeError);
|
||||
// throw new Error('Invalid refresh token format');
|
||||
// }
|
||||
|
||||
// const expiresAt = refreshTokenPayload.exp * 1000
|
||||
|
||||
|
||||
// Update server-side session with new tokens
|
||||
try {
|
||||
await fetch('/api/auth/session', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
accessToken: data.access_token,
|
||||
idToken: data.id_token,
|
||||
refreshToken: data.refresh_token,
|
||||
// expiresAt: expiresAt
|
||||
}),
|
||||
})
|
||||
console.log('✅ Server session updated with new tokens')
|
||||
} catch (sessionError) {
|
||||
console.error('⚠️ Failed to update server session:', sessionError)
|
||||
// Don't throw error here - token refresh was successful, session update is secondary
|
||||
}
|
||||
|
||||
console.log('✅ Token refreshed successfully')
|
||||
return data.id_token || data.access_token
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user