Skip to content

Commit 30a80db

Browse files
Refetch user session when it expires (#9632)
1 parent 7e34507 commit 30a80db

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

app/ide-desktop/lib/dashboard/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ function AppRouter(props: AppRouterProps) {
304304
}, [props, /* should never change */ navigate])
305305

306306
const userSession = authService?.cognito.userSession.bind(authService.cognito) ?? null
307+
const refreshUserSession =
308+
authService?.cognito.refreshUserSession.bind(authService.cognito) ?? null
307309
const registerAuthEventListener = authService?.registerAuthEventListener ?? null
308310
const initialBackend: Backend =
309311
isAuthenticationDisabled && projectManagerUrl != null && projectManagerRootDirectory != null
@@ -425,6 +427,13 @@ function AppRouter(props: AppRouterProps) {
425427
mainPageUrl={mainPageUrl}
426428
userSession={userSession}
427429
registerAuthEventListener={registerAuthEventListener}
430+
refreshUserSession={
431+
refreshUserSession
432+
? async () => {
433+
await refreshUserSession()
434+
}
435+
: null
436+
}
428437
>
429438
{result}
430439
</SessionProvider>

app/ide-desktop/lib/dashboard/src/authentication/cognito.mock.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,13 @@ export class Cognito {
273273
return results.Err(cognitoUserResult.val)
274274
}
275275
}
276+
277+
/**
278+
* Refresh the current user's session.
279+
*/
280+
async refreshUserSession() {
281+
return Promise.resolve(results.Ok(null))
282+
}
276283
}
277284

278285
// ===================

app/ide-desktop/lib/dashboard/src/authentication/cognito.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,27 @@ export class Cognito {
273273
return result.mapErr(intoAmplifyErrorOrThrow).mapErr(intoSignInWithPasswordErrorOrThrow)
274274
}
275275

276+
/**
277+
* Refresh the current user session.
278+
*/
279+
async refreshUserSession() {
280+
const result = await results.Result.wrapAsync(async () => {
281+
const currentUser = await currentAuthenticatedUser()
282+
const refreshToken = (await amplify.Auth.currentSession()).getRefreshToken()
283+
284+
await new Promise((resolve, reject) => {
285+
currentUser.unwrap().refreshSession(refreshToken, (error, session) => {
286+
if (error instanceof Error) {
287+
reject(error)
288+
} else {
289+
resolve(session)
290+
}
291+
})
292+
})
293+
})
294+
return result.mapErr(intoCurrentSessionErrorType)
295+
}
296+
276297
/** Sign out the current user. */
277298
async signOut() {
278299
// FIXME [NP]: https://github.com/enso-org/cloud-v2/issues/341

app/ide-desktop/lib/dashboard/src/providers/SessionProvider.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* currently authenticated user's session. */
33
import * as React from 'react'
44

5+
import * as reactQuery from '@tanstack/react-query'
6+
57
import * as asyncEffectHooks from '#/hooks/asyncEffectHooks'
68
import * as refreshHooks from '#/hooks/refreshHooks'
79

@@ -44,12 +46,17 @@ export interface SessionProviderProps {
4446
readonly mainPageUrl: URL
4547
readonly registerAuthEventListener: listen.ListenFunction | null
4648
readonly userSession: (() => Promise<cognito.UserSession | null>) | null
49+
readonly refreshUserSession: (() => Promise<void>) | null
4750
readonly children: React.ReactNode
4851
}
4952

53+
const FIVE_MINUTES_MS = 300_000
54+
const SIX_HOURS_MS = 21_600_000
55+
5056
/** A React provider for the session of the authenticated user. */
5157
export default function SessionProvider(props: SessionProviderProps) {
52-
const { mainPageUrl, children, userSession, registerAuthEventListener } = props
58+
const { mainPageUrl, children, userSession, registerAuthEventListener, refreshUserSession } =
59+
props
5360
const [refresh, doRefresh] = refreshHooks.useRefresh()
5461
const [initialized, setInitialized] = React.useState(false)
5562
const errorCallbacks = React.useRef(new Set<(error: Error) => void>())
@@ -89,6 +96,26 @@ export default function SessionProvider(props: SessionProviderProps) {
8996
[refresh]
9097
)
9198

99+
const timeUntilRefresh = session
100+
? // If the session has not expired, we should refresh it when it is 5 minutes from expiring.
101+
new Date(session.expireAt).getTime() - Date.now() - FIVE_MINUTES_MS
102+
: Infinity
103+
104+
reactQuery.useQuery({
105+
queryKey: ['userSession'],
106+
queryFn: refreshUserSession
107+
? () =>
108+
refreshUserSession()
109+
.then(() => {
110+
doRefresh()
111+
})
112+
.then(() => null)
113+
: reactQuery.skipToken,
114+
refetchOnWindowFocus: true,
115+
refetchIntervalInBackground: true,
116+
refetchInterval: timeUntilRefresh < SIX_HOURS_MS ? timeUntilRefresh : SIX_HOURS_MS,
117+
})
118+
92119
// Register an effect that will listen for authentication events. When the event occurs, we
93120
// will refresh or clear the user's session, forcing a re-render of the page with the new
94121
// session.

0 commit comments

Comments
 (0)