Performance insights, Goals & Events, 2FA improvements, auth fixes #36

Merged
uz1mani merged 12 commits from staging into main 2026-02-25 19:41:07 +00:00
8 changed files with 64 additions and 66 deletions
Showing only changes of commit 3cb5416251 - Show all commits

View File

@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Fixed
- **Sign in after inactivity.** Clicking "Sign in" after a period of inactivity no longer does nothing. Previously, stale refresh cookies caused the middleware to redirect away from the login page; now only a valid access token triggers that redirect, so you can complete OAuth sign-in when your session has expired.
- **Frequent re-login.** You no longer have to sign in multiple times a day. When the access token expires after 15 minutes of inactivity, the app now automatically refreshes it using your refresh token on the next page load, so you stay logged in for up to 30 days.
- **2FA disable now requires password confirmation.** Disabling 2FA sends the derived password to the backend for verification. This prevents an attacker with a hijacked session from stripping 2FA.
## [0.11.1-alpha] - 2026-02-23

View File

@@ -110,8 +110,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
const init = async () => {
// * 1. Check server-side session (cookies)
const session = await getSessionAction()
let session = await getSessionAction()
// * 2. If no access_token but refresh_token may exist, try refresh (fixes 15-min inactivity logout)
if (!session && typeof window !== 'undefined') {
const refreshRes = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
})
if (refreshRes.ok) {
session = await getSessionAction()
}
}
if (session) {
greptile-apps[bot] commented 2026-02-25 19:43:19 +00:00 (Migrated from github.com)
Review

Silent refresh failure may hide errors

When the refresh request fails (non-ok response or network error), the code silently falls through to the else branch which sets user to null. This is functionally correct, but a fetch exception (e.g., network failure) will propagate as an unhandled error from the init function, which could cause the loading state to never be set to false, leaving the app in a permanent loading state.

Consider wrapping the refresh attempt in a try/catch:

        if (!session && typeof window !== 'undefined') {
          try {
            const refreshRes = await fetch('/api/auth/refresh', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              credentials: 'include',
            })
            if (refreshRes.ok) {
              session = await getSessionAction()
            }
          } catch {
            // Refresh failed (network error) — fall through to no-session handling
          }
        }
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/auth/context.tsx
Line: 116-125

Comment:
**Silent refresh failure may hide errors**

When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state.

Consider wrapping the refresh attempt in a try/catch:

```suggestion
        if (!session && typeof window !== 'undefined') {
          try {
            const refreshRes = await fetch('/api/auth/refresh', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              credentials: 'include',
            })
            if (refreshRes.ok) {
              session = await getSessionAction()
            }
          } catch {
            // Refresh failed (network error) — fall through to no-session handling
          }
        }
```

How can I resolve this? If you propose a fix, please make it concise.
**Silent refresh failure may hide errors** When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state. Consider wrapping the refresh attempt in a try/catch: ```suggestion if (!session && typeof window !== 'undefined') { try { const refreshRes = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', }) if (refreshRes.ok) { session = await getSessionAction() } } catch { // Refresh failed (network error) — fall through to no-session handling } } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/auth/context.tsx Line: 116-125 Comment: **Silent refresh failure may hide errors** When the refresh request fails (non-ok response or network error), the code silently falls through to the `else` branch which sets `user` to `null`. This is functionally correct, but a `fetch` exception (e.g., network failure) will propagate as an unhandled error from the `init` function, which could cause the `loading` state to never be set to `false`, leaving the app in a permanent loading state. Consider wrapping the refresh attempt in a try/catch: ```suggestion if (!session && typeof window !== 'undefined') { try { const refreshRes = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', }) if (refreshRes.ok) { session = await getSessionAction() } } catch { // Refresh failed (network error) — fall through to no-session handling } } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
setUser(session)
localStorage.setItem('user', JSON.stringify(session))
@@ -129,7 +141,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
localStorage.removeItem('user')
setUser(null)
}
// * Clear legacy tokens if they exist (migration)
if (localStorage.getItem('token')) {
localStorage.removeItem('token')