({ token, user: _user })
| 347 | return baseUrl; |
| 348 | }, |
| 349 | async jwt({ token, user: _user }) { |
| 350 | const user = _user as User | undefined; |
| 351 | // @note: `user` will be available on signUp or signIn triggers. |
| 352 | // Cache the userId in the JWT for later use. |
| 353 | if (user) { |
| 354 | token.userId = user.id; |
| 355 | token.sessionVersion = user.sessionVersion ?? 0; |
| 356 | } |
| 357 | |
| 358 | if (token.userId) { |
| 359 | // Single query: fetch the user's current sessionVersion for |
| 360 | // the cross-check below, plus any accounts that still need |
| 361 | // the issuerUrl lazy migration. |
| 362 | // |
| 363 | // @see https://github.com/sourcebot-dev/sourcebot/pull/993 |
| 364 | const dbUser = await __unsafePrisma.user.findUnique({ |
| 365 | where: { |
| 366 | id: token.userId as string, |
| 367 | }, |
| 368 | select: { |
| 369 | sessionVersion: true, |
| 370 | accounts: { |
| 371 | where: { |
| 372 | issuerUrl: null, |
| 373 | }, |
| 374 | }, |
| 375 | }, |
| 376 | }); |
| 377 | |
| 378 | // The user row was removed (e.g., deleted via /api/ee/user |
| 379 | // or org-removal cascade). Treat the JWT as invalid so |
| 380 | // /api/auth/session reports logged-out and @auth/core clears |
| 381 | // the cookie from the browser. |
| 382 | if (!dbUser) { |
| 383 | return null; |
| 384 | } |
| 385 | |
| 386 | // On every non-login request, cross-check the JWT's |
| 387 | // sessionVersion against the user's current sessionVersion in |
| 388 | // the database. A mismatch means the user signed out, was |
| 389 | // removed from the org, or their sessions were otherwise |
| 390 | // invalidated since the JWT was minted. Returning null here |
| 391 | // is what makes invalidation visible at /api/auth/session, |
| 392 | // not just at withAuth-gated endpoints. |
| 393 | const tokenSessionVersion = token.sessionVersion ?? 0; |
| 394 | if (!user && tokenSessionVersion !== dbUser.sessionVersion) { |
| 395 | return null; |
| 396 | } |
| 397 | |
| 398 | // Lazy migration of issuerUrl on accounts created before |
| 399 | // the column was introduced in v4.15.4. The where clause |
| 400 | // above scopes this to only accounts that still need it, |
| 401 | // so the loop is a no-op once everyone is backfilled. |
| 402 | for (const account of dbUser.accounts) { |
| 403 | const issuerUrl = await getIssuerUrlForProviderId(account.providerId); |
| 404 | if (issuerUrl) { |
| 405 | await __unsafePrisma.account.update({ |
| 406 | where: { |
nothing calls this directly
no test coverage detected