mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
[backport rh-test] Add session cookie auth (#6299)
## Summary Backport of session cookie authentication implementation from main to rh-test. ## Changes - Added session cookie management via extension hooks - Cookie created on login, refreshed on token refresh, deleted on logout - New extension hooks: `onAuthTokenRefreshed()` and `onAuthUserLogout()` - DDD-compliant structure with platform layer (`src/platform/auth/session/`) ## Conflict Resolution - Resolved import conflict in `firebaseAuthStore.ts` (merged `onIdTokenChanged` + `sendEmailVerification`) - Added `onIdTokenChanged` mock to tests ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6299-backport-rh-test-Add-session-cookie-auth-2986d73d365081238507f99ae789d44b) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { whenever } from '@vueuse/core'
|
import { whenever } from '@vueuse/core'
|
||||||
import { computed } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
@@ -37,6 +37,15 @@ export const useCurrentUser = () => {
|
|||||||
const onUserResolved = (callback: (user: AuthUserInfo) => void) =>
|
const onUserResolved = (callback: (user: AuthUserInfo) => void) =>
|
||||||
whenever(resolvedUserInfo, callback, { immediate: true })
|
whenever(resolvedUserInfo, callback, { immediate: true })
|
||||||
|
|
||||||
|
const onTokenRefreshed = (callback: () => void) =>
|
||||||
|
whenever(() => authStore.tokenRefreshTrigger, callback)
|
||||||
|
|
||||||
|
const onUserLogout = (callback: () => void) => {
|
||||||
|
watch(resolvedUserInfo, (user) => {
|
||||||
|
if (!user) callback()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const userDisplayName = computed(() => {
|
const userDisplayName = computed(() => {
|
||||||
if (isApiKeyLogin.value) {
|
if (isApiKeyLogin.value) {
|
||||||
return apiKeyStore.currentUser?.name
|
return apiKeyStore.currentUser?.name
|
||||||
@@ -133,6 +142,8 @@ export const useCurrentUser = () => {
|
|||||||
handleSignOut,
|
handleSignOut,
|
||||||
handleSignIn,
|
handleSignIn,
|
||||||
handleDeleteAccount,
|
handleDeleteAccount,
|
||||||
onUserResolved
|
onUserResolved,
|
||||||
|
onTokenRefreshed,
|
||||||
|
onUserLogout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/extensions/core/cloudSessionCookie.ts
Normal file
25
src/extensions/core/cloudSessionCookie.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { useSessionCookie } from '@/platform/auth/session/useSessionCookie'
|
||||||
|
import { useExtensionService } from '@/services/extensionService'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cloud-only extension that manages session cookies for authentication.
|
||||||
|
* Creates session cookie on login, refreshes it when token refreshes, and deletes on logout.
|
||||||
|
*/
|
||||||
|
useExtensionService().registerExtension({
|
||||||
|
name: 'Comfy.Cloud.SessionCookie',
|
||||||
|
|
||||||
|
onAuthUserResolved: async () => {
|
||||||
|
const { createSession } = useSessionCookie()
|
||||||
|
await createSession()
|
||||||
|
},
|
||||||
|
|
||||||
|
onAuthTokenRefreshed: async () => {
|
||||||
|
const { createSession } = useSessionCookie()
|
||||||
|
await createSession()
|
||||||
|
},
|
||||||
|
|
||||||
|
onAuthUserLogout: async () => {
|
||||||
|
const { deleteSession } = useSessionCookie()
|
||||||
|
await deleteSession()
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -28,6 +28,7 @@ import './widgetInputs'
|
|||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
await import('./cloudRemoteConfig')
|
await import('./cloudRemoteConfig')
|
||||||
await import('./cloudBadges')
|
await import('./cloudBadges')
|
||||||
|
await import('./cloudSessionCookie')
|
||||||
|
|
||||||
if (window.__CONFIG__?.subscription_required) {
|
if (window.__CONFIG__?.subscription_required) {
|
||||||
await import('./cloudSubscription')
|
await import('./cloudSubscription')
|
||||||
|
|||||||
65
src/platform/auth/session/useSessionCookie.ts
Normal file
65
src/platform/auth/session/useSessionCookie.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
|
import { api } from '@/scripts/api'
|
||||||
|
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session cookie management for cloud authentication.
|
||||||
|
* Creates and deletes session cookies on the ComfyUI server.
|
||||||
|
*/
|
||||||
|
export const useSessionCookie = () => {
|
||||||
|
/**
|
||||||
|
* Creates or refreshes the session cookie.
|
||||||
|
* Called after login and on token refresh.
|
||||||
|
*/
|
||||||
|
const createSession = async (): Promise<void> => {
|
||||||
|
if (!isCloud) return
|
||||||
|
|
||||||
|
const authStore = useFirebaseAuthStore()
|
||||||
|
const authHeader = await authStore.getAuthHeader()
|
||||||
|
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new Error('No auth header available for session creation')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(api.apiURL('/auth/session'), {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
...authHeader,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}))
|
||||||
|
throw new Error(
|
||||||
|
`Failed to create session: ${errorData.message || response.statusText}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the session cookie.
|
||||||
|
* Called on logout.
|
||||||
|
*/
|
||||||
|
const deleteSession = async (): Promise<void> => {
|
||||||
|
if (!isCloud) return
|
||||||
|
|
||||||
|
const response = await fetch(api.apiURL('/auth/session'), {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}))
|
||||||
|
throw new Error(
|
||||||
|
`Failed to delete session: ${errorData.message || response.statusText}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSession,
|
||||||
|
deleteSession
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,6 +80,20 @@ export const useExtensionService = () => {
|
|||||||
void extension.onAuthUserResolved?.(user, app)
|
void extension.onAuthUserResolved?.(user, app)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension.onAuthTokenRefreshed) {
|
||||||
|
const { onTokenRefreshed } = useCurrentUser()
|
||||||
|
onTokenRefreshed(() => {
|
||||||
|
void extension.onAuthTokenRefreshed?.()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.onAuthUserLogout) {
|
||||||
|
const { onUserLogout } = useCurrentUser()
|
||||||
|
onUserLogout(() => {
|
||||||
|
void extension.onAuthUserLogout?.()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
getAdditionalUserInfo,
|
getAdditionalUserInfo,
|
||||||
onAuthStateChanged,
|
onAuthStateChanged,
|
||||||
|
onIdTokenChanged,
|
||||||
sendEmailVerification,
|
sendEmailVerification,
|
||||||
sendPasswordResetEmail,
|
sendPasswordResetEmail,
|
||||||
setPersistence,
|
setPersistence,
|
||||||
@@ -62,6 +63,9 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
|||||||
const balance = ref<GetCustomerBalanceResponse | null>(null)
|
const balance = ref<GetCustomerBalanceResponse | null>(null)
|
||||||
const lastBalanceUpdateTime = ref<Date | null>(null)
|
const lastBalanceUpdateTime = ref<Date | null>(null)
|
||||||
|
|
||||||
|
// Token refresh trigger - increments when token is refreshed
|
||||||
|
const tokenRefreshTrigger = ref(0)
|
||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
const googleProvider = new GoogleAuthProvider()
|
const googleProvider = new GoogleAuthProvider()
|
||||||
googleProvider.addScope('email')
|
googleProvider.addScope('email')
|
||||||
@@ -99,6 +103,13 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
|||||||
lastBalanceUpdateTime.value = null
|
lastBalanceUpdateTime.value = null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Listen for token refresh events
|
||||||
|
onIdTokenChanged(auth, (user) => {
|
||||||
|
if (user && isCloud) {
|
||||||
|
tokenRefreshTrigger.value++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const getIdToken = async (): Promise<string | undefined> => {
|
const getIdToken = async (): Promise<string | undefined> => {
|
||||||
if (!currentUser.value) return
|
if (!currentUser.value) return
|
||||||
try {
|
try {
|
||||||
@@ -434,6 +445,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
|||||||
balance,
|
balance,
|
||||||
lastBalanceUpdateTime,
|
lastBalanceUpdateTime,
|
||||||
isFetchingBalance,
|
isFetchingBalance,
|
||||||
|
tokenRefreshTrigger,
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
|
|||||||
@@ -219,5 +219,17 @@ export interface ComfyExtension {
|
|||||||
*/
|
*/
|
||||||
onAuthUserResolved?(user: AuthUserInfo, app: ComfyApp): Promise<void> | void
|
onAuthUserResolved?(user: AuthUserInfo, app: ComfyApp): Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired whenever the auth token is refreshed.
|
||||||
|
* This is an experimental API and may be changed or removed in the future.
|
||||||
|
*/
|
||||||
|
onAuthTokenRefreshed?(): Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when user logs out.
|
||||||
|
* This is an experimental API and may be changed or removed in the future.
|
||||||
|
*/
|
||||||
|
onAuthUserLogout?(): Promise<void> | void
|
||||||
|
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ vi.mock('firebase/auth', async (importOriginal) => {
|
|||||||
createUserWithEmailAndPassword: vi.fn(),
|
createUserWithEmailAndPassword: vi.fn(),
|
||||||
signOut: vi.fn(),
|
signOut: vi.fn(),
|
||||||
onAuthStateChanged: vi.fn(),
|
onAuthStateChanged: vi.fn(),
|
||||||
|
onIdTokenChanged: vi.fn(),
|
||||||
signInWithPopup: vi.fn(),
|
signInWithPopup: vi.fn(),
|
||||||
GoogleAuthProvider: class {
|
GoogleAuthProvider: class {
|
||||||
addScope = vi.fn()
|
addScope = vi.fn()
|
||||||
|
|||||||
Reference in New Issue
Block a user