|
|
@@ -107,8 +107,11 @@
|
|
|
|
<button @click="logoutEverywhere" class="btn-danger" :disabled="authStore.isLoading" @keydown="handleLogoutButtonKeydown">All</button>
|
|
|
|
<button @click="logoutEverywhere" class="btn-danger" :disabled="authStore.isLoading" @keydown="handleLogoutButtonKeydown">All</button>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p class="logout-note" v-if="!hasMultipleSessions"><strong>Logout</strong> from {{ currentSessionHost }}.</p>
|
|
|
|
<div class="logout-footer">
|
|
|
|
<p class="logout-note" v-else><strong>Logout</strong> this session on {{ currentSessionHost }}, or <strong>All</strong> sessions across all sites and devices for {{ rpName }}. You'll need to log in again with your passkey afterwards.</p>
|
|
|
|
<p class="logout-note" v-if="!hasMultipleSessions"><strong>Logout</strong> from {{ currentSessionHost }}.</p>
|
|
|
|
|
|
|
|
<p class="logout-note" v-else><strong>Logout</strong> this session on {{ currentSessionHost }}, or <strong>All</strong> sessions across all sites and devices for {{ rpName }}. You'll need to log in again with your passkey afterwards.</p>
|
|
|
|
|
|
|
|
<a class="paskia-version" href="https://git.zi.fi/leovasanko/paskia" target="_blank" rel="noopener noreferrer">Paskia {{ paskiaVersion }}</a>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<RegistrationLinkModal
|
|
|
|
<RegistrationLinkModal
|
|
|
|
v-if="showRegLink"
|
|
|
|
v-if="showRegLink"
|
|
|
@@ -308,6 +311,7 @@ const handleDelete = async (credential) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const rpName = computed(() => authStore.settings?.rp_name || 'this service')
|
|
|
|
const rpName = computed(() => authStore.settings?.rp_name || 'this service')
|
|
|
|
|
|
|
|
const paskiaVersion = computed(() => authStore.settings?.version || '')
|
|
|
|
const sessions = computed(() => authStore.userInfo?.sessions || [])
|
|
|
|
const sessions = computed(() => authStore.userInfo?.sessions || [])
|
|
|
|
const currentSessionHost = computed(() => {
|
|
|
|
const currentSessionHost = computed(() => {
|
|
|
|
const currentSession = sessions.value.find(session => session.is_current)
|
|
|
|
const currentSession = sessions.value.find(session => session.is_current)
|
|
|
@@ -337,7 +341,21 @@ const isAdmin = computed(() => {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
const hasMultipleSessions = computed(() => sessions.value.length > 1)
|
|
|
|
const hasMultipleSessions = computed(() => sessions.value.length > 1)
|
|
|
|
const credentials = computed(() => authStore.userInfo?.credentials || [])
|
|
|
|
const credentials = computed(() => authStore.userInfo?.credentials || [])
|
|
|
|
const useWideLayout = computed(() => sessions.value.length > 4 || credentials.value.length > 4)
|
|
|
|
const useWideLayout = computed(() => {
|
|
|
|
|
|
|
|
// Check if any single site has more than 8 sessions
|
|
|
|
|
|
|
|
const groups = {}
|
|
|
|
|
|
|
|
for (const session of sessions.value) {
|
|
|
|
|
|
|
|
const host = session.host || ''
|
|
|
|
|
|
|
|
if (!groups[host]) groups[host] = []
|
|
|
|
|
|
|
|
groups[host].push(session)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasLargeSessionGroup = Object.values(groups).some(group => group.length > 8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if passkeys exceed 8
|
|
|
|
|
|
|
|
const hasManyCredentials = credentials.value.length > 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return hasLargeSessionGroup || hasManyCredentials
|
|
|
|
|
|
|
|
})
|
|
|
|
const breadcrumbEntries = computed(() => { const entries = [{ label: 'Auth', href: makeUiHref() }]; if (isAdmin.value) entries.push({ label: 'Admin', href: adminUiPath() }); return entries })
|
|
|
|
const breadcrumbEntries = computed(() => { const entries = [{ label: 'Auth', href: makeUiHref() }]; if (isAdmin.value) entries.push({ label: 'Admin', href: adminUiPath() }); return entries })
|
|
|
|
|
|
|
|
|
|
|
|
const saveName = async () => {
|
|
|
|
const saveName = async () => {
|
|
|
@@ -357,7 +375,10 @@ const saveName = async () => {
|
|
|
|
<style scoped>
|
|
|
|
<style scoped>
|
|
|
|
.section-header { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
|
|
.section-header { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
|
|
.empty-state { margin: 0; color: var(--color-text-muted); text-align: center; padding: 1rem 0; }
|
|
|
|
.empty-state { margin: 0; color: var(--color-text-muted); text-align: center; padding: 1rem 0; }
|
|
|
|
.logout-note { margin: 0.75rem 0 0; color: var(--color-text-muted); font-size: 0.875rem; }
|
|
|
|
.logout-note { margin: 0; color: var(--color-text-muted); }
|
|
|
|
|
|
|
|
.logout-footer { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin-top: 0.75rem; }
|
|
|
|
|
|
|
|
.paskia-version { color: var(--color-text-muted); text-decoration: none; white-space: nowrap; font-weight: 700; }
|
|
|
|
|
|
|
|
.paskia-version:hover { color: var(--color-link-hover); }
|
|
|
|
.remote-auth-inline { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
|
|
.remote-auth-inline { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
|
|
.remote-auth-label { display: block; margin: 0; font-size: 0.875rem; color: var(--color-text-muted); font-weight: 500; }
|
|
|
|
.remote-auth-label { display: block; margin: 0; font-size: 0.875rem; color: var(--color-text-muted); font-weight: 500; }
|
|
|
|
.remote-auth-description { font-size: 0.75rem; color: var(--color-text-muted); }
|
|
|
|
.remote-auth-description { font-size: 0.75rem; color: var(--color-text-muted); }
|
|
|
|