Allows Paskia to authenticate the user to a client site. - User friendly client registration flow on the admin app - Redirect-based authentication flow (per spec) - Backchannel logout both ways to keep sessions synchronized - Groups integrated with Paskia's permission system - Adds email, preferred username and telephone fields on user profile - All new user basic info layout to show the new information, better looks - API and DB structures redesigned - Various unrelated fixes to theming and layout
130 lines
3.9 KiB
Vue
130 lines
3.9 KiB
Vue
<template>
|
|
<section class="view-root view-root--wide host-view" data-view="host-profile">
|
|
<header class="view-header">
|
|
<h1>{{ headingTitle }}</h1>
|
|
<p class="view-lede">{{ subheading }}</p>
|
|
</header>
|
|
|
|
<section class="section-block" ref="userInfoSection">
|
|
<div class="section-body">
|
|
<UserBasicInfo
|
|
v-if="ctx"
|
|
:name="ctx.user.display_name"
|
|
:visits="authStore.userInfo?.visits || 0"
|
|
:created-at="authStore.userInfo?.created_at"
|
|
:last-seen="authStore.userInfo?.last_seen"
|
|
:email="ctx.user.email"
|
|
:telephone="ctx.user.telephone"
|
|
:org-display-name="orgDisplayName"
|
|
:role-name="roleDisplayName"
|
|
:can-edit="false"
|
|
/>
|
|
<p v-else class="empty-state">
|
|
{{ initializing ? 'Loading your account…' : 'No active session found.' }}
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section-block">
|
|
<div class="section-body host-actions">
|
|
<div class="button-row" ref="buttonRow" @keydown="handleButtonRowKeydown">
|
|
<button
|
|
type="button"
|
|
class="btn-secondary"
|
|
@click="goBack"
|
|
>
|
|
Back
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger"
|
|
:disabled="authStore.isLoading"
|
|
@click="logout"
|
|
>
|
|
{{ authStore.isLoading ? 'Signing out…' : 'Logout' }}
|
|
</button>
|
|
<button
|
|
v-if="authSiteUrl"
|
|
type="button"
|
|
class="btn-primary"
|
|
:disabled="authStore.isLoading"
|
|
@click="goToAuthSite"
|
|
>
|
|
Full Profile
|
|
</button>
|
|
</div>
|
|
<p class="note"><strong>Logout</strong> from {{ currentHost }}, or access your <strong>Full Profile</strong> at {{ authSiteHost }} (you may need to sign in again).</p>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref } from 'vue'
|
|
import UserBasicInfo from '@/components/UserBasicInfo.vue'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { goBack } from '@/utils/helpers'
|
|
import { getDirection, navigateButtonRow } from '@/utils/keynav'
|
|
|
|
defineProps({
|
|
initializing: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const authStore = useAuthStore()
|
|
const currentHost = window.location.host
|
|
|
|
// Template refs for navigation
|
|
const userInfoSection = ref(null)
|
|
const buttonRow = ref(null)
|
|
|
|
const ctx = computed(() => authStore.userInfo || null)
|
|
const orgDisplayName = computed(() => ctx.value?.org?.display_name ?? '')
|
|
const roleDisplayName = computed(() => ctx.value?.role?.display_name ?? '')
|
|
|
|
const headingTitle = computed(() => {
|
|
const service = authStore.settings?.rp_name
|
|
return service ? `${service} account` : 'Account overview'
|
|
})
|
|
|
|
const subheading = computed(() => {
|
|
return `You're signed in to ${currentHost}.`
|
|
})
|
|
|
|
const authSiteHost = computed(() => authStore.settings?.auth_host || '')
|
|
const authSiteUrl = computed(() => {
|
|
const host = authSiteHost.value
|
|
if (!host) return ''
|
|
let path = authStore.settings?.ui_base_path ?? '/auth/'
|
|
if (!path.startsWith('/')) path = `/${path}`
|
|
if (!path.endsWith('/')) path = `${path}/`
|
|
const protocol = window.location.protocol || 'https:'
|
|
return `${protocol}//${host}${path}`
|
|
})
|
|
|
|
const goToAuthSite = () => {
|
|
if (!authSiteUrl.value) return
|
|
window.location.href = authSiteUrl.value
|
|
}
|
|
|
|
const logout = async () => {
|
|
await authStore.logout()
|
|
}
|
|
|
|
// Keyboard navigation for button row
|
|
const handleButtonRowKeydown = (event) => {
|
|
const direction = getDirection(event)
|
|
if (!direction) return
|
|
|
|
event.preventDefault()
|
|
|
|
if (direction === 'left' || direction === 'right') {
|
|
navigateButtonRow(buttonRow.value, event.target, direction, { itemSelector: 'button' })
|
|
}
|
|
// Up does nothing (no elements above to navigate to)
|
|
// Down does nothing (no elements below to navigate to)
|
|
}
|
|
</script>
|