Skip to content

feat(auth): add token refresh handling - #64

Merged
kelleyglenn merged 6 commits into
masterfrom
feature/3-token-refresh
Feb 24, 2026
Merged

feat(auth): add token refresh handling#64
kelleyglenn merged 6 commits into
masterfrom
feature/3-token-refresh

Conversation

@kelleyglenn

Copy link
Copy Markdown
Owner

Summary

  • Stores refresh token in sessionStorage alongside access token
  • Adds proactive refresh timer (fires at 80% of token lifetime)
  • Adds axios 401 interceptor with request queue as fallback
  • Adds refreshTokens() API function and RefreshResponse type
  • Silent redirect to login on refresh failure

Closes #3

Test plan

  • Unit tests for refreshTokens API function
  • Unit tests for 401 interceptor (queue behavior, callback registration, /auth/refresh exclusion)
  • Unit tests for AuthProvider (stores refresh token, registers callbacks, session restore, timer scheduling)
  • All 398 tests pass with coverage thresholds met
  • Manual test: login → wait 15+ min → verify still logged in

🤖 Generated with Claude Code

kelleyglenn and others added 6 commits February 22, 2026 23:46
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive tests for axios 401 interceptor (queue behavior,
refresh callback, clearAuth on failure, /auth/refresh exclusion) and
AuthProvider (performRefresh, session restore with expired tokens,
schedule refresh timer). Coverage now passes all thresholds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d lifetime (#3)

- Store actual token lifetime in sessionStorage instead of hardcoding 900s
- Split scheduleRefresh into two functions to compute correct delay on restore
- Add isRefreshingRef guard to prevent concurrent refresh requests
- Fix minor type issue (unknown | null -> unknown) in client.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- fetchCurrentUser catch block now calls clearAuth() instead of partial cleanup
- performRefresh returns in-flight promise so concurrent callers await the same refresh
- Replace return Promise.reject() with throw in interceptor (typescript:S7746)
- Replace parseInt with Number.parseInt (typescript:S7773)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage after merging feature/3-token-refresh into master will be

76.03%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src/app/login
   page.tsx0%0%0%0%11, 114, 14–16, 16, 16, 18, 18, 18, 18, 18, 21–25, 28, 28–29, 3, 30, 33–36, 38–39, 4, 40, 42, 42, 42, 42, 42–44, 44, 44, 46, 49, 5–6, 61, 7, 75, 8, 86, 9
src/app/map
   page.tsx0%0%0%0%11, 23–26, 28, 28, 28–29, 29, 29, 3, 32, 32, 32, 32, 32, 36, 36, 4, 47, 5–6
src/app/profile
   page.tsx96.15%81.82%100%100%114, 129
src/app/register
   page.tsx0%0%0%0%101, 11, 112, 13–21, 24, 24–26, 29, 3, 30–31, 33, 33–35, 38, 38–39, 4, 40, 43, 45–47, 49, 49, 49, 49, 49, 5, 50–51, 51, 51, 53, 56, 6, 68, 7, 79, 8–9, 90
src/app/users/[id]
   page.tsx96.88%94.44%100%100%10
src/app/videos/[id]
   page.tsx0%0%0%0%11–13, 13, 13, 3–4, 7
src/app/videos/new
   page.tsx0%0%0%0%10–11, 16–18, 20–21, 21, 21, 21–22, 26, 26, 3, 34, 34, 4–6
src/components
   HeroMap.tsx100%100%100%100%
   NavBar.tsx100%100%100%100%
src/components/map
   AmendmentFilter.tsx100%100%100%100%
   BottomSheet.tsx0%0%0%0%108, 111, 20–21, 24, 3, 30–32, 36–37, 4, 41, 43–44, 44, 46, 48, 5, 50, 50, 50, 52, 56, 60–61, 61, 61, 63–66, 66, 66, 68–69, 69, 69, 73–74, 74, 74, 78, 8, 81, 85, 97
   ClusterMarker.tsx100%100%100%100%
   FilterBar.tsx97.30%95.24%100%100%96
   LocationSearch.tsx0%0%0%0%27–29, 3, 31, 33, 33–37, 39, 39, 39, 39, 4, 43–45, 5, 51, 51–52, 6
   MapContainer.tsx0%0%0%0%10, 101, 101, 103, 107, 107, 107, 11, 115, 12, 127, 127, 13, 134, 134, 137, 14, 146, 146, 15, 152, 16–17, 179, 179, 18, 181, 185, 185, 187, 191, 191, 191, 199, 21–24, 27, 3, 33, 39, 4, 42–43, 43–44, 46, 46, 46, 46, 46, 46, 46, 46, 46, 5, 58–59, 59, 6, 60, 62, 62, 62, 62, 62, 7, 70–71, 71, 71, 71–72, 72, 72, 75–76, 8, 80, 80, 89, 89, 9, 95, 95, 97
   MapView.tsx0%0%0%0%11, 17–18, 27, 3, 30–32, 32, 34–35, 35, 37, 43, 47, 49, 59–60, 64–65, 69, 7, 70, 70, 70, 70, 72, 74, 74, 78, 8, 82–83, 83, 83, 83, 85, 9, 90, 93, 93
   ParticipantFilter.tsx100%100%100%100%
   SidePanel.tsx0%0%0%0%15–17, 20–21, 21, 21, 21, 23, 27, 27–28, 3–4, 40, 42, 42, 5, 53, 53, 56, 6, 67, 67, 86
   VideoInfoCard.tsx95.74%83.33%100%100%67, 75
   VideoListItem.tsx96.36%90.48%100%100%40, 48
   VideoMarker.tsx97.22%92.86%100%100%37
src/components/profile
   AvatarPicker.tsx86.67%66.67%80%93.10%115, 91, 94, 94
   MySubmissions.tsx100%100%100%100%
   PrivacySettingsForm.tsx79.17%25%75%93.75%47, 62, 64, 64
   ProfileInfoForm.tsx100%100%100%100%
   SocialLinksForm.tsx100%100%100%100%
src/components/ui
   Button.tsx100%100%100%100%
   Card.tsx100%100%100%100%
   Chip.tsx100%100%100%100%
   Input.tsx100%100%100%100%
   Toast.tsx100%100%100%100%
src/components/video
   LocationPicker.tsx76.19%66.67%83.33%77.78%174, 56–57, 77–78, 81, 93, 93–99
   MiniMap.tsx100%100%100%100%
   ModerationControls.tsx100%100%100%100%
   VideoDetail.tsx84%75%83.33%95%156, 160, 163, 176, 178, 178, 212, 34, 58, 68, 70, 73, 77, 82
   VideoSubmitForm.tsx85.49%75.36%84%90.06%101, 104, 106, 108, 147, 151, 164, 185, 188, 243, 245–246, 250, 252, 252, 252–254, 254–255, 257, 262, 262, 262, 268, 290, 290–292, 361, 71–72, 86
   YouTubePreview.tsx100%100%100%100%
src/config
   mapbox.ts100%100%100%100%
src/hooks
   useLocationClusters.ts100%100%100%100%
   useResponsive.ts100%100%100%100%
   useVideoSearch.ts92.31%80%100%100%30
src/lib/api
   auth.ts81.25%100%75%83.33%25–26
   client.ts100%100%100%100%
   

@sonarqubecloud

Copy link
Copy Markdown

@kelleyglenn
kelleyglenn merged commit ed804d9 into master Feb 24, 2026
3 checks passed
@kelleyglenn
kelleyglenn deleted the feature/3-token-refresh branch February 24, 2026 04:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement refresh token handling

1 participant