FEAT: Migrate authentication system from Directus to Keycloak, implementing token refresh and enhancing session management
This commit is contained in:
@@ -3,6 +3,7 @@ interface User {
|
||||
email: string
|
||||
username: string
|
||||
name: string
|
||||
authMethod: string
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
@@ -14,6 +15,7 @@ export const useCustomAuth = () => {
|
||||
const user = ref<User | null>(null)
|
||||
const authenticated = ref(false)
|
||||
const loading = ref(true)
|
||||
const refreshing = ref(false)
|
||||
|
||||
// Check authentication status
|
||||
const checkAuth = async () => {
|
||||
@@ -22,8 +24,13 @@ export const useCustomAuth = () => {
|
||||
const data = await $fetch<AuthState>('/api/auth/session')
|
||||
user.value = data.user
|
||||
authenticated.value = data.authenticated
|
||||
|
||||
console.log('[CUSTOM_AUTH] Session check result:', {
|
||||
authenticated: data.authenticated,
|
||||
userId: data.user?.id
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[AUTH] Session check failed:', error)
|
||||
console.error('[CUSTOM_AUTH] Session check failed:', error)
|
||||
user.value = null
|
||||
authenticated.value = false
|
||||
} finally {
|
||||
@@ -31,6 +38,36 @@ export const useCustomAuth = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh token
|
||||
const refreshToken = async () => {
|
||||
if (refreshing.value) return false
|
||||
|
||||
try {
|
||||
refreshing.value = true
|
||||
console.log('[CUSTOM_AUTH] Attempting token refresh...')
|
||||
|
||||
const response = await $fetch<{ success: boolean }>('/api/auth/refresh', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
console.log('[CUSTOM_AUTH] Token refresh successful')
|
||||
await checkAuth() // Re-check auth state after refresh
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('[CUSTOM_AUTH] Token refresh failed:', error)
|
||||
// Clear auth state on refresh failure
|
||||
user.value = null
|
||||
authenticated.value = false
|
||||
return false
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Login with Keycloak
|
||||
const login = () => {
|
||||
const authUrl = 'https://auth.portnimara.dev/realms/client-portal/protocol/openid-connect/auth?' +
|
||||
@@ -42,16 +79,24 @@ export const useCustomAuth = () => {
|
||||
state: Math.random().toString(36).substring(2)
|
||||
}).toString()
|
||||
|
||||
console.log('[AUTH] Redirecting to Keycloak login')
|
||||
console.log('[CUSTOM_AUTH] Redirecting to Keycloak login:', authUrl)
|
||||
navigateTo(authUrl, { external: true })
|
||||
}
|
||||
|
||||
// Logout
|
||||
const logout = async () => {
|
||||
try {
|
||||
console.log('[CUSTOM_AUTH] Initiating logout...')
|
||||
// Clear local state immediately
|
||||
user.value = null
|
||||
authenticated.value = false
|
||||
|
||||
// Redirect to logout endpoint
|
||||
await navigateTo('/api/auth/logout', { external: true })
|
||||
} catch (error) {
|
||||
console.error('[AUTH] Logout failed:', error)
|
||||
console.error('[CUSTOM_AUTH] Logout failed:', error)
|
||||
// Fallback: redirect to login
|
||||
await navigateTo('/login')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +109,10 @@ export const useCustomAuth = () => {
|
||||
user: readonly(user),
|
||||
authenticated: readonly(authenticated),
|
||||
loading: readonly(loading),
|
||||
refreshing: readonly(refreshing),
|
||||
login,
|
||||
logout,
|
||||
checkAuth
|
||||
checkAuth,
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,75 +2,53 @@ export interface UnifiedUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
username: string;
|
||||
tier?: string;
|
||||
authSource: 'keycloak' | 'directus';
|
||||
authSource: 'keycloak';
|
||||
raw: any;
|
||||
}
|
||||
|
||||
export const useUnifiedAuth = () => {
|
||||
// Get both auth systems
|
||||
const directusAuth = useDirectusAuth();
|
||||
const directusUser = useDirectusUser();
|
||||
// Get Keycloak auth
|
||||
const customAuth = useCustomAuth();
|
||||
|
||||
// Create unified user object
|
||||
// Create unified user object from Keycloak only
|
||||
const user = computed<UnifiedUser | null>(() => {
|
||||
// Check custom Keycloak auth first
|
||||
if (customAuth.authenticated?.value && customAuth.user?.value) {
|
||||
const keycloakUser = customAuth.user.value as any; // Type cast for flexibility
|
||||
|
||||
// Construct name from available fields
|
||||
let name = keycloakUser.name || keycloakUser.username || keycloakUser.email || 'User';
|
||||
const keycloakUser = customAuth.user.value as any;
|
||||
|
||||
return {
|
||||
id: keycloakUser.id || 'unknown',
|
||||
id: keycloakUser.id,
|
||||
email: keycloakUser.email || '',
|
||||
name: name,
|
||||
tier: 'basic', // Could be enhanced with Keycloak attributes
|
||||
username: keycloakUser.username || keycloakUser.email || '',
|
||||
name: keycloakUser.name || keycloakUser.username || keycloakUser.email || 'User',
|
||||
tier: 'basic', // Could be enhanced with Keycloak attributes/roles
|
||||
authSource: 'keycloak',
|
||||
raw: keycloakUser
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to Directus user
|
||||
if (directusUser.value && directusUser.value.email) {
|
||||
return {
|
||||
id: directusUser.value.id,
|
||||
email: directusUser.value.email,
|
||||
name: `${directusUser.value.first_name || ''} ${directusUser.value.last_name || ''}`.trim() || directusUser.value.email,
|
||||
tier: directusUser.value.tier || 'basic',
|
||||
authSource: 'directus',
|
||||
raw: directusUser.value
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// Unified logout function
|
||||
// Unified logout function (Keycloak only)
|
||||
const logout = async () => {
|
||||
if (user.value?.authSource === 'keycloak') {
|
||||
// Custom Keycloak logout
|
||||
await customAuth.logout();
|
||||
} else if (user.value?.authSource === 'directus') {
|
||||
// Directus logout
|
||||
await directusAuth.logout();
|
||||
await navigateTo('/login');
|
||||
}
|
||||
console.log('[UNIFIED_AUTH] Logging out user');
|
||||
await customAuth.logout();
|
||||
};
|
||||
|
||||
// Check if user is authenticated
|
||||
const isAuthenticated = computed(() => !!user.value);
|
||||
|
||||
// Get auth source
|
||||
const authSource = computed(() => user.value?.authSource);
|
||||
// Get auth source (always Keycloak now)
|
||||
const authSource = computed(() => user.value?.authSource || 'keycloak');
|
||||
|
||||
// Check if user has specific tier
|
||||
const hasTier = (tier: string) => {
|
||||
return user.value?.tier === tier;
|
||||
};
|
||||
|
||||
// Check if user is admin
|
||||
// Check if user is admin (could be enhanced with Keycloak roles)
|
||||
const isAdmin = computed(() => hasTier('admin'));
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user