mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
## Summary Add workspace authentication composables and types for per-tab workspace isolation. This infrastructure enables users to work in different workspaces in different browser tabs. ## Changes - **useWorkspaceAuth composable** - workspace token management - Exchange Firebase token for workspace-scoped JWT via `POST /api/auth/token` - Auto-refresh tokens 5 minutes before expiry - Per-tab sessionStorage caching - **useWorkspaceSwitch composable** - workspace switching with unsaved changes confirmation - **WorkspaceWithRole/WorkspaceTokenResponse types** - aligned with backend API - **firebaseAuthStore.getAuthHeader()** - prioritizes workspace tokens over Firebase tokens - **useSessionCookie** - uses Firebase token directly (getIdToken()) since getAuthHeader() now returns workspace token ## Backend Dependency - `POST /api/auth/token` - exchange Firebase token for workspace token - `GET /api/workspaces` - list user's workspaces ## Related - https://github.com/Comfy-Org/ComfyUI_frontend/pull/6295 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8073-feat-add-per-tab-workspace-authentication-infrastructure-2e96d73d3650816c8cf9dae9c330aebb) by [Unito](https://www.unito.io) --------- Co-authored-by: anthropic/claude <noreply@anthropic.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
150 lines
3.9 KiB
TypeScript
150 lines
3.9 KiB
TypeScript
import { whenever } from '@vueuse/core'
|
|
import { computed, watch } from 'vue'
|
|
|
|
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
|
import { t } from '@/i18n'
|
|
import { useDialogService } from '@/services/dialogService'
|
|
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
|
import type { AuthUserInfo } from '@/types/authTypes'
|
|
|
|
export const useCurrentUser = () => {
|
|
const authStore = useFirebaseAuthStore()
|
|
const commandStore = useCommandStore()
|
|
const apiKeyStore = useApiKeyAuthStore()
|
|
const dialogService = useDialogService()
|
|
const { deleteAccount } = useFirebaseAuthActions()
|
|
|
|
const firebaseUser = computed(() => authStore.currentUser)
|
|
const isApiKeyLogin = computed(() => apiKeyStore.isAuthenticated)
|
|
const isLoggedIn = computed(
|
|
() => !!isApiKeyLogin.value || firebaseUser.value !== null
|
|
)
|
|
|
|
const resolvedUserInfo = computed<AuthUserInfo | null>(() => {
|
|
if (isApiKeyLogin.value && apiKeyStore.currentUser) {
|
|
return { id: apiKeyStore.currentUser.id }
|
|
}
|
|
|
|
if (firebaseUser.value) {
|
|
return { id: firebaseUser.value.uid }
|
|
}
|
|
|
|
return null
|
|
})
|
|
|
|
const onUserResolved = (callback: (user: AuthUserInfo) => void) =>
|
|
whenever(resolvedUserInfo, callback, { immediate: true })
|
|
|
|
const onTokenRefreshed = (callback: () => void) =>
|
|
whenever(() => authStore.tokenRefreshTrigger, callback)
|
|
|
|
const onUserLogout = (callback: () => void) => {
|
|
watch(resolvedUserInfo, (user, prevUser) => {
|
|
if (prevUser && !user) callback()
|
|
})
|
|
}
|
|
|
|
const userDisplayName = computed(() => {
|
|
if (isApiKeyLogin.value) {
|
|
return apiKeyStore.currentUser?.name
|
|
}
|
|
return firebaseUser.value?.displayName
|
|
})
|
|
|
|
const userEmail = computed(() => {
|
|
if (isApiKeyLogin.value) {
|
|
return apiKeyStore.currentUser?.email
|
|
}
|
|
return firebaseUser.value?.email
|
|
})
|
|
|
|
const providerName = computed(() => {
|
|
if (isApiKeyLogin.value) {
|
|
return 'Comfy API Key'
|
|
}
|
|
|
|
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
|
if (providerId?.includes('google')) {
|
|
return 'Google'
|
|
}
|
|
if (providerId?.includes('github')) {
|
|
return 'GitHub'
|
|
}
|
|
return providerId
|
|
})
|
|
|
|
const providerIcon = computed(() => {
|
|
if (isApiKeyLogin.value) {
|
|
return 'pi pi-key'
|
|
}
|
|
|
|
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
|
if (providerId?.includes('google')) {
|
|
return 'pi pi-google'
|
|
}
|
|
if (providerId?.includes('github')) {
|
|
return 'pi pi-github'
|
|
}
|
|
return 'pi pi-user'
|
|
})
|
|
|
|
const isEmailProvider = computed(() => {
|
|
if (isApiKeyLogin.value) {
|
|
return false
|
|
}
|
|
|
|
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
|
return providerId === 'password'
|
|
})
|
|
|
|
const userPhotoUrl = computed(() => {
|
|
if (isApiKeyLogin.value) return null
|
|
return firebaseUser.value?.photoURL
|
|
})
|
|
|
|
const handleSignOut = async () => {
|
|
if (isApiKeyLogin.value) {
|
|
await apiKeyStore.clearStoredApiKey()
|
|
} else {
|
|
await commandStore.execute('Comfy.User.SignOut')
|
|
}
|
|
}
|
|
|
|
const handleSignIn = async () => {
|
|
await commandStore.execute('Comfy.User.OpenSignInDialog')
|
|
}
|
|
|
|
const handleDeleteAccount = async () => {
|
|
const confirmed = await dialogService.confirm({
|
|
title: t('auth.deleteAccount.confirmTitle'),
|
|
message: t('auth.deleteAccount.confirmMessage'),
|
|
type: 'delete'
|
|
})
|
|
|
|
if (confirmed) {
|
|
await deleteAccount()
|
|
}
|
|
}
|
|
|
|
return {
|
|
loading: authStore.loading,
|
|
isLoggedIn,
|
|
isApiKeyLogin,
|
|
isEmailProvider,
|
|
userDisplayName,
|
|
userEmail,
|
|
userPhotoUrl,
|
|
providerName,
|
|
providerIcon,
|
|
resolvedUserInfo,
|
|
handleSignOut,
|
|
handleSignIn,
|
|
handleDeleteAccount,
|
|
onUserResolved,
|
|
onTokenRefreshed,
|
|
onUserLogout
|
|
}
|
|
}
|