feat: add auth refresh token flow if token expires (#5490)

This commit is contained in:
Nivedin 2025-10-26 22:24:59 +05:30 committed by GitHub
parent 795cc820db
commit 68d1db7e74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 18 deletions

View file

@ -96,11 +96,20 @@ const createHoppClient = () => {
willAuthError() {
return platform.auth.willBackendHaveAuthError()
},
didAuthError() {
return false
didAuthError(error) {
// Check for specific error patterns that indicate expired token
return error.graphQLErrors.some(
(e) =>
e.message.includes("auth/fail") ||
e.message.includes("jwt expired") ||
e.extensions?.code === "UNAUTHENTICATED"
)
},
async refreshAuth() {
// TODO
const refresh = platform.auth.refreshAuthToken
// should we logout if refreshAuthToken is not defined?
if (!refresh) return
await refresh()
},
}
}),

View file

@ -7,8 +7,28 @@ export type ValidUserResponse = {
export const SESSION_EXPIRED = "Session expired. Please log in again."
/**
* Attempts to refresh the authentication token
* @returns Promise resolving to a ValidUserResponse with the result
*/
const attemptTokenRefresh = async (): Promise<ValidUserResponse> => {
if (!platform.auth.refreshAuthToken)
return { valid: false, error: SESSION_EXPIRED }
try {
const refreshSuccessful = await platform.auth.refreshAuthToken()
return {
valid: refreshSuccessful,
error: refreshSuccessful ? "" : SESSION_EXPIRED,
}
} catch {
return { valid: false, error: SESSION_EXPIRED }
}
}
/**
* Validates user authentication and token validity by making an API call.
* Refreshes tokens if they are expired.
*
* This function is kept separate from `handleTokenValidation()` to enable different use cases:
* - Silent validation for conditional UI states (e.g., disabling components on token expiration)
@ -23,22 +43,26 @@ export const SESSION_EXPIRED = "Session expired. Please log in again."
export const isValidUser = async (): Promise<ValidUserResponse> => {
const user = platform.auth.getCurrentUser()
if (user) {
try {
// If the platform provides a method to verify auth tokens, use it else assume tokens are valid (for central instance where firebase handles it)
const hasValidTokens = platform.auth.verifyAuthTokens
? await platform.auth.verifyAuthTokens()
: true
// If no user is logged in, consider it valid (allows public actions)
if (!user) return { valid: true, error: "" }
return {
valid: hasValidTokens,
error: hasValidTokens ? "" : SESSION_EXPIRED,
try {
// If the platform provides a method to verify auth tokens, use it
if (platform.auth.verifyAuthTokens) {
const hasValidTokens = await platform.auth.verifyAuthTokens()
if (hasValidTokens) {
return { valid: true, error: "" }
}
} catch (error) {
return { valid: false, error: SESSION_EXPIRED }
}
}
// allow user to perform actions without being logged in
return { valid: true, error: "" }
// Try token refresh if verification failed
return attemptTokenRefresh()
}
// For platforms without token verification capability
return { valid: true, error: "" }
} catch (error) {
// Handle errors from token verification
return attemptTokenRefresh()
}
}

View file

@ -281,4 +281,10 @@ export type AuthPlatformDef = {
* @returns True if tokens are valid, false otherwise
*/
verifyAuthTokens?: () => Promise<boolean>
/** Refreshes the authentication tokens for the current user
* For self-hosted, this should refresh the tokens with the backend
* @returns True if tokens were refreshed successfully, false otherwise
*/
refreshAuthToken?: () => Promise<boolean>
}

View file

@ -395,6 +395,10 @@ export const def: AuthPlatformDef = {
})
},
async refreshAuthToken() {
return refreshToken()
},
/**
* Verifies if the current user's authentication tokens are valid
* @returns True if tokens are valid, false otherwise

View file

@ -527,6 +527,11 @@ export const def: AuthPlatformDef = {
})
},
async refreshAuthToken() {
const refreshed = await refreshToken()
return refreshed ?? false
},
/**
* Verifies if the current user's authentication tokens are valid
* @returns True if tokens are valid, false otherwise

View file

@ -353,6 +353,10 @@ export const def: AuthPlatformDef = {
})
},
async refreshAuthToken() {
return refreshToken()
},
async processMagicLink() {
if (this.isSignInWithEmailLink(window.location.href)) {
const deviceIdentifier =