Implement session termination in admin API, for completeness.
This commit is contained in:
@@ -501,16 +501,20 @@ async function toggleOrgPermission(org, permId, checked) {
|
||||
function openDialog(type, data) { dialog.value = { type, data, busy: false, error: '' } }
|
||||
function closeDialog() { dialog.value = { type: null, data: null, busy: false, error: '' } }
|
||||
|
||||
async function onUserNameSaved() {
|
||||
async function refreshUserDetail() {
|
||||
await loadOrgs()
|
||||
if (selectedUser.value) {
|
||||
try {
|
||||
const r = await fetch(`/auth/api/admin/orgs/${selectedUser.value.org_uuid}/users/${selectedUser.value.uuid}`)
|
||||
const r = await fetch(`/auth/api/admin/orgs/${selectedUser.value.org_uuid}/users/${selectedUser.value.uuid}`)
|
||||
const jd = await r.json()
|
||||
if (!r.ok || jd.detail) throw new Error(jd.detail || 'Reload failed')
|
||||
userDetail.value = jd
|
||||
} catch (e) { authStore.showMessage(e.message || 'Failed to reload user', 'error') }
|
||||
}
|
||||
}
|
||||
|
||||
async function onUserNameSaved() {
|
||||
await refreshUserDetail()
|
||||
authStore.showMessage('User renamed', 'success', 1500)
|
||||
}
|
||||
|
||||
@@ -625,6 +629,7 @@ async function submitDialog() {
|
||||
@go-overview="goOverview"
|
||||
@open-org="openOrg"
|
||||
@on-user-name-saved="onUserNameSaved"
|
||||
@refresh-user-detail="refreshUserDetail"
|
||||
@edit-user-name="editUserName"
|
||||
@close-reg-modal="showRegModal = false"
|
||||
/>
|
||||
|
||||
@@ -14,9 +14,10 @@ const props = defineProps({
|
||||
showRegModal: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['generateUserRegistrationLink', 'goOverview', 'openOrg', 'onUserNameSaved', 'closeRegModal', 'editUserName'])
|
||||
const emit = defineEmits(['generateUserRegistrationLink', 'goOverview', 'openOrg', 'onUserNameSaved', 'closeRegModal', 'editUserName', 'refreshUserDetail'])
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const terminatingSessions = ref({})
|
||||
|
||||
function onLinkCopied() {
|
||||
authStore.showMessage('Link copied to clipboard!')
|
||||
@@ -39,6 +40,34 @@ function handleDelete(credential) {
|
||||
.catch(err => console.error('Delete credential error', err))
|
||||
}
|
||||
|
||||
async function handleTerminateSession(session) {
|
||||
const sessionId = session?.id
|
||||
if (!sessionId) return
|
||||
terminatingSessions.value = { ...terminatingSessions.value, [sessionId]: true }
|
||||
try {
|
||||
const res = await fetch(`/auth/api/admin/orgs/${props.selectedUser.org_uuid}/users/${props.selectedUser.uuid}/sessions/${sessionId}`, { method: 'DELETE' })
|
||||
const data = await res.json()
|
||||
if (data.status === 'ok') {
|
||||
if (data.current_session_terminated) {
|
||||
sessionStorage.clear()
|
||||
location.reload()
|
||||
return
|
||||
}
|
||||
emit('refreshUserDetail') // Refresh without showing rename message
|
||||
authStore.showMessage('Session terminated', 'success', 2500)
|
||||
} else {
|
||||
authStore.showMessage(data.detail || 'Failed to terminate session', 'error')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Terminate session error', err)
|
||||
authStore.showMessage('Failed to terminate session', 'error')
|
||||
} finally {
|
||||
const next = { ...terminatingSessions.value }
|
||||
delete next[sessionId]
|
||||
terminatingSessions.value = next
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,9 +113,10 @@ function handleDelete(credential) {
|
||||
</section>
|
||||
<SessionList
|
||||
:sessions="userDetail.sessions || []"
|
||||
:allow-terminate="false"
|
||||
:terminating-sessions="terminatingSessions"
|
||||
:empty-message="'This user has no active sessions.'"
|
||||
:section-description="'View the active sessions for this user.'"
|
||||
:section-description="'View and manage the active sessions for this user.'"
|
||||
@terminate="handleTerminateSession"
|
||||
/>
|
||||
</template>
|
||||
<div class="actions ancillary-actions">
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<span v-if="session.is_current" class="badge badge-current">Current</span>
|
||||
<span v-else-if="isSameNetwork(session.ip)" class="badge">Same IP</span>
|
||||
<button
|
||||
v-if="allowTerminate"
|
||||
@click="$emit('terminate', session)"
|
||||
class="btn-card-delete"
|
||||
:disabled="isTerminating(session.id)"
|
||||
@@ -54,7 +53,6 @@ import { formatDate } from '@/utils/helpers'
|
||||
|
||||
const props = defineProps({
|
||||
sessions: { type: Array, default: () => [] },
|
||||
allowTerminate: { type: Boolean, default: true },
|
||||
emptyMessage: { type: String, default: 'You currently have no other active sessions.' },
|
||||
sectionDescription: { type: String, default: "Review where you're signed in and end any sessions you no longer recognize." },
|
||||
terminatingSessions: { type: Object, default: () => ({}) }
|
||||
|
||||
Reference in New Issue
Block a user