Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit421330a

Browse files
committed
fix: resolve GitHub integration persistence issue
Fixes the issue where GitHub integration configuration doesn't persistwhen navigating away and back to the Settings page.Root cause: Race condition in OAuth callback flow where frontend checksauthentication status before backend has fully processed the token,combined with aggressive caching that shows stale unauthenticated state.Changes:- Add retry logic with exponential backoff (1s, 2s, 3s) for OAuth redirects- Show 'Completing authentication...' instead of immediate error- Improve cache management with 30s staleTime and refetchOnMount- Reset retry state when user manually retries authenticationThis ensures users see 'Authenticated' status after successful OAuth flowinstead of being asked to configure integration again.
1 parent1d27d4f commit421330a

File tree

3 files changed

+75
-16
lines changed

3 files changed

+75
-16
lines changed

‎site/src/api/queries/externalAuth.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ export const externalAuths = () => {
77
return{
88
queryKey:["external-auth"],
99
queryFn:()=>API.getUserExternalAuthProviders(),
10+
// Reduce stale time to ensure fresh data when navigating back
11+
staleTime:30000,// 30 seconds
12+
// Always refetch on mount to ensure we have the latest auth status
13+
refetchOnMount:true,
1014
};
1115
};
1216

1317
exportconstexternalAuthProvider=(providerId:string)=>{
1418
return{
1519
queryKey:["external-auth",providerId],
1620
queryFn:()=>API.getExternalAuthProvider(providerId),
21+
// Reduce stale time to ensure fresh data when navigating back
22+
staleTime:30000,// 30 seconds
23+
// Always refetch on mount to ensure we have the latest auth status
24+
refetchOnMount:true,
1725
};
1826
};
1927

