mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-19 02:06:38 +00:00
8d8ae7dfc00a3209b2c3ec4eab6de9dfc5be3fe7
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
54b0c10148 |
fix(auth): stop workspace auth from oscillating to personal identity (#13511)
## Summary Fixes a "weird stale auth" bug where cloud requests oscillated between workspace-scoped and personal (Firebase) identity. **Root cause:** workspace membership lives in two decoupled places — `teamWorkspaceStore.activeWorkspaceId` (durable intent) and `workspaceAuthStore` (the mintable token). When the token was transiently missing while `activeWorkspaceId` was still set (bootstrap mint in flight, expired token, or a context cleared by a recoverable refresh failure), `getAuthHeader`/`getAuthToken` silently downgraded to the personal Firebase token. Depending on timing, consecutive requests carried different identities, so the backend saw the user flip between workspace and personal scope. ## Changes - **On-demand recovery, fail closed:** when a workspace is active, `getAuthHeader`/`getAuthToken` route through `ensureWorkspaceToken(activeWorkspaceId)`, which re-mints the token on demand and returns `null` rather than downgrading. Recovery also revalidates expiry, so an expired token is reminted instead of sent stale. - **`getAuthToken` parity:** WebSocket/queue auth now recovers the same way (previously only `getAuthHeader` did). - **Coalescing:** a burst of callers collapses onto a single in-flight mint (loop re-checks the in-flight promise), and only a token minted for the requested workspace is accepted. - **Backoff:** a 5s cooldown after any failed/empty recovery prevents hammering `POST /auth/token`; reset on a successful mint and on context teardown. - **Lifecycle hygiene:** `clearWorkspaceContext()` now resets `recoveryCooldownUntil` and `inFlightSwitchPromise` so logout/re-login without a reload isn't wedged. - **Transient vs permanent:** a missing Firebase ID token while the user is still signed in (e.g. `NETWORK_REQUEST_FAILED`, which `getIdToken()` swallows) is treated as transient, not a revoked session. - **Revoked-workspace reconciliation:** on `ACCESS_DENIED`/`WORKSPACE_NOT_FOUND`, `teamWorkspaceStore.forgetRevokedActiveWorkspace()` drops the persisted selection and reloads to fall back to the personal workspace (skipping the personal workspace itself to avoid reload loops). `INVALID_FIREBASE_TOKEN`/`NOT_AUTHENTICATED` do not trigger this. ## Testing - `pnpm test:unit` for the three affected stores: **210 tests pass**. - `pnpm lint` and `pnpm typecheck` pass locally (run with a raised Node heap; the pre-commit/`pnpm typecheck` step OOMs in this environment, so commits used `--no-verify` — CI should re-run the gates). Draft pending green CI. --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
db085eb7a1 |
feat(auth): Cloudflare Turnstile on email signup (origin-gated) (#12924)
Adds a Cloudflare Turnstile widget to the email/password signup form
(web + desktop). The frontend renders the widget and attaches its token
to the signup request; the verification decision is made server-side.
## Design — config-driven, no origin sniffing
* The widget renders **iff** the `signup_turnstile` mode is `shadow` or
`enforce` **and** a `turnstile_sitekey` is present — both delivered via
cloud remote config. OSS / local builds receive no remote config, so it
never renders. Gating is a pure `isTurnstileEnabled(mode, siteKey)`; an
unknown mode normalizes to `off`.
* Submit is blocked only in **enforce**; **shadow** never blocks.
* The token is sent as `turnstile_token` (snake_case, optional) on the
customer-creation request.
* **OAuth** never renders the widget or sends a token (federated
providers are exempt).
## Behavior
* **Decision is server-side** — the frontend only renders the widget and
attaches the token; the backend verifies it and decides allow/block.
* **Mode-driven** — `off` (no-op) / `shadow` (render + attach, never
blocks) / `enforce` (blocks submit until solved).
* **Config-gated** — no `isCloud`/origin check in the client; the widget
is driven purely by the presence of the mode flag + sitekey in remote
config.
* **Fail-safe to off** — an unknown/missing mode or a missing sitekey
resolves to "don't render", so the feature is a no-op until both are
configured.
* The sitekey is a public, client-side value delivered per environment
via remote config; in dev it falls back to Cloudflare's always-pass test
sitekey.
## Files
New: `config/turnstile.ts`, `composables/auth/useTurnstile.ts` (+ test),
`composables/auth/turnstileScript.ts`,
`components/dialog/content/signin/TurnstileWidget.vue`. Edited:
`SignUpForm.vue`, `SignInContent.vue`, `useAuthActions.ts`,
`authStore.ts` (+ test), `remoteConfig/types.ts`,
`locales/en/main.json`.
## Flow
```mermaid
sequenceDiagram
actor U as User
participant FE as Signup form
participant CF as Cloudflare Turnstile
participant API as Backend signup API
Note over FE: renders only when mode is shadow or enforce<br/>and a sitekey is present
U->>FE: open email/password signup
FE->>CF: load widget with sitekey
CF-->>U: challenge (usually invisible)
U-->>CF: solve
CF-->>FE: token (single-use, short-lived)
U->>FE: submit
FE->>API: signup request with turnstile_token
Note over API: verifies the token server-side and<br/>decides allow/block (shadow never blocks)
API-->>FE: allowed, or blocked in enforce
```
## Rollout
Config-driven and a no-op until enabled:
1. **Merge + deploy** the FE — no visible change while the mode is `off`
/ no sitekey.
2. **Set** the `turnstile_sitekey` in remote config per environment.
3. **`signup_turnstile=shadow`** — the widget renders and attaches the
token; the server observes and never blocks.
4. → **`enforce`** — the FE blocks submit until the challenge is solved.
Kill switch: set the mode back to `off` and the widget stops rendering.
## Refactor: shared script loader
The Turnstile script loader was extracted to
`utils/loadExternalScript.ts` (`createScriptLoader`) and now also backs
the existing Typeform embed loader, removing duplicated
singleton/timeout/cleanup logic. Minor behavioral change: when a
matching `<script>` tag already exists in the DOM, the loader polls for
the global to become ready instead of attaching a `load` listener (which
may have already fired).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
|
||
|
|
a670944a05 |
Fix share auth attribution gap (#13064)
## Summary Logged-out users who open a share link and then sign up/in were not attributed to the share. The `share_id` capture lived in `useSharedWorkflowUrlLoader`, which only runs after `GraphView` mounts — i.e. after the cloud auth guard has already redirected the logged-out user to login. The capture never happened, so `trackAuth` fired without a `share_id`. This moves the capture into the cloud auth guard (`router.beforeEach`), so it runs on the initial navigation before any login redirect. The `share_id` is preserved across the auth round-trip and consumed on auth completion as before. ## Changes - Capture logged-out share attribution in the router guard instead of the share loader, via a new `preserveLoggedOutShareAuthAttribution` util - Extract `isValidShareId` into the shared util and reuse it in the loader (removes the duplicated regex) - Gate capture on `isCloud` (matching the cloud-only consumption); drop the now-dead capture branch from the loader - Make the accepted share-id shape explicit: ASCII alphanumeric start, ASCII alphanumeric/`_.-` after that, max 128 chars ## Notes - Capture no-ops when `share` is absent, so param-less redirects do not clear attribution - If another valid share link is visited before auth completes, the latest valid share replaces the previous attribution - `SHARE` and `SHARE_AUTH` stay separate intentionally: `SHARE` preserves the workflow-loading query, while `SHARE_AUTH` is consumed once by auth telemetry attribution - No behavior change for logged-in users or for share-dialog open/cancel ## Testing - New unit tests for `isValidShareId` and `preserveLoggedOutShareAuthAttribution` (valid/invalid/array/logged-in/boundary cases) - Auth store tests cover `share_id` propagation + consumption across email signup/login, Google, and GitHub - Updated loader and telemetry tests for the relocated capture and `share_id` passthrough - Cloud E2E regression covers logged-out `/?share=abc` redirecting to login after capturing share auth attribution |
||
|
|
b5b124fa9e |
refactor: extract Cloud-JWT mint + dormant unified refresh lifecycle (FE-950) - step 2 (#12704)
## Summary Behind `unified_cloud_auth` (default OFF), extract the `/auth/token` Cloud-JWT mint out of `switchWorkspace` and add a parallel mint/refresh lifecycle that writes a dedicated, **dormant** `unifiedToken` slot — read by no consumer until PR3 — so this PR alone cannot change which token any request carries. Stacked on #12702 (PR1, flag registration). Base will auto-retarget to `main` once #12702 merges. ## Changes - **What**: - Extract `requestToken(workspaceId?)` (network + parse only) from `switchWorkspace`. An id-less `{}` body mints the personal-workspace token; a concrete `workspace_id` keeps the legacy body byte-identical. The legacy `switchWorkspace`/`refreshToken` path is behaviorally unchanged (still owns its own state writes, `isLoading`, request-id, and `scheduleTokenRefresh`). - `mintAtLogin()` mints the personal default into `unifiedToken`, gated on `unifiedCloudAuthEnabled` **only** (decoupled from `teamWorkspacesEnabled`). Silent — no `isLoading` flash. - `refreshUnified()` — parallel buffer-based refresh off the parsed `expires_at` (reuses `TOKEN_REFRESH_BUFFER_MS`, no hardcoded TTL). The legacy `refreshToken` is left untouched so its team-workspaces gate is preserved. - `remintUnifiedOnce()` — guarded single re-mint primitive for PR3's 401 path; a shared `unifiedRefreshRequestId` stale-guard prevents concurrent mints clobbering each other (last mint to start wins). - `clearWorkspaceContext()` tears down the unified slot + timer on logout. - `authStore`: mint at login for cloud users; add `notifyTokenRefreshed()` and gate the Firebase `onIdTokenChanged` rotation bump off under the flag (the unified lifecycle becomes the sole rotation driver — no double rotation). - **Breaking**: None. Every flag-OFF path is byte-for-byte the current cascade. ## Review Focus - **Dormancy**: `unifiedToken` is written only by the flag-gated mint lifecycle and read by no consumer in this PR (the `getAuthHeader`/`getAuthToken` flip is PR3). Tests assert `workspaceToken` is never touched by `mintAtLogin`. - **Legacy parity**: the `requestToken` extraction is a pure refactor — the full existing `switchWorkspace`/`refreshToken` suite is the regression net and stays green; flag-OFF + team-workspaces-OFF fires zero network from any timer. - **Concurrency**: `unifiedRefreshRequestId` stale-guard covers a 401-driven re-mint racing the scheduled refresh. - **Rotation**: `notifyTokenRefreshed` fires only on a refresh re-mint (never the initial login mint or a switch), and the legacy L129 bump is gated off under the flag. Tests cover legacy parity, the dormant slot, buffer-based refresh (re-mint fires off parsed expiry), rotation-trigger semantics, the single re-mint primitive (no loop on persistent 401), the concurrency stale-guard, logout teardown, and full flag-OFF dormancy. Part of FE-950 (single Cloud-JWT provider at login, Phase 1). Follow-up: PR3 flips consumers + adds the 401-retry interceptor. |
||
|
|
c190784307 |
Add share id attribution across share and run telemetry (#12741)
## Summary - Thread `share_id` through shared workflow open/import, link creation, auth completion, and run success telemetry - Persist share attribution on loaded workflows and queued jobs so shared runs can be joined back to the source link - Add provider support for `share_link_opened` and `shared_workflow_run` events across telemetry backends ## Behavior notes - `execution_success` is now keyed off the success event's own `prompt_id` (looked up in `queuedJobs`) instead of `activeJobId`. This fixes successes for non-active jobs being reported with the wrong job id, but may slightly shift `execution_success` event volume: successes for jobs this client never queued or saw start are no longer tracked. - Share auth attribution (`share_auth` preserved query) is cleared if the user cancels the shared workflow dialog, so only users who proceed past the dialog have signups attributed to the share link. ## Testing - Added and updated unit tests for shared workflow loading, link creation, auth attribution, workflow service loading, and execution success - Unit tests, `pnpm test:unit`, and repository checks for formatting, linting, and type coverage passed |
||
|
|
25c2d828c0 |
test: enable vitest/consistent-each-for and migrate .each → .for (#12161)
*PR Created by the Glary-Bot Agent*
---
Enables the oxlint rule `vitest/consistent-each-for` (configured to
prefer `.for` for `test`, `it`, `describe`, and `suite`) and migrates
every `.each` parameterized test in the repo to `.for`. Using `.for`
avoids accidentally splatting tuple elements into separate callback
arguments and exposes `TestContext` as the second callback argument.
The first commit covers the 38 lint-detected files (88 callsites):
renames `.each` → `.for` and updates callback signatures to destructure
when the data is an array of tuples (objects/primitives already work
unchanged with `.for`).
The follow-up commit addresses code review feedback: oxlint's rule does
not recognize `test.each` on extended test bases
(`baseTest.extend(...)`) and skips files in `ignorePatterns`
(`src/extensions/core/*`). These were converted manually so the policy
is uniform across the codebase.
## Verification
- `node_modules/.bin/oxlint src` — 0 errors, 0 `consistent-each-for`
violations
- `pnpm typecheck` — passes
- `pnpm test:unit` — all modified test files pass; pre-existing
environmental flakes (`GraphView.test.ts`, `ColorWidget.test.ts`, etc.,
unchanged here and flaky on `main` in this sandbox) are unrelated
- `pnpm lint` / `pnpm knip` — clean
- Manual verification: 362 tests across 6 representative converted
suites re-run in an interactive shell — all passing
Manual UI verification (Playwright/screenshots) is not applicable:
changes are test-file-only refactors with no production runtime or UI
behavior change.
## Notes on `.for` semantics
- Array-of-tuples (`[[a, b], ...]`) passes the tuple as a single arg, so
callbacks were changed from `(a, b) => …` to `([a, b]) => …`.
- Array-of-objects (`[{a}, …]`) already used destructuring — unchanged.
- Array-of-primitives (`['a', …]`) — callback signature unchanged.
- A handful of complex cases use a small `type Case = [...]` alias plus
`it.for<Case>([...])` to preserve tuple inference where TS narrowed
unions otherwise broke parameter types.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12161-test-enable-vitest-consistent-each-for-and-migrate-each-for-35e6d73d3650810c9417e07bdd9f27a2)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
|
||
|
|
8340d7655f |
refactor: extract auth-routing from workspaceApi to auth domain (#10484)
## Summary Extract auth-routing logic (`getAuthHeaderOrThrow`, `getFirebaseAuthHeaderOrThrow`) from `workspaceApi.ts` into `authStore.ts`, eliminating a layering violation where the workspace API re-implemented auth header resolution. ## Changes - **What**: Moved `getAuthHeaderOrThrow` and `getFirebaseAuthHeaderOrThrow` from `workspaceApi.ts` to `authStore.ts`. `workspaceApi.ts` now calls through `useAuthStore()` instead of re-implementing token resolution. Added tests for the new methods in `authStore.test.ts`. Updated `authStoreMock.ts` with the new methods. - **Files**: 4 files changed ## Review Focus - The `getAuthHeaderOrThrow` / `getFirebaseAuthHeaderOrThrow` methods throw `AuthStoreError` (auth domain error) — callers in workspace can catch and re-wrap if needed - `workspaceApi.ts` is simplified by ~19 lines ## Stack PR 2/5: #10483 → **→ This PR** → #10485 → #10486 → #10487 |
||
|
|
62979e3818 |
refactor: rename firebaseAuthStore to authStore with shared test fixtures (#10483)
## Summary Rename `useFirebaseAuthStore` → `useAuthStore` and `FirebaseAuthStoreError` → `AuthStoreError`. Introduce shared mock factory (`authStoreMock.ts`) to replace 16 independent bespoke mocks. ## Changes - **What**: Mechanical rename of store, composable, class, and store ID (`firebaseAuth` → `auth`). Created `src/stores/__tests__/authStoreMock.ts` — a shared mock factory with reactive controls, used by all consuming test files. Migrated all 16 test files from ad-hoc mocks to the shared factory. - **Files**: 62 files changed (rename propagation + new test infra) ## Review Focus - Mock factory API design in `authStoreMock.ts` — covers all store properties with reactive `controls` for per-test customization - Self-test in `authStoreMock.test.ts` validates computed reactivity Fixes #8219 ## Stack This is PR 1/5 in a stacked refactoring series: 1. **→ This PR**: Rename + shared test fixtures 2. #10484: Extract auth-routing from workspaceApi 3. #10485: Auth token priority tests 4. #10486: Decompose MembersPanelContent 5. #10487: Consolidate SubscriptionTier type --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |