2025-06-14 14:50:29 +02:00
|
|
|
import type Keycloak from 'keycloak-js'
|
|
|
|
|
|
|
|
|
|
export const useKeycloak = () => {
|
|
|
|
|
const config = useRuntimeConfig()
|
|
|
|
|
const keycloak = ref<Keycloak | null>(null)
|
|
|
|
|
const isAuthenticated = ref(false)
|
|
|
|
|
const user = ref<any>(null)
|
|
|
|
|
const token = ref<string | null>(null)
|
|
|
|
|
const isInitialized = ref(false)
|
|
|
|
|
|
2025-06-14 15:26:26 +02:00
|
|
|
// Get the correct base URL considering proxy setup
|
|
|
|
|
const getBaseUrl = () => {
|
|
|
|
|
if (process.server) {
|
|
|
|
|
// In production, always use the configured HTTPS base URL
|
|
|
|
|
return config.public.baseUrl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client-side: detect if we're behind a proxy
|
|
|
|
|
const currentOrigin = window.location.origin
|
|
|
|
|
const isProduction = !currentOrigin.includes('localhost') && !currentOrigin.includes('127.0.0.1')
|
|
|
|
|
|
|
|
|
|
if (isProduction) {
|
|
|
|
|
// Force HTTPS in production environments
|
|
|
|
|
return config.public.baseUrl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Development: use current origin
|
|
|
|
|
return currentOrigin
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const logDebug = (message: string, data?: any) => {
|
|
|
|
|
if (config.public.keycloakDebug) {
|
|
|
|
|
console.log(`[KEYCLOAK] ${message}`, data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 14:50:29 +02:00
|
|
|
const initKeycloak = async () => {
|
2025-06-14 15:01:45 +02:00
|
|
|
if (process.server) return false
|
2025-06-14 14:50:29 +02:00
|
|
|
|
|
|
|
|
try {
|
2025-06-14 15:26:26 +02:00
|
|
|
const baseUrl = getBaseUrl()
|
|
|
|
|
logDebug('Initializing Keycloak', {
|
|
|
|
|
baseUrl,
|
|
|
|
|
keycloakUrl: config.public.keycloak.url,
|
|
|
|
|
realm: config.public.keycloak.realm,
|
|
|
|
|
clientId: config.public.keycloak.clientId
|
|
|
|
|
})
|
|
|
|
|
|
2025-06-14 14:50:29 +02:00
|
|
|
// Dynamically import keycloak-js
|
|
|
|
|
const KeycloakModule = await import('keycloak-js')
|
|
|
|
|
const KeycloakConstructor = KeycloakModule.default
|
|
|
|
|
|
|
|
|
|
keycloak.value = new KeycloakConstructor({
|
|
|
|
|
url: config.public.keycloak.url,
|
|
|
|
|
realm: config.public.keycloak.realm,
|
|
|
|
|
clientId: config.public.keycloak.clientId,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const authenticated = await keycloak.value.init({
|
|
|
|
|
onLoad: 'check-sso',
|
2025-06-14 15:26:26 +02:00
|
|
|
// Use proper HTTPS redirect URI
|
|
|
|
|
redirectUri: `${baseUrl}/auth/callback`,
|
2025-06-14 15:38:40 +02:00
|
|
|
// Disable all iframe-based features that cause CORS issues
|
|
|
|
|
checkLoginIframe: false,
|
|
|
|
|
silentCheckSsoRedirectUri: undefined, // Disable silent SSO check
|
|
|
|
|
enableLogging: false, // Reduce console noise
|
|
|
|
|
// Use standard flow compatible with proxy setups
|
|
|
|
|
flow: 'standard',
|
|
|
|
|
responseMode: 'query', // Use query params instead of fragments
|
|
|
|
|
// Disable third-party cookie checks
|
|
|
|
|
checkLoginIframeInterval: 0,
|
|
|
|
|
// PKCE for security
|
|
|
|
|
pkceMethod: 'S256',
|
|
|
|
|
// Timeout settings
|
|
|
|
|
messageReceiveTimeout: 10000,
|
|
|
|
|
// Disable adapter features that can cause issues in proxied environments
|
|
|
|
|
adapter: 'default'
|
2025-06-14 15:26:26 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
logDebug('Keycloak initialization result', {
|
|
|
|
|
authenticated,
|
2025-06-14 15:38:40 +02:00
|
|
|
redirectUri: `${baseUrl}/auth/callback`,
|
|
|
|
|
checkLoginIframe: false,
|
|
|
|
|
silentSso: 'disabled'
|
2025-06-14 14:50:29 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
isAuthenticated.value = authenticated
|
|
|
|
|
isInitialized.value = true
|
|
|
|
|
|
|
|
|
|
if (authenticated && keycloak.value.token) {
|
|
|
|
|
token.value = keycloak.value.token || null
|
|
|
|
|
user.value = {
|
|
|
|
|
id: keycloak.value.subject,
|
|
|
|
|
username: keycloak.value.tokenParsed?.preferred_username,
|
|
|
|
|
email: keycloak.value.tokenParsed?.email,
|
|
|
|
|
firstName: keycloak.value.tokenParsed?.given_name,
|
|
|
|
|
lastName: keycloak.value.tokenParsed?.family_name,
|
|
|
|
|
fullName: keycloak.value.tokenParsed?.name,
|
|
|
|
|
roles: keycloak.value.tokenParsed?.realm_access?.roles || [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set up token refresh
|
|
|
|
|
keycloak.value.onTokenExpired = () => {
|
|
|
|
|
keycloak.value?.updateToken(30).then((refreshed) => {
|
|
|
|
|
if (refreshed) {
|
|
|
|
|
token.value = keycloak.value?.token || null
|
|
|
|
|
console.log('Token refreshed')
|
|
|
|
|
} else {
|
|
|
|
|
console.log('Token still valid')
|
|
|
|
|
}
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
console.log('Failed to refresh token')
|
|
|
|
|
logout()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return authenticated
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to initialize Keycloak:', error)
|
|
|
|
|
isInitialized.value = true
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const login = async () => {
|
2025-06-14 15:44:28 +02:00
|
|
|
console.log('[KEYCLOAK] Login function called')
|
|
|
|
|
|
2025-06-14 14:50:29 +02:00
|
|
|
if (!keycloak.value) {
|
2025-06-14 15:44:28 +02:00
|
|
|
console.log('[KEYCLOAK] Keycloak not initialized, initializing now...')
|
2025-06-14 14:50:29 +02:00
|
|
|
await initKeycloak()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (keycloak.value) {
|
|
|
|
|
try {
|
2025-06-14 15:38:40 +02:00
|
|
|
const baseUrl = getBaseUrl()
|
2025-06-14 15:44:28 +02:00
|
|
|
console.log('[KEYCLOAK] Starting login', {
|
|
|
|
|
redirectUri: `${baseUrl}/dashboard`,
|
|
|
|
|
keycloakInitialized: !!keycloak.value,
|
|
|
|
|
keycloakAuthenticated: keycloak.value.authenticated
|
|
|
|
|
})
|
2025-06-14 15:38:40 +02:00
|
|
|
|
2025-06-14 14:50:29 +02:00
|
|
|
await keycloak.value.login({
|
2025-06-14 15:38:40 +02:00
|
|
|
redirectUri: `${baseUrl}/dashboard`
|
2025-06-14 14:50:29 +02:00
|
|
|
})
|
2025-06-14 15:44:28 +02:00
|
|
|
|
|
|
|
|
console.log('[KEYCLOAK] Login method completed successfully')
|
2025-06-14 14:50:29 +02:00
|
|
|
} catch (error) {
|
2025-06-14 15:44:28 +02:00
|
|
|
console.error('[KEYCLOAK] Login failed:', error)
|
|
|
|
|
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
console.error('[KEYCLOAK] Error details:', {
|
|
|
|
|
message: error.message,
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
name: error.name
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
console.error('[KEYCLOAK] Unknown error type:', error)
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 14:50:29 +02:00
|
|
|
throw error
|
|
|
|
|
}
|
2025-06-14 15:44:28 +02:00
|
|
|
} else {
|
|
|
|
|
console.error('[KEYCLOAK] No keycloak instance available for login')
|
|
|
|
|
throw new Error('Keycloak not available')
|
2025-06-14 14:50:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const logout = async () => {
|
|
|
|
|
if (keycloak.value) {
|
|
|
|
|
try {
|
|
|
|
|
await keycloak.value.logout({
|
|
|
|
|
redirectUri: window.location.origin
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Logout failed:', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear local state
|
|
|
|
|
isAuthenticated.value = false
|
|
|
|
|
user.value = null
|
|
|
|
|
token.value = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getToken = () => {
|
|
|
|
|
return token.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasRole = (role: string) => {
|
|
|
|
|
return user.value?.roles?.includes(role) || false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasAnyRole = (roles: string[]) => {
|
|
|
|
|
return roles.some(role => hasRole(role))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
keycloak: readonly(keycloak),
|
|
|
|
|
isAuthenticated: readonly(isAuthenticated),
|
|
|
|
|
user: readonly(user),
|
|
|
|
|
token: readonly(token),
|
|
|
|
|
isInitialized: readonly(isInitialized),
|
|
|
|
|
initKeycloak,
|
|
|
|
|
login,
|
|
|
|
|
logout,
|
|
|
|
|
getToken,
|
|
|
|
|
hasRole,
|
|
|
|
|
hasAnyRole,
|
|
|
|
|
}
|
|
|
|
|
}
|