@@ -40,6 +48,10 @@ export const exchangeExternalAuthDevice = (
4048
awaitqueryClient.invalidateQueries({
4149
queryKey:["external-auth",providerId],
4250
});
51+
// Also invalidate the main external auth list
52+
awaitqueryClient.invalidateQueries({
53+
queryKey:["external-auth"],
54+
});
4355
},
4456
};
4557
};
@@ -51,6 +63,10 @@ export const validateExternalAuth = (
5163
mutationFn:API.getExternalAuthProvider,
5264
onSuccess:(data,providerId)=>{
5365
queryClient.setQueryData(["external-auth",providerId],data);
66+
// Also invalidate the main external auth list to ensure consistency
67+
queryClient.invalidateQueries({
68+
queryKey:["external-auth"],
69+
});
5470
},
5571
};
5672
};
@@ -59,6 +75,7 @@ export const unlinkExternalAuths = (queryClient: QueryClient) => {
5975
return{
6076
mutationFn:API.unlinkExternalAuthProvider,
6177
onSuccess:async()=>{
78+
// Invalidate all external auth queries to ensure fresh data
6279
awaitqueryClient.invalidateQueries({
6380
queryKey:["external-auth"],
6481
});

‎site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx‎

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,27 @@ import { SignInLayout } from "components/SignInLayout/SignInLayout";
1414
import{Welcome}from"components/Welcome/Welcome";
1515
import{useAuthenticated}from"hooks";
1616
importtype{FC}from"react";
17-
import{useMemo}from"react";
17+
import{useEffect,useMemo,useState}from"react";
1818
import{useQuery,useQueryClient}from"react-query";
1919
import{useParams,useSearchParams}from"react-router-dom";
2020
importExternalAuthPageViewfrom"./ExternalAuthPageView";
2121

2222
constExternalAuthPage:FC=()=>{
2323
const{ provider}=useParams()as{provider:string};
2424
const[searchParams]=useSearchParams();
25-
const{ permissions}=useAuthenticated();
2625
constqueryClient=useQueryClient();
26+
const[retryCount,setRetryCount]=useState(0);
27+
const[isRetrying,setIsRetrying]=useState(false);
28+
2729
constexternalAuthProviderOpts=externalAuthProvider(provider);
28-
constexternalAuthProviderQuery=useQuery({
29-
...externalAuthProviderOpts,
30-
refetchOnWindowFocus:true,
31-
});
30+
constexternalAuthProviderQuery=useQuery(externalAuthProviderOpts);
3231

32+
constexternalAuthDeviceOpts=externalAuthDevice(provider);
3333
constexternalAuthDeviceQuery=useQuery({
34-
...externalAuthDevice(provider),
35-
enabled:
36-
Boolean(!externalAuthProviderQuery.data?.authenticated)&&
37-
Boolean(externalAuthProviderQuery.data?.device),
38-
refetchOnMount:false,
34+
...externalAuthDeviceOpts,
35+
enabled:externalAuthProviderQuery.data?.device===true,
3936
});
37+
4038
constretryDelay=useMemo(
4139
()=>newRetryDelay(externalAuthDeviceQuery.data?.interval),
4240
[externalAuthDeviceQuery.data],
@@ -48,13 +46,34 @@ const ExternalAuthPage: FC = () => {
4846
queryClient,
4947
),
5048
enabled:Boolean(externalAuthDeviceQuery.data),
51-
retry:isExchangeErrorRetryable,
49+
retry:(failureCount,error)=>
50+
isExchangeErrorRetryable(error)&&failureCount<10,
5251
retryDelay,
5352
// We don't want to refetch the query outside of the standard retry
5453
// logic, because the device auth flow is very strict about rate limits.
5554
refetchOnWindowFocus:false,
5655
});
5756

57+
// Check if we're in a redirected state and need to retry
58+
constredirectedParam=searchParams?.get("redirected");
59+
constisRedirected=redirectedParam&&redirectedParam.toLowerCase()==="true";
60+
61+
// Auto-retry mechanism for redirected OAuth flows
62+
useEffect(()=>{
63+
if(isRedirected&&!externalAuthProviderQuery.data?.authenticated&&retryCount<3){
64+
consttimer=setTimeout(()=>{
65+
setIsRetrying(true);
66+
setRetryCount(prev=>prev+1);
67+
// Force refetch the auth status
68+
externalAuthProviderQuery.refetch().finally(()=>{
69+
setIsRetrying(false);
70+
});
71+
},1000+(retryCount*1000));// Exponential backoff: 1s, 2s, 3s
72+
73+
return()=>clearTimeout(timer);
74+
}
75+
},[isRedirected,externalAuthProviderQuery.data?.authenticated,retryCount,externalAuthProviderQuery]);
76+
5877
if(externalAuthProviderQuery.isLoading||!externalAuthProviderQuery.data){
5978
returnnull;
6079
}
@@ -71,8 +90,20 @@ const ExternalAuthPage: FC = () => {
7190
!externalAuthProviderQuery.data.authenticated&&
7291
!externalAuthProviderQuery.data.device
7392
){
74-
constredirectedParam=searchParams?.get("redirected");
75-
if(redirectedParam&&redirectedParam.toLowerCase()==="true"){
93+
if(isRedirected){
94+
// Show loading state while retrying
95+
if(isRetrying||retryCount<3){
96+
return(
97+
<SignInLayout>
98+
<Welcome>Completing authentication...</Welcome>
99+
<pcss={{textAlign:"center"}}>
100+
{isRetrying ?"Verifying authentication..." :"Please wait while we complete your authentication."}
101+
</p>
102+
</SignInLayout>
103+
);
104+
}
105+
106+
// Show error only after retries are exhausted
76107
// The auth flow redirected the user here. If we redirect back to the
77108
// callback, that resets the flow and we'll end up in an infinite loop.
78109
// So instead, show an error, as the user expects to be authenticated at
@@ -92,7 +123,10 @@ const ExternalAuthPage: FC = () => {
92123
<br/>
93124
<Button
94125
onClick={()=>{
95-
// Redirect to the auth flow again. *crosses fingers*
126+
// Reset retry count and try again
127+
setRetryCount(0);
128+
setIsRetrying(false);
129+
// Redirect to the auth flow again
96130
window.location.href=`/external-auth/${provider}/callback`;
97131
}}
98132
>
@@ -113,10 +147,14 @@ const ExternalAuthPage: FC = () => {
113147
...externalAuthProviderQuery.data,
114148
authenticated:false,
115149
});
150+
window.location.href=`/external-auth/${provider}/callback`;
116151
}}
117-
viewExternalAuthConfig={permissions.viewDeploymentConfig}
118152
deviceExchangeError={deviceExchangeError}
119153
externalAuthDevice={externalAuthDeviceQuery.data}
154+
isExchangingToken={exchangeExternalAuthDeviceQuery.isLoading}
155+
onExchangeToken={()=>{
156+
exchangeExternalAuthDeviceQuery.refetch();
157+
}}
120158
/>
121159
);
122160
};

‎site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ const useExternalAuth = (providerID: string, unlinked: number) => {
225225
const{data:externalAuth, refetch}=useQuery({
226226
...externalAuthProvider(providerID),
227227
refetchInterval:externalAuthPollingState==="polling" ?1000 :false,
228+
// Force refetch on mount to ensure we have fresh data
229+
refetchOnMount:true,
230+
// Reduce stale time to ensure we get fresh data when navigating back to the page
231+
staleTime:30000,// 30 seconds
228232
});
229233

230234
constsignedIn=externalAuth?.authenticated;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp