diff --git a/app/composables/useAuth.js b/app/composables/useAuth.js
new file mode 100644
index 0000000..4852bc1
--- /dev/null
+++ b/app/composables/useAuth.js
@@ -0,0 +1,160 @@
+import { ref } from 'vue'
+
+const userToken = ref(null)
+const refreshToken = ref(null)
+const userProfile = ref(null)
+const isAuthenticated = ref(false)
+const isInitialized = ref(false)
+
+let sessionPollTimer = null
+
+export const useAuth = () => {
+ const setSession = (data) => {
+ userToken.value = data.access_token
+ if (data.refresh_token) {
+ refreshToken.value = data.refresh_token
+ if (import.meta.client) {
+ localStorage.setItem('keycloak_refresh_token', data.refresh_token)
+ }
+ }
+ isAuthenticated.value = true
+ }
+
+ const clearSession = () => {
+ stopLiveSessionPolling()
+ userToken.value = null
+ refreshToken.value = null
+ userProfile.value = null
+ isAuthenticated.value = false
+ if (import.meta.client) {
+ localStorage.removeItem('keycloak_refresh_token')
+ }
+ }
+
+ const exchangeCodeForToken = async (code) => {
+ try {
+ const data = await $fetch('/api/auth/token', {
+ method: 'POST',
+ body: {
+ code,
+ redirectUri: `${window.location.origin}/`
+ }
+ })
+
+ setSession(data)
+ window.history.replaceState({}, document.title, window.location.pathname)
+ await verifySession()
+ startLiveSessionPolling()
+ return true
+ } catch (error) {
+ console.error('Code exchange failed:', error)
+ clearSession()
+ return false
+ } finally {
+ isInitialized.value = true
+ }
+ }
+
+ const silentRefresh = async () => {
+ const storedRefreshToken = refreshToken.value || (import.meta.client && localStorage.getItem('keycloak_refresh_token'))
+
+ if (!storedRefreshToken) {
+ clearSession()
+ isInitialized.value = true
+ return false
+ }
+
+ try {
+ const data = await $fetch('/api/auth/refresh', {
+ method: 'POST',
+ body: { refreshToken: storedRefreshToken }
+ })
+
+ setSession(data)
+ const isValid = await verifySession()
+ if (isValid) {
+ startLiveSessionPolling()
+ return true
+ }
+ return false
+ } catch (error) {
+ console.warn('Silent refresh failed:', error)
+ clearSession()
+ return false
+ } finally {
+ isInitialized.value = true
+ }
+ }
+
+ const verifySession = async () => {
+ if (!userToken.value) return false
+
+ try {
+ const res = await $fetch('/api/auth/check-session', {
+ method: 'POST',
+ body: { token: userToken.value }
+ })
+
+ if (res.active) {
+ userProfile.value = res.user
+ isAuthenticated.value = true
+ return true
+ } else {
+ return await silentRefresh()
+ }
+ } catch (e) {
+ return await silentRefresh()
+ }
+ }
+
+ const startLiveSessionPolling = () => {
+ if (import.meta.server) return
+ stopLiveSessionPolling()
+
+ sessionPollTimer = setInterval(async () => {
+ if (isAuthenticated.value) {
+ const valid = await verifySession()
+ if (!valid) {
+ handleLocalLogout()
+ }
+ }
+ }, 5000)
+ }
+
+ const stopLiveSessionPolling = () => {
+ if (sessionPollTimer) {
+ clearInterval(sessionPollTimer)
+ sessionPollTimer = null
+ }
+ }
+
+ const handleLocalLogout = () => {
+ clearSession()
+ isInitialized.value = true
+ navigateTo('/oauth')
+ }
+
+ const logout = async () => {
+ try {
+ await $fetch('/api/auth/logout', {
+ method: 'POST',
+ body: { refreshToken: refreshToken.value }
+ })
+ } catch (e) {
+ console.warn('Server logout failed:', e)
+ } finally {
+ handleLocalLogout()
+ }
+ }
+
+ return {
+ userToken,
+ userProfile,
+ isAuthenticated,
+ isInitialized,
+ exchangeCodeForToken,
+ silentRefresh,
+ verifySession,
+ logout
+ }
+}
\ No newline at end of file
diff --git a/app/composables/useKeycloak.js b/app/composables/useKeycloak.js
deleted file mode 100644
index 11a8968..0000000
--- a/app/composables/useKeycloak.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { ref } from 'vue'
-
-const keycloakInstance = ref(null)
-const isAuthenticated = ref(false)
-const userProfile = ref(null)
-const isInitialized = ref(false)
-
-export const useKeycloak = () => {
- const config = useRuntimeConfig()
-
- const initKeycloak = async () => {
- if (isInitialized.value && keycloakInstance.value?.authenticated) {
- return true
- }
-
- if (!import.meta.client) return false
-
- try {
- const Keycloak = (await import('keycloak-js')).default
- const keycloak = new Keycloak({
- url: config.public.keycloakUrl,
- realm: config.public.keycloakRealm,
- clientId: config.public.keycloakClientId
- })
-
- keycloakInstance.value = keycloak
-
- const auth = await keycloak.init({
- onLoad: 'check-sso',
- pkceMethod: 'S256',
- responseMode: 'query',
- checkLoginIframe: false
- })
-
- const loggedIn = Boolean(auth || keycloak.authenticated)
- isAuthenticated.value = loggedIn
- isInitialized.value = true
-
- if (loggedIn) {
- try {
- userProfile.value = await keycloak.loadUserProfile()
- } catch (e) {
- console.warn('Could not load user profile:', e)
- }
-
- if (window.location.search.includes('code=')) {
- window.history.replaceState({}, document.title, window.location.pathname)
- }
- }
-
- return loggedIn
- } catch (error) {
- console.error('Keycloak init failed:', error)
- isInitialized.value = true
- isAuthenticated.value = false
- return false
- }
- }
-
- const login = () => {
- if (keycloakInstance.value) {
- keycloakInstance.value.login({
- redirectUri: `${window.location.origin}/`
- })
- }
- }
-
- const logout = () => {
- if (keycloakInstance.value) {
- keycloakInstance.value.logout({
- redirectUri: `${window.location.origin}/oauth`
- })
- }
- }
-
- return {
- keycloakInstance,
- isAuthenticated,
- userProfile,
- isInitialized,
- initKeycloak,
- login,
- logout
- }
-}
\ No newline at end of file
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 3108717..2711c83 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,20 +1,23 @@
diff --git a/app/pages/oauth/index.vue b/app/pages/oauth/index.vue
index 8f19614..6eb6a76 100644
--- a/app/pages/oauth/index.vue
+++ b/app/pages/oauth/index.vue
@@ -1,19 +1,31 @@
@@ -27,12 +39,7 @@ const handleLogin = () => {
Hai there :3
-