fix: gate cloud API calls behind Firebase authentication (#9909)

## Summary

- On cloud deployments, the bootstrap sequence was firing authenticated
API calls (`/api/users`, `/api/settings`, `/api/userdata`, `/api/i18n`)
before verifying the user was authenticated via Firebase, causing
repeated 401 responses
- Moves the Firebase auth gate to the top of `startStoreBootstrap()` and
waits for both `isInitialized` **and** `isAuthenticated` before making
any API calls
- The router guard already redirects unauthenticated users to login;
once they authenticate, `isAuthenticated` becomes `true` and the
bootstrap proceeds normally

## Test plan

- [ ] Verify on cloud deployment: unauthenticated users see no 401 API
errors in the network tab
- [ ] Verify on cloud: after login, settings/i18n/workflows load
correctly
- [ ] Verify non-cloud deployments are unaffected (no `isCloud` guard
hit)
- [ ] Unit tests pass (`pnpm test:unit -- --run
src/stores/bootstrapStore.test.ts`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9909-fix-gate-cloud-API-calls-behind-Firebase-authentication-3236d73d3650819287e4e4c68623463b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Miller
2026-03-17 03:15:58 -07:00
committed by GitHub
parent 54fe02bdf1
commit 1934064839
2 changed files with 22 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ref } from 'vue'
import { nextTick, ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -51,9 +51,11 @@ vi.mock('@/stores/userStore', () => ({
}))
const mockIsFirebaseInitialized = ref(false)
const mockIsFirebaseAuthenticated = ref(false)
vi.mock('@/stores/firebaseAuthStore', () => ({
useFirebaseAuthStore: vi.fn(() => ({
isInitialized: mockIsFirebaseInitialized
isInitialized: mockIsFirebaseInitialized,
isAuthenticated: mockIsFirebaseAuthenticated
}))
}))
@@ -66,6 +68,7 @@ describe('bootstrapStore', () => {
beforeEach(() => {
mockIsSettingsReady.value = false
mockIsFirebaseInitialized.value = false
mockIsFirebaseAuthenticated.value = false
mockNeedsLogin.value = false
mockDistributionTypes.isCloud = false
setActivePinia(createTestingPinia({ stubActions: false }))
@@ -95,17 +98,23 @@ describe('bootstrapStore', () => {
mockDistributionTypes.isCloud = true
})
it('waits for Firebase auth before loading i18n and settings', async () => {
it('waits for Firebase auth before loading stores', async () => {
const store = useBootstrapStore()
const settingStore = useSettingStore()
const bootstrapPromise = store.startStoreBootstrap()
// Bootstrap is blocked waiting for firebase
expect(store.isI18nReady).toBe(false)
expect(settingStore.isReady).toBe(false)
// Unblock by initializing firebase
// Firebase initialized but user not yet authenticated
mockIsFirebaseInitialized.value = true
await nextTick()
expect(store.isI18nReady).toBe(false)
expect(settingStore.isReady).toBe(false)
// User authenticates (e.g. signs in on login page)
mockIsFirebaseAuthenticated.value = true
await bootstrapPromise
await vi.waitFor(() => {

View File

@@ -36,14 +36,17 @@ export const useBootstrapStore = defineStore('bootstrap', () => {
}
async function startStoreBootstrap() {
if (isCloud) {
const { isInitialized, isAuthenticated } = storeToRefs(
useFirebaseAuthStore()
)
await until(isInitialized).toBe(true)
await until(isAuthenticated).toBe(true)
}
const userStore = useUserStore()
await userStore.initialize()
if (isCloud) {
const { isInitialized } = storeToRefs(useFirebaseAuthStore())
await until(isInitialized).toBe(true)
}
const { needsLogin } = storeToRefs(userStore)
await until(needsLogin).toBe(false)