Profile iframe handles no-session internally with in-place login
The restricted entry validates the session before rendering anything (no load-time flash): 401/403 switches to the existing login component in place of the profile, success renders the panel fully populated via props, other failures show a minimal card with Back only. The dialog drops the standalone page's heading and help text.
This commit is contained in:
@@ -1,9 +1,34 @@
|
||||
<template>
|
||||
<HostProfileView
|
||||
v-if="authMode === 'profile'"
|
||||
@back="handleBack"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
<template v-if="authMode === 'profile'">
|
||||
<!--
|
||||
Profile mode: render nothing until the session check completes (avoids
|
||||
a load-time flash of the wrong view). Without a session, the login flow
|
||||
runs in place of the profile; on success auth-success is posted and the
|
||||
host resolves profile() with 'login'.
|
||||
-->
|
||||
<RestrictedAuth
|
||||
v-if="profileState === 'login'"
|
||||
mode="login"
|
||||
@authenticated="handleAuthenticated"
|
||||
@back="handleBack"
|
||||
/>
|
||||
<HostProfileView
|
||||
v-else-if="profileState === 'ready'"
|
||||
:ctx="profileCtx"
|
||||
:user-info="profileInfo"
|
||||
:settings="profileSettings"
|
||||
@back="handleBack"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
<div v-else class="view-root profile-pending">
|
||||
<div class="surface surface--tight">
|
||||
<p class="view-lede">{{ profileState === 'error' ? 'Could not load your account.' : 'Loading your account…' }}</p>
|
||||
<div class="button-row">
|
||||
<button type="button" class="btn-secondary" @click="handleBack">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<RestrictedAuth
|
||||
v-else
|
||||
:mode="authMode"
|
||||
@@ -18,6 +43,9 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import RestrictedAuth from '@/components/RestrictedAuth.vue'
|
||||
import HostProfileView from '@/components/HostProfileView.vue'
|
||||
import { fetchJson, settings as paskiaSettings } from 'paskia'
|
||||
import { getSettings } from '@/utils/settings'
|
||||
import { updateThemeFromSession } from '@/utils/theme'
|
||||
|
||||
// Check if this is a remote auth URL: /auth/{token}
|
||||
// The token is a 5-word passphrase like "word1.word2.word3.word4.word5"
|
||||
@@ -56,6 +84,30 @@ if (window.location.pathname === '/auth/restricted/oidc') {
|
||||
authMode = ['reauth', 'forbidden', 'profile'].includes(hashParams.get('mode')) ? hashParams.get('mode') : 'login'
|
||||
}
|
||||
|
||||
// Profile mode state: 'loading' | 'login' | 'ready' | 'error'
|
||||
const profileState = ref('loading')
|
||||
const profileCtx = ref(null)
|
||||
const profileInfo = ref(null)
|
||||
const profileSettings = ref(null)
|
||||
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const [validateData, infoData, settingsData] = await Promise.all([
|
||||
fetchJson('/auth/api/validate', { method: 'POST', timeout: paskiaSettings.auth_ms }),
|
||||
fetchJson('/auth/api/user-info', { method: 'GET', timeout: paskiaSettings.auth_ms }),
|
||||
getSettings()
|
||||
])
|
||||
profileCtx.value = validateData.ctx
|
||||
profileInfo.value = infoData
|
||||
profileSettings.value = settingsData
|
||||
updateThemeFromSession(validateData.ctx)
|
||||
profileState.value = 'ready'
|
||||
} catch (error) {
|
||||
// No/expired session: run the login flow in place of the profile
|
||||
profileState.value = error.status === 401 || error.status === 403 ? 'login' : 'error'
|
||||
}
|
||||
}
|
||||
|
||||
function postToParent(message) {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage(message, '*')
|
||||
@@ -91,6 +143,8 @@ onMounted(() => {
|
||||
// Check for remote auth token in URL
|
||||
remoteAuthToken.value = extractRemoteToken()
|
||||
|
||||
if (authMode === 'profile') loadProfile()
|
||||
|
||||
postToParent({
|
||||
type: 'auth-ready'
|
||||
})
|
||||
@@ -102,3 +156,15 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-root.profile-pending { min-height: 100vh; align-items: center; justify-content: center; padding: 2rem 1rem; }
|
||||
.profile-pending .surface {
|
||||
max-width: 520px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="view-root host-profile" data-view="host-profile">
|
||||
<div class="surface surface--tight">
|
||||
<header class="view-header center">
|
||||
<!-- Heading/lede belong to the standalone page; in the dialog the host
|
||||
page already provides the surrounding context. -->
|
||||
<header v-if="!inIframe" class="view-header center">
|
||||
<h1>{{ headingTitle }}</h1>
|
||||
<p class="view-lede">{{ subheading }}</p>
|
||||
</header>
|
||||
@@ -55,8 +57,7 @@
|
||||
Full Profile
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="isRemoteAuthSite" class="note"><strong>Logout</strong> from {{ currentHost }}, or access your <strong>Full Profile</strong> at {{ authSiteHost }} (you may need to sign in again).</p>
|
||||
<p v-else class="note"><strong>Logout</strong> from {{ currentHost }}, or open your <strong>Full Profile</strong>.</p>
|
||||
<p v-if="!inIframe" class="note"><strong>Logout</strong> from {{ currentHost }}, or view your <strong>Full Profile</strong> at {{ authSiteHost }} (you may need to sign in again).</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -118,11 +119,6 @@ const subheading = computed(() => {
|
||||
})
|
||||
|
||||
const authSiteHost = computed(() => settingsData.value?.auth_host || '')
|
||||
// Normalize for comparison (lowercase, strip default ports), matching App.vue
|
||||
const normalizeHost = (raw) => (raw || '').trim().toLowerCase().replace(/:80$/, '').replace(/:443$/, '')
|
||||
const isRemoteAuthSite = computed(() => {
|
||||
return !!authSiteHost.value && normalizeHost(authSiteHost.value) !== normalizeHost(currentHost)
|
||||
})
|
||||
const authSiteUrl = computed(() => {
|
||||
// Fall back to the current host when no separate auth host is configured;
|
||||
// the full profile is at ui_base_path either way.
|
||||
|
||||
Reference in New Issue
Block a user