Finalize Paskia integration and built-in authentication.

This commit is contained in:
2026-01-31 00:47:04 +00:00
parent be69164c8f
commit 232fd92b22
19 changed files with 668 additions and 449 deletions
+28 -7
View File
@@ -2,7 +2,6 @@ import type { FileEntry, FUID, SelectedItems } from '@/repositories/Document'
import { Doc } from '@/repositories/Document'
import { defineStore, type StateTree } from 'pinia'
import { collator } from '@/utils'
import { logoutUser } from '@/repositories/User'
import { watchConnect, resumeWatching } from '@/repositories/WS'
import { sorted, type SortOrder } from '@/utils/docsort'
@@ -14,9 +13,10 @@ export const useMainStore = defineStore('main', {
fileExplorer: null as any,
error: '' as string,
connected: false,
authInProgress: false,
cursor: '' as string,
server: {} as Record<string, any> & { authentication?: 'none' | 'paskia' | 'password' },
dialog: '' as '' | 'settings' | 'usermgmt',
server: {} as Record<string, any> & { public?: boolean, paskia?: boolean },
dialog: '' as '' | 'settings' | 'usermgmt' | 'accessdenied',
uprogress: {} as any,
dprogress: {} as any,
prefs: {
@@ -69,12 +69,33 @@ export const useMainStore = defineStore('main', {
this.dialog = ''
if (!this.connected) resumeWatching()
},
clearSensitiveData() {
// Clear all sensitive state on logout or auth failure
localStorage.removeItem('cista-files')
this.document = []
this.selected.clear()
this.user.username = ''
this.user.privileged = false
this.user.isLoggedIn = false
this.connected = false
this.dialog = ''
this.cursor = ''
},
async logout() {
console.log("Logout")
await logoutUser()
this.$reset()
localStorage.clear()
history.go() // Reload page
try {
const res = await fetch('/auth/api/logout', { method: 'POST' })
if (!res.ok) {
const data = await res.json().catch(() => ({}))
this.error = data.message || data.detail || 'Logout failed'
return
}
} catch (e) {
this.error = 'Logout failed'
return
}
this.clearSensitiveData()
resumeWatching()
},
toggleSort(name: SortOrder) {
if (this.query) this.prefs.sortFiltered = this.prefs.sortFiltered === name ? '' : name
+6 -94
View File
@@ -1,107 +1,19 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { computed } from 'vue'
import { useMainStore } from './main'
import { SessionValidator, apiFetch, AuthCancelledError } from 'paskia'
// Session validator instance (only used in paskia mode)
let sessionValidator: SessionValidator | null = null
import { clearTree } from '@/repositories/WS'
export const useSsoAuthStore = defineStore('ssoAuth', () => {
// State
const userName = ref('')
const userUuid = ref('')
// Getters
const isExternalAuth = computed(() => {
const mainStore = useMainStore()
return mainStore.server?.authentication === 'paskia'
return mainStore.server?.paskia === true
})
// Actions
function clearDataOnUnauth() {
const mainStore = useMainStore()
// Clear localStorage
localStorage.removeItem('cista-files')
// Clear visible files by resetting document
mainStore.document = []
mainStore.selected.clear()
mainStore.user.isLoggedIn = false
userName.value = ''
userUuid.value = ''
mainStore.clearSensitiveData()
clearTree()
}
function handleSessionLost(error: Error) {
console.warn('Session lost:', error)
clearDataOnUnauth()
// Trigger re-authentication by reloading - paskia will handle the auth flow
location.reload()
}
async function validateSession(): Promise<boolean> {
// Only do session validation in paskia mode
if (!isExternalAuth.value) return true
try {
const res = await apiFetch('/auth/api/validate', {
method: 'POST',
headers: { 'accept': 'application/json' }
})
if (res.ok) {
// Extract user display name from Remote-Name header
userName.value = res.headers.get('Remote-Name') || ''
try {
const data = await res.json()
if (data.uuid) userUuid.value = data.uuid
} catch {
// Response may not have JSON body
}
return true
}
return false
} catch (e) {
if (e instanceof AuthCancelledError) {
console.log('User cancelled authentication')
return false
}
console.error('SSO validation error:', e)
return false
}
}
function startValidationPolling() {
if (!isExternalAuth.value) return
// Stop any existing validator
stopValidationPolling()
// Initial validation to get user info
validateSession()
// Use paskia's SessionValidator for ongoing session monitoring
sessionValidator = new SessionValidator(
() => userUuid.value || undefined, // getter for current user ID
handleSessionLost // callback when session is lost
)
sessionValidator.start()
}
function stopValidationPolling() {
if (sessionValidator) {
sessionValidator.stop()
sessionValidator = null
}
}
return {
// State
userName,
userUuid,
// Getters
isExternalAuth,
// Actions
validateSession,
clearDataOnUnauth,
startValidationPolling,
stopValidationPolling,
}
return { isExternalAuth, clearDataOnUnauth }
})