| 339 | // url is required by Auth.js endpoint validation; the request function overrides the actual fetch |
| 340 | url: `${baseUrl}/plugins/servlet/applinks/whoami`, |
| 341 | async request({ tokens }: { tokens: TokenSet }) { |
| 342 | const accessToken = tokens.access_token; |
| 343 | if (!accessToken) { |
| 344 | throw new Error("Missing access token for Bitbucket Server userinfo request"); |
| 345 | } |
| 346 | |
| 347 | const whoamiRes = await fetch(`${baseUrl}/plugins/servlet/applinks/whoami`, { |
| 348 | headers: { Authorization: `Bearer ${accessToken}` }, |
| 349 | signal: AbortSignal.timeout(10_000), |
| 350 | }); |
| 351 | if (!whoamiRes.ok) { |
| 352 | throw new Error(`Bitbucket whoami failed (${whoamiRes.status})`); |
| 353 | } |
| 354 | |
| 355 | const username = (await whoamiRes.text()).trim(); |
| 356 | if (!username) { |
| 357 | throw new Error("Bitbucket whoami returned an empty username"); |
| 358 | } |
| 359 | |
| 360 | const profileRes = await fetch(`${baseUrl}/rest/api/1.0/users/${encodeURIComponent(username)}`, { |
| 361 | headers: { Authorization: `Bearer ${accessToken}` }, |
| 362 | signal: AbortSignal.timeout(10_000), |
| 363 | }); |
| 364 | if (!profileRes.ok) { |
| 365 | throw new Error(`Bitbucket profile lookup failed (${profileRes.status})`); |
| 366 | } |
| 367 | |
| 368 | return await profileRes.json(); |
| 369 | } |
| 370 | }, |
| 371 | profile(profile) { |
| 372 | return { |