mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
fix: prevent OAuth callback race condition between startup handler and OAuthCallback component (#1671)
This commit is contained in:
parent
014437f478
commit
325fb791f6
@ -5,6 +5,7 @@ import React, { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { StackClientApp } from "../lib/hexclave-app/apps/interfaces/client-app";
|
||||
import { hexclaveAppInternalsSymbol } from "../lib/hexclave-app/common";
|
||||
import { TranslationProviderClient } from "../providers/translation-provider-client";
|
||||
import { OAuthCallback } from "./oauth-callback";
|
||||
|
||||
@ -40,6 +41,9 @@ function createAppTestDouble(options: {
|
||||
callOAuthCallback: options.callOAuthCallback,
|
||||
redirectToSignIn: vi.fn(async () => {}),
|
||||
redirectToHome: vi.fn(async () => {}),
|
||||
[hexclaveAppInternalsSymbol]: {
|
||||
awaitPendingAuthResolutions: vi.fn(async () => {}),
|
||||
},
|
||||
};
|
||||
|
||||
// This test double intentionally implements only the StackClientApp surface
|
||||
|
||||
@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useStackApp } from "..";
|
||||
import { MaybeFullPage } from "../components/elements/maybe-full-page";
|
||||
import { StyledLink } from "../components/link";
|
||||
import { hexclaveAppInternalsSymbol } from "../lib/hexclave-app/common";
|
||||
import { useTranslation } from "../lib/translations";
|
||||
import { ErrorPage } from "./error-page";
|
||||
|
||||
@ -22,6 +23,11 @@ export function OAuthCallback({ fullPage }: { fullPage?: boolean }) {
|
||||
if (called.current) return;
|
||||
called.current = true;
|
||||
try {
|
||||
// The startup handler in StackClientApp's constructor may have already consumed the
|
||||
// one-time OAuth params (code + state cookie) via a microtask that fires before this
|
||||
// macrotask-scheduled useEffect. Await its completion so we don't race: if it succeeds
|
||||
// it will redirect and this page tears down; if it fails we fall through below.
|
||||
await app[hexclaveAppInternalsSymbol].awaitPendingAuthResolutions();
|
||||
const hasRedirected = await app.callOAuthCallback();
|
||||
if (!hasRedirected) {
|
||||
await app.redirectToSignIn({ noRedirectBack: true });
|
||||
|
||||
@ -4204,6 +4204,9 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
||||
signInWithTokens: async (tokens: { accessToken: string, refreshToken: string }) => {
|
||||
await this._signInToAccountWithTokens(tokens);
|
||||
},
|
||||
awaitPendingAuthResolutions: async () => {
|
||||
await this._awaitPendingAuthResolutions();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -136,6 +136,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
|
||||
redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,
|
||||
redirectToHandler(handlerName: keyof HandlerUrls, options?: RedirectToOptions): Promise<void>,
|
||||
signInWithTokens(tokens: { accessToken: string, refreshToken: string }): Promise<void>,
|
||||
awaitPendingAuthResolutions(): Promise<void>,
|
||||
},
|
||||
}
|
||||
& AsyncStoreProperty<"project", [], Project, false>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user