feat: add auth refresh token flow if token expires (#5490)
This commit is contained in:
parent
795cc820db
commit
68d1db7e74
6 changed files with 70 additions and 18 deletions
|
|
@ -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()
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -353,6 +353,10 @@ export const def: AuthPlatformDef = {
|
|||
})
|
||||
},
|
||||
|
||||
async refreshAuthToken() {
|
||||
return refreshToken()
|
||||
},
|
||||
|
||||
async processMagicLink() {
|
||||
if (this.isSignInWithEmailLink(window.location.href)) {
|
||||
const deviceIdentifier =
|
||||
|
|
|
|||
Loading…
Reference in a new issue