perbaikan keycloak dan config
This commit is contained in:
+18
-8
@@ -16,19 +16,29 @@ export const useAuth = () => {
|
||||
isLoading.value = true
|
||||
clearError()
|
||||
|
||||
const response = await $fetch<SessionResponse>('/api/auth/session')
|
||||
// The session API returns SessionResponse, or throws 401 if not authenticated
|
||||
// $fetch automatically sends cookies for same-origin requests
|
||||
const response = await $fetch<SessionResponse>('/api/auth/session', {
|
||||
credentials: 'include' // Explicitly include cookies (though $fetch does this by default)
|
||||
})
|
||||
|
||||
if (response.success === false && response.error) {
|
||||
error.value = response.error
|
||||
user.value = null
|
||||
return null
|
||||
// Handle response structure - session API returns { success, user, ... }
|
||||
if (response && response.user) {
|
||||
user.value = response.user
|
||||
return response.user
|
||||
}
|
||||
|
||||
user.value = response.user
|
||||
return response.user
|
||||
// If response exists but no user, clear and return null
|
||||
user.value = null
|
||||
return null
|
||||
} catch (fetchError: any) {
|
||||
console.error('Session check failed:', fetchError)
|
||||
error.value = 'Failed to check authentication status'
|
||||
// 401 errors are expected when not authenticated
|
||||
if (fetchError.statusCode === 401 || fetchError.status === 401) {
|
||||
error.value = null // Don't show error for expected unauthenticated state
|
||||
} else {
|
||||
error.value = 'Failed to check authentication status'
|
||||
}
|
||||
user.value = null
|
||||
return null
|
||||
} finally {
|
||||
|
||||
+55
-3
@@ -28,8 +28,13 @@ export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) =>
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip auth check if it's the development server side render pass.
|
||||
// On server-side during development, skip intensive checks
|
||||
// The cookie check will happen on client-side
|
||||
if (process.server && process.env.NODE_ENV === 'development') {
|
||||
if (to.query.authenticated === 'true') {
|
||||
console.log('⏭️ Server-side: Allowing authenticated redirect to pass through (dev mode)');
|
||||
return; // Allow server-side render to proceed, client will verify
|
||||
}
|
||||
console.log('⏭️ Skipping intensive check on server-side during development');
|
||||
useAuth();
|
||||
return;
|
||||
@@ -39,8 +44,55 @@ export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) =>
|
||||
const isAuthRedirect: boolean = to.query.authenticated === 'true';
|
||||
|
||||
if (isAuthRedirect) {
|
||||
console.log('⏳ Client-side is processing a new login session, allowing the route to load...');
|
||||
return navigateTo({ path: to.path, query: {} }, { replace: true });
|
||||
console.log('⏳ Client-side is processing a new login session, checking cookie...');
|
||||
|
||||
// Give the browser a moment to process the cookie from the redirect
|
||||
if (process.client) {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
// Check authentication first before removing query parameter
|
||||
try {
|
||||
const { checkAuth } = useAuth();
|
||||
console.log('🔍 Checking authentication after redirect...');
|
||||
|
||||
let user = await checkAuth();
|
||||
|
||||
// If not found, retry once more
|
||||
if (!user && process.client) {
|
||||
console.log('⚠️ Cookie not available yet, retrying...');
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
user = await checkAuth();
|
||||
}
|
||||
|
||||
if (user) {
|
||||
console.log('✅ User is authenticated after redirect:', user.name || user.preferred_username || user.email);
|
||||
// Remove query parameter and allow access
|
||||
await navigateTo({ path: to.path, query: {} }, { replace: true });
|
||||
return; // Allow access
|
||||
} else {
|
||||
console.log('❌ Still no session after retry, redirecting to login');
|
||||
return navigateTo('/LoginPage');
|
||||
}
|
||||
} catch (authError) {
|
||||
console.error('❌ Auth check failed after redirect:', authError);
|
||||
// Retry once
|
||||
if (process.client) {
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
try {
|
||||
const { checkAuth } = useAuth();
|
||||
const retryUser = await checkAuth();
|
||||
if (retryUser) {
|
||||
console.log('✅ User authenticated on retry after error');
|
||||
await navigateTo({ path: to.path, query: {} }, { replace: true });
|
||||
return;
|
||||
}
|
||||
} catch (retryError) {
|
||||
console.error('❌ Retry also failed:', retryError);
|
||||
}
|
||||
}
|
||||
return navigateTo('/LoginPage');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+18
-18
@@ -37,23 +37,23 @@ export default defineNuxtConfig({
|
||||
// @ts-expect-error
|
||||
config.plugins.push(vuetify({ autoImport: true }));
|
||||
|
||||
// Add HTTPS plugin
|
||||
try {
|
||||
// @ts-ignore
|
||||
const { default: basicSsl } = await import('@vitejs/plugin-basic-ssl');
|
||||
// @ts-expect-error
|
||||
config.plugins.push(basicSsl());
|
||||
// @ts-expect-error
|
||||
config.server = config.server || {};
|
||||
// @ts-expect-error
|
||||
config.server.https = true;
|
||||
// @ts-expect-error
|
||||
config.server.host = '10.10.150.114';
|
||||
// @ts-expect-error
|
||||
config.server.port = 3001;
|
||||
} catch (e) {
|
||||
console.warn('Failed to load HTTPS plugin:', e);
|
||||
}
|
||||
// // Add HTTPS plugin
|
||||
// try {
|
||||
// // @ts-ignore
|
||||
// const { default: basicSsl } = await import('@vitejs/plugin-basic-ssl');
|
||||
// // @ts-expect-error
|
||||
// config.plugins.push(basicSsl());
|
||||
// // @ts-expect-error
|
||||
// config.server = config.server || {};
|
||||
// // @ts-expect-error
|
||||
// config.server.https = true;
|
||||
// // @ts-expect-error
|
||||
// config.server.host = '10.10.150.175';
|
||||
// // @ts-expect-error
|
||||
// config.server.port = 3001;
|
||||
// } catch (e) {
|
||||
// console.warn('Failed to load HTTPS plugin:', e);
|
||||
// }
|
||||
});
|
||||
},
|
||||
],
|
||||
@@ -96,7 +96,7 @@ export default defineNuxtConfig({
|
||||
|
||||
devServer: {
|
||||
port: 3001,
|
||||
host: '10.10.150.114'
|
||||
host: 'localhost'
|
||||
},
|
||||
|
||||
vite: {
|
||||
|
||||
+3
-4
@@ -4,11 +4,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"_command_dev": "nuxt dev -o --host --port 3001",
|
||||
"_command_dev2": "nuxt dev -o --port 3001",
|
||||
"_command_dev3": "nuxt dev -o --host 10.10.150.114 --port 3001",
|
||||
"_command_dev": "nuxt dev -o --host --port 3000",
|
||||
"_command_dev2": "nuxt dev -o --port 3000",
|
||||
"dev": "nuxt dev -o",
|
||||
"dev:https": "nuxt dev -o --host localhost --port 3001",
|
||||
"dev:https": "nuxt dev -o --host localhost --port 3000",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ import type { LoginResponse } from '~/types/auth'
|
||||
// Use guest middleware to redirect if already authenticated
|
||||
definePageMeta({
|
||||
layout: 'empty',
|
||||
middleware:['auth','guest']
|
||||
middleware: ['guest']
|
||||
})
|
||||
|
||||
// Reactive state
|
||||
|
||||
@@ -137,39 +137,77 @@ export default defineEventHandler(async (event) => {
|
||||
return sendRedirect(event, `/LoginPage?error=${errorMsg}`);
|
||||
}
|
||||
|
||||
// Store minimal session data in cookie to reduce size
|
||||
// The ID token contains user info, so we can decode it when needed
|
||||
const sessionData = {
|
||||
// Store only essential user info (can be decoded from ID token if needed)
|
||||
user: {
|
||||
id: idTokenPayload.sub,
|
||||
email: idTokenPayload.email,
|
||||
name: idTokenPayload.name || idTokenPayload.preferred_username,
|
||||
preferred_username: idTokenPayload.preferred_username,
|
||||
given_name: idTokenPayload.given_name,
|
||||
family_name: idTokenPayload.family_name,
|
||||
},
|
||||
// Store tokens - these are necessary for API calls
|
||||
// Note: These JWT tokens are large, but necessary for authentication
|
||||
accessToken: tokens.access_token,
|
||||
idToken: tokens.id_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
// CHANGED: Use custom session duration instead of Keycloak's token expiry
|
||||
// Session metadata
|
||||
expiresAt: Date.now() + (SESSION_DURATION * 1000),
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
const isSecure = process.env.NODE_ENV === 'production' ||
|
||||
// Determine if we should use secure cookies
|
||||
// For localhost, always use secure: false
|
||||
const isSecure = process.env.NODE_ENV === 'production' &&
|
||||
event.node.req.headers['x-forwarded-proto'] === 'https';
|
||||
|
||||
console.log('🔗 Setting session cookie with secure flag:', isSecure);
|
||||
console.log('⏱️ Session duration:', SESSION_DURATION, 'seconds');
|
||||
console.log('🌐 Request host:', event.node.req.headers.host);
|
||||
console.log('🔒 Protocol:', event.node.req.headers['x-forwarded-proto'] || 'http');
|
||||
|
||||
setCookie(event, 'user_session', JSON.stringify(sessionData), {
|
||||
// Set cookie with proper settings for localhost
|
||||
// For localhost HTTP, we need secure: false and sameSite: 'lax'
|
||||
// IMPORTANT: Ensure domain is not set for localhost (allows cookie to work)
|
||||
const cookieOptions: any = {
|
||||
httpOnly: true,
|
||||
secure: isSecure,
|
||||
sameSite: 'lax',
|
||||
// CHANGED: Use custom session duration (7 days default)
|
||||
sameSite: 'lax' as const,
|
||||
maxAge: SESSION_DURATION,
|
||||
path: '/',
|
||||
});
|
||||
// Explicitly don't set domain for localhost - this is important!
|
||||
// Setting domain to 'localhost' can cause cookies to not work
|
||||
};
|
||||
|
||||
// For localhost, don't set domain (allows cookie to work on localhost)
|
||||
// Only set domain in production if needed
|
||||
if (process.env.NODE_ENV === 'production' && !event.node.req.headers.host?.includes('localhost')) {
|
||||
// Optionally set domain in production
|
||||
// cookieOptions.domain = '.yourdomain.com';
|
||||
}
|
||||
|
||||
// Store session in server-side store and use session ID in cookie
|
||||
// This avoids cookie size limits (4KB)
|
||||
const { createSession } = await import('~/server/utils/sessionStore');
|
||||
const sessionId = createSession(sessionData);
|
||||
|
||||
console.log('💾 Session stored server-side with ID:', sessionId.substring(0, 8) + '...');
|
||||
console.log('📦 Session ID cookie size: ~64 bytes (much smaller!)');
|
||||
|
||||
// Store only the session ID in the cookie (much smaller)
|
||||
setCookie(event, 'user_session', sessionId, cookieOptions);
|
||||
|
||||
console.log('✅ Session ID cookie set in response headers (will be available in next request)');
|
||||
|
||||
console.log('✅ Session cookie created successfully');
|
||||
console.log('🍪 Cookie details:');
|
||||
console.log(' - Path: /');
|
||||
console.log(' - Secure:', isSecure);
|
||||
console.log(' - SameSite: lax');
|
||||
console.log(' - HttpOnly: true');
|
||||
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
|
||||
@@ -182,11 +220,17 @@ export default defineEventHandler(async (event) => {
|
||||
console.error('⚠️ Failed to auto-sync user on login:', syncError);
|
||||
}
|
||||
|
||||
const testCookie = getCookie(event, 'user_session');
|
||||
console.log('🧪 Cookie test - can read back in this handler (Expected False):', !!testCookie);
|
||||
// IMPORTANT: Ensure cookie is set before redirect
|
||||
// The cookie should be in the Set-Cookie header of the redirect response
|
||||
console.log('↪️ Redirecting to dashboard with cookie in response headers...');
|
||||
|
||||
console.log('↪️ Redirecting to dashboard...');
|
||||
return sendRedirect(event, '/dashboard?authenticated=true');
|
||||
// Note: In H3/Nitro, setCookie automatically adds Set-Cookie header to response
|
||||
// The cookie will be available in the browser after the redirect
|
||||
// We can't verify it in the same request, but it should be set correctly
|
||||
|
||||
// Use sendRedirect - it should include the Set-Cookie header
|
||||
// The browser will receive the cookie and include it in the next request
|
||||
return sendRedirect(event, '/dashboard?authenticated=true', 302);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ === CALLBACK ERROR ===');
|
||||
|
||||
@@ -5,16 +5,21 @@ export default defineEventHandler(async (event) => {
|
||||
console.log('🚪 Logout handler called');
|
||||
|
||||
// Get the current session to retrieve tokens
|
||||
const sessionCookie = getCookie(event, 'user_session');
|
||||
const sessionId = getCookie(event, 'user_session');
|
||||
let idToken = null;
|
||||
|
||||
if (sessionCookie) {
|
||||
if (sessionId) {
|
||||
try {
|
||||
const session = JSON.parse(sessionCookie);
|
||||
idToken = session.idToken;
|
||||
console.log('🔑 ID token found in session:', !!idToken);
|
||||
const { getSession, deleteSession } = await import('~/server/utils/sessionStore');
|
||||
const session = getSession(sessionId);
|
||||
if (session) {
|
||||
idToken = session.idToken;
|
||||
console.log('🔑 ID token found in session:', !!idToken);
|
||||
// Delete session from store
|
||||
deleteSession(sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not parse session cookie:', error);
|
||||
console.warn('⚠️ Could not retrieve session:', error);
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ No session cookie found');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/api/auth/session.get.ts
|
||||
import type { SessionResponse } from '~/types/auth'
|
||||
|
||||
// Helper function to safely decode the JWT payload (Access Token or ID Token)
|
||||
const decodeTokenPayload = (token: string | undefined): any | null => {
|
||||
@@ -23,10 +24,10 @@ const decodeTokenPayload = (token: string | undefined): any | null => {
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log("🔍 Session endpoint called");
|
||||
|
||||
const sessionCookie = getCookie(event, "user_session");
|
||||
console.log("🍪 Session cookie exists:", !!sessionCookie);
|
||||
const sessionId = getCookie(event, "user_session");
|
||||
console.log("🍪 Session cookie exists:", !!sessionId);
|
||||
|
||||
if (!sessionCookie) {
|
||||
if (!sessionId) {
|
||||
console.log("❌ No session cookie found");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
@@ -35,8 +36,20 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const session = JSON.parse(sessionCookie);
|
||||
console.log("📋 Session parsed successfully");
|
||||
// Get session from server-side store using session ID
|
||||
const { getSession } = await import('~/server/utils/sessionStore');
|
||||
const session = getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
console.log("❌ Session not found or expired");
|
||||
deleteCookie(event, "user_session");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Session expired or invalid",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("📋 Session retrieved from store successfully");
|
||||
|
||||
const isExpired = Date.now() > session.expiresAt;
|
||||
console.log(" Is Expired:", isExpired);
|
||||
@@ -55,27 +68,29 @@ export default defineEventHandler(async (event) => {
|
||||
const idTokenPayload = decodeTokenPayload(session.idToken);
|
||||
const accessTokenPayload = decodeTokenPayload(session.accessToken);
|
||||
|
||||
// Final response object for the frontend debug page
|
||||
const sessionResponse = {
|
||||
// Final response object - ensure it matches SessionResponse interface
|
||||
const sessionResponse: SessionResponse & {
|
||||
idTokenPayload?: any
|
||||
accessTokenPayload?: any
|
||||
fullSessionObject?: any
|
||||
status?: string
|
||||
} = {
|
||||
success: true,
|
||||
// Basic User Info
|
||||
user: session.user,
|
||||
|
||||
// Raw Tokens
|
||||
idToken: session.idToken,
|
||||
// Raw Tokens (optional in SessionResponse)
|
||||
accessToken: session.accessToken,
|
||||
refreshToken: session.refreshToken,
|
||||
|
||||
// Session Timestamps
|
||||
// Session Timestamps (optional in SessionResponse)
|
||||
expiresAt: session.expiresAt,
|
||||
createdAt: session.createdAt,
|
||||
|
||||
// Parsed Payloads
|
||||
// Additional debug fields (not in SessionResponse interface)
|
||||
idToken: session.idToken,
|
||||
idTokenPayload: idTokenPayload,
|
||||
accessTokenPayload: accessTokenPayload,
|
||||
|
||||
// Raw Session Data (for Debug section)
|
||||
fullSessionObject: session,
|
||||
|
||||
status: "authenticated",
|
||||
};
|
||||
|
||||
|
||||
@@ -5,26 +5,17 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log("🔄 User sync endpoint called");
|
||||
|
||||
const sessionCookie = getCookie(event, "user_session");
|
||||
const { getSessionFromCookie } = await import('~/server/utils/sessionStore');
|
||||
const session = await getSessionFromCookie(event);
|
||||
|
||||
if (!sessionCookie) {
|
||||
if (!session) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "No session cookie found",
|
||||
statusMessage: "No session found or session expired",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const session = JSON.parse(sessionCookie);
|
||||
|
||||
const isExpired = Date.now() > session.expiresAt;
|
||||
if (isExpired) {
|
||||
deleteCookie(event, "user_session");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Session expired",
|
||||
});
|
||||
}
|
||||
|
||||
// Use the shared sync utility
|
||||
// Use session createdAt as loginTime, or current time if not available
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// server/utils/sessionStore.ts
|
||||
// Simple in-memory session store (for development)
|
||||
// In production, use Redis or a database
|
||||
|
||||
import { getCookie } from 'h3'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
interface SessionData {
|
||||
user: any;
|
||||
accessToken: string;
|
||||
idToken: string;
|
||||
refreshToken: string;
|
||||
expiresAt: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
const sessions = new Map<string, SessionData>();
|
||||
|
||||
// Clean up expired sessions every 5 minutes
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [sessionId, session] of sessions.entries()) {
|
||||
if (session.expiresAt < now) {
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
export function createSession(data: SessionData): string {
|
||||
// Generate a secure random session ID
|
||||
const sessionId = randomBytes(32).toString('hex');
|
||||
sessions.set(sessionId, data);
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
export function getSession(sessionId: string): SessionData | null {
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if expired
|
||||
if (session.expiresAt < Date.now()) {
|
||||
sessions.delete(sessionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export function deleteSession(sessionId: string): void {
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
|
||||
// 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');
|
||||
if (!sessionId) {
|
||||
return null;
|
||||
}
|
||||
return getSession(sessionId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user