major: Migration from better-auth to keycloak

This commit is contained in:
2025-10-28 10:40:38 +01:00
parent e5e063bbde
commit 36364a7977
77 changed files with 1444 additions and 2930 deletions

View File

@@ -1,75 +1,14 @@
// Copied from https://github.com/atinux/nuxthub-better-auth
import { defu } from 'defu'
import type { RouteLocationNormalized } from '#vue-router'
type MiddlewareOptions =
| false
| {
/**
* Only apply auth middleware to guest or user
*/
only?: 'guest' | 'user'
/**
* Redirect authenticated user to this route
*/
redirectUserTo?: string
/**
* Redirect guest to this route
*/
redirectGuestTo?: string
}
declare module '#app' {
interface PageMeta {
auth?: MiddlewareOptions
}
}
declare module 'vue-router' {
interface RouteMeta {
auth?: MiddlewareOptions
}
}
export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) => {
// 1. If auth is disabled, skip middleware
if (to.meta?.auth === false) {
console.log('[1] Auth middleware disabled for this route:', to.path)
// https://github.com/WaldemarEnns/nuxtui-github-auth/blob/7e3110f933d5d0445d3ac89d6c84c48052b49041/middleware/auth.global.ts
const { loggedIn } = useUserSession()
if (to.meta.auth === false) {
return
}
const { loggedIn, options, fetchSession, isPublicPath } = useAuth()
const { only, redirectUserTo, redirectGuestTo } = defu(to.meta?.auth, options)
// 2. If guest mode, redirect if authenticated
if (only === 'guest' && loggedIn.value) {
console.log('[2] Guest mode: user is authenticated, redirecting to', redirectUserTo)
if (to.path === redirectUserTo) {
console.log('[2.1] Already at redirectUserTo:', redirectUserTo)
return
}
return navigateTo(redirectUserTo)
}
// 3. If client-side, fetch session between each navigation
if (import.meta.client) {
console.log('[3] Client-side navigation, fetching session')
try {
await fetchSession(to.path)
} catch (e) {
console.error(e)
}
}
// 4. If not authenticated, redirect to home or guest route
if (!loggedIn.value) {
if (isPublicPath(to.path)) {
console.log('[4] Not authenticated, but route is public:', to.path)
// Continue navigating to the public route
return
}
// No public route, redirect to guest route
console.log('[4.1] Not authenticated, redirecting to guest route:', redirectGuestTo)
return navigateTo(redirectGuestTo)
return navigateTo('/login')
}
})

View File

@@ -0,0 +1,83 @@
// Copied from https://github.com/atinux/nuxt-auth-utils/issues/91#issuecomment-2476019136
import { appendResponseHeader } from 'h3'
import { parse, parseSetCookie, serialize } from 'cookie-es'
import { jwtDecode, type JwtPayload } from 'jwt-decode'
export default defineNuxtRouteMiddleware(async (to, from) => {
const nuxtApp = useNuxtApp()
// Don't run on client hydration when server rendered
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
console.log('🔍 Middleware: refreshToken.global.ts')
console.log(` from: ${from.fullPath} to: ${to.fullPath}`)
const { session, clear: clearSession, fetch: fetchSession } = useUserSession()
// Ignore if no tokens
if (!session.value?.jwt) return
const serverEvent = useRequestEvent()
const runtimeConfig = useRuntimeConfig()
const { accessToken, refreshToken } = session.value.jwt
const accessPayload = jwtDecode(accessToken)
const refreshPayload = jwtDecode(refreshToken)
// Both tokens expired, clearing session
if (isExpired(accessPayload) && isExpired(refreshPayload)) {
console.info('both tokens expired, clearing session')
await clearSession()
return navigateTo('/login')
} else if (isExpired(accessPayload)) {
console.info('access token expired, refreshing')
await useRequestFetch()('/api/jwt/refresh', {
method: 'POST',
onResponse({ response: { headers } }) {
// Forward the Set-Cookie header to the main server event
if (import.meta.server && serverEvent) {
for (const setCookie of headers.getSetCookie()) {
appendResponseHeader(serverEvent, 'Set-Cookie', setCookie)
// Update session cookie for next fetch requests
const { name, value } = parseSetCookie(setCookie)
if (name === runtimeConfig.session.name) {
console.log('updating headers.cookie to', value)
const cookies = parse(serverEvent.headers.get('cookie') || '')
// set or overwrite existing cookie
cookies[name] = value
// update cookie event header for future requests
serverEvent.headers.set(
'cookie',
Object.entries(cookies)
.map(([name, value]) => serialize(name, value))
.join('; ')
)
// Also apply to serverEvent.node.req.headers
if (serverEvent.node?.req?.headers) {
serverEvent.node.req.headers['cookie'] = serverEvent.headers.get('cookie') || ''
}
}
}
}
},
onError() {
console.error('🔍 Middleware: Token refresh failed')
const { loggedIn } = useUserSession()
if (!loggedIn.value) {
console.log('🔍 Middleware: User not logged in, redirecting to /login')
return navigateTo('/login')
}
}
})
// Refresh the session
await fetchSession()
}
})
function isExpired(payload: JwtPayload) {
return payload?.exp && payload.exp < Date.now() / 1000
}