( requestId: string, credential: any, credentialId: string )
| 589 | * Enhanced version that returns additional information about the refresh operation |
| 590 | */ |
| 591 | export async function refreshTokenIfNeeded( |
| 592 | requestId: string, |
| 593 | credential: any, |
| 594 | credentialId: string |
| 595 | ): Promise<{ accessToken: string; refreshed: boolean }> { |
| 596 | const resolvedCredentialId = credential.resolvedCredentialId ?? credentialId |
| 597 | |
| 598 | // Decide if we should refresh: token missing OR expired |
| 599 | const accessTokenExpiresAt = credential.accessTokenExpiresAt |
| 600 | const refreshTokenExpiresAt = credential.refreshTokenExpiresAt |
| 601 | const now = new Date() |
| 602 | |
| 603 | // Check if access token needs refresh (missing or expired) |
| 604 | const accessTokenNeedsRefresh = |
| 605 | !!credential.refreshToken && |
| 606 | (!credential.accessToken || (accessTokenExpiresAt && accessTokenExpiresAt <= now)) |
| 607 | |
| 608 | // Check if we should proactively refresh to prevent refresh token expiry |
| 609 | // This applies to Microsoft providers whose refresh tokens expire after 90 days of inactivity |
| 610 | const proactiveRefreshThreshold = new Date( |
| 611 | now.getTime() + PROACTIVE_REFRESH_THRESHOLD_DAYS * 24 * 60 * 60 * 1000 |
| 612 | ) |
| 613 | const refreshTokenNeedsProactiveRefresh = |
| 614 | !!credential.refreshToken && |
| 615 | isMicrosoftProvider(credential.providerId) && |
| 616 | refreshTokenExpiresAt && |
| 617 | refreshTokenExpiresAt <= proactiveRefreshThreshold |
| 618 | |
| 619 | const shouldRefresh = accessTokenNeedsRefresh || refreshTokenNeedsProactiveRefresh |
| 620 | |
| 621 | // If token appears valid and present, return it directly |
| 622 | if (!shouldRefresh) { |
| 623 | logger.info(`[${requestId}] Access token is valid`) |
| 624 | return { accessToken: credential.accessToken, refreshed: false } |
| 625 | } |
| 626 | |
| 627 | const fresh = await performCoalescedRefresh({ |
| 628 | accountId: resolvedCredentialId, |
| 629 | providerId: credential.providerId, |
| 630 | refreshToken: credential.refreshToken!, |
| 631 | requestId, |
| 632 | userId: credential.userId, |
| 633 | }) |
| 634 | if (fresh) return { accessToken: fresh, refreshed: true } |
| 635 | |
| 636 | if (!accessTokenNeedsRefresh && credential.accessToken) { |
| 637 | logger.info(`[${requestId}] Refresh unavailable; reusing still-valid access token`) |
| 638 | return { accessToken: credential.accessToken, refreshed: false } |
| 639 | } |
| 640 | throw new Error('Failed to refresh token') |
| 641 | } |
| 642 | |
| 643 | export interface CredentialSetCredential { |
| 644 | userId: string |
no test coverage detected