(req: Request)
| 300 | * `enrichUserWithMembership` resolves real membership via the DB. |
| 301 | */ |
| 302 | export async function validateWorkOSBearerJWT(req: Request): Promise<ValidatedBearerJWT | null> { |
| 303 | const authHeader = req.headers.authorization; |
| 304 | if (!authHeader?.startsWith('Bearer ')) return null; |
| 305 | const token = authHeader.slice(7); |
| 306 | |
| 307 | if (isWorkOSApiKeyFormat(token)) return null; // handled by validateWorkOSApiKey |
| 308 | if (ADMIN_API_KEY && token === ADMIN_API_KEY) return null; // handled by hasValidAdminApiKey |
| 309 | if (!looksLikeJWT(token)) return null; |
| 310 | |
| 311 | const cacheKey = hashBearerToken(token); |
| 312 | const now = Date.now(); |
| 313 | const cached = bearerJwtCache.get(cacheKey); |
| 314 | if (cached && cached.expiresAt > now) { |
| 315 | return { user: cached.user, rawToken: token, orgId: cached.orgId }; |
| 316 | } |
| 317 | |
| 318 | let verified: Awaited<ReturnType<typeof verifyWorkOSJWT>>; |
| 319 | try { |
| 320 | verified = await verifyWorkOSJWT(token); |
| 321 | } catch (err) { |
| 322 | logger.debug({ err }, 'Bearer JWT verification failed'); |
| 323 | return null; |
| 324 | } |
| 325 | |
| 326 | if (verified.isM2M || !verified.sub) return null; |
| 327 | |
| 328 | // Confirm the subject corresponds to a real local user. This catches |
| 329 | // tokens from WorkOS accounts that have been deleted or never synced, |
| 330 | // and gives us names for the synthesized WorkOSUser. |
| 331 | const pool = getPool(); |
| 332 | const localUser = await pool.query<{ |
| 333 | first_name: string | null; |
| 334 | last_name: string | null; |
| 335 | email: string | null; |
| 336 | }>( |
| 337 | `SELECT first_name, last_name, email FROM users WHERE workos_user_id = $1`, |
| 338 | [verified.sub], |
| 339 | ); |
| 340 | if (localUser.rowCount === 0) { |
| 341 | logger.warn({ sub: verified.sub }, 'Bearer JWT verified but user not found in local DB'); |
| 342 | return null; |
| 343 | } |
| 344 | |
| 345 | const row = localUser.rows[0]; |
| 346 | const email = verified.email ?? row.email ?? ''; |
| 347 | const user: WorkOSUser = { |
| 348 | id: verified.sub, |
| 349 | email, |
| 350 | firstName: row.first_name?.trim() || undefined, |
| 351 | lastName: row.last_name?.trim() || undefined, |
| 352 | emailVerified: true, |
| 353 | createdAt: new Date().toISOString(), |
| 354 | updatedAt: new Date().toISOString(), |
| 355 | }; |
| 356 | |
| 357 | const tokenExpMs = verified.expiresAt ? verified.expiresAt * 1000 : Infinity; |
| 358 | const cacheUntil = Math.min(now + BEARER_JWT_CACHE_TTL_MS, tokenExpMs); |
| 359 | if (cacheUntil > now) { |
no test coverage detected