Files
paskia/frontend/auth/App.vue
T
LeoVasanko 42240dd2c7 Unify host profile view as framed dialog, add profile iframe mode
HostProfileView now renders the same centered frame card as the login
flows, whether shown full-page at /auth/ (host mode) or inside the new
#mode=profile restricted iframe. The component self-fetches its data
when the parent does not provide it, and emits back/logout so each
context reacts appropriately: the full page reloads, the iframe posts
auth-back / auth-logout to the host.
2026-09-17 19:17:00 +00:00

125 lines
4.2 KiB
Vue

<template>
<div class="app-shell">
<StatusMessage />
<main class="app-main">
<HostProfileView
v-if="viewState === 'profile' && isHostMode"
:ctx="store.ctx"
:user-info="store.userInfo"
:settings="store.settings"
@back="goBack"
@logout="onHostLogout"
/>
<ProfileView v-else-if="viewState === 'profile'" />
<LoadingView v-else-if="viewState === 'loading'" :message="loadingMessage" />
<AccessDenied v-else-if="viewState === 'terminal'" />
</main>
</div>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { apiJson, SessionValidator, settings as paskiaSettings } from 'paskia'
import { updateThemeFromSession } from '@/utils/theme'
import { goBack } from '@/utils/helpers'
import StatusMessage from '@/components/StatusMessage.vue'
import ProfileView from '@/components/ProfileView.vue'
import HostProfileView from '@/components/HostProfileView.vue'
import LoadingView from '@/components/LoadingView.vue'
import AccessDenied from '@/components/AccessDenied.vue'
const store = useAuthStore()
const viewState = ref('loading') // 'loading' | 'profile' | 'terminal'
const loadingMessage = ref('Loading...')
/**
* Normalize a host string for comparison (lowercase, strip default ports).
*/
function normalizeHost(raw) {
if (!raw) return null
const trimmed = raw.trim().toLowerCase()
if (!trimmed) return null
// Remove default ports
return trimmed.replace(/:80$/, '').replace(/:443$/, '')
}
/**
* Host mode is active when an own_auth_host is configured AND the current host differs from it.
* In host mode, we show a limited profile view with logout and link to full profile.
*/
const isHostMode = computed(() => {
const authHost = store.settings?.own_auth_host
if (!authHost) return false
const currentHost = normalizeHost(window.location.host)
const configuredHost = normalizeHost(authHost)
return currentHost !== configuredHost
})
// HostProfileView already posted /auth/api/logout; clear local state and reload.
function onHostLogout() {
sessionStorage.clear()
window.location.reload()
}
function onSessionLost(e) {
store.userInfo = null
store.ctx = null
if (e?.name === 'AuthCancelledError') {
viewState.value = 'terminal'
} else {
store.showMessage(e?.message || 'Session lost', 'error', 5000)
viewState.value = 'terminal'
}
}
const userUuidGetter = () => store.ctx?.user.uuid
const sessionValidator = new SessionValidator(userUuidGetter, onSessionLost)
onMounted(() => sessionValidator.start())
onUnmounted(() => sessionValidator.stop())
async function loadUserInfo() {
viewState.value = 'loading'
loadingMessage.value = 'Loading...'
try {
// apiJson handles 401/403 with auth.iframe automatically:
// shows overlay iframe, waits for auth, retries the request.
const [validateData, userInfoData] = await Promise.all([
apiJson('/auth/api/validate', { method: 'POST', timeout: paskiaSettings.auth_ms }),
apiJson('/auth/api/user-info', { method: 'GET', timeout: paskiaSettings.auth_ms })
])
store.userInfo = userInfoData
store.ctx = validateData.ctx
updateThemeFromSession(store.userInfo)
// Verify that the user UUIDs match between user-info and validate responses
if (store.userInfo.user.uuid !== store.ctx.user.uuid) {
console.error('User UUID mismatch between user-info and validate responses')
window.location.reload()
return
}
viewState.value = 'profile'
} catch (e) {
onSessionLost(e)
}
}
onMounted(async () => {
// Load settings
await store.loadSettings()
// Set appropriate page title based on mode
const rpName = store.settings?.rp_name
if (rpName) {
// In host mode, show "account summary" style title
// Settings are loaded but isHostMode depends on them, so check here
const authHost = store.settings?.own_auth_host
const inHostMode = authHost && normalizeHost(window.location.host) !== normalizeHost(authHost)
document.title = inHostMode ? `${rpName} · Account summary` : rpName
}
// Load user info (apiJson handles auth iframe if needed)
await loadUserInfo()
})
</script>