kavachos 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5356,15 +5356,110 @@ function mergeScopes(defaults, extras) {
5356
5356
  return [...merged];
5357
5357
  }
5358
5358
 
5359
+ // src/auth/oauth/providers/atlassian.ts
5360
+ var AUTHORIZATION_URL2 = "https://auth.atlassian.com/authorize";
5361
+ var TOKEN_URL2 = "https://auth.atlassian.com/oauth/token";
5362
+ var USER_INFO_URL = "https://api.atlassian.com/me";
5363
+ var ATLASSIAN_AUDIENCE = "api.atlassian.com";
5364
+ var DEFAULT_ATLASSIAN_SCOPES = ["read:me"];
5365
+ function normalizeProfile(raw) {
5366
+ const data = raw;
5367
+ if (!data.account_id) {
5368
+ throw new Error("Atlassian user response missing required field: account_id");
5369
+ }
5370
+ return {
5371
+ id: data.account_id,
5372
+ email: data.email,
5373
+ name: data.name,
5374
+ avatar: data.picture,
5375
+ raw
5376
+ };
5377
+ }
5378
+ function createAtlassianProvider(config) {
5379
+ const scopes = mergeScopes2(DEFAULT_ATLASSIAN_SCOPES, config.scopes);
5380
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5381
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
5382
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5383
+ const params = new URLSearchParams({
5384
+ client_id: config.clientId,
5385
+ redirect_uri: effectiveRedirectUri,
5386
+ response_type: "code",
5387
+ scope: scopes.join(" "),
5388
+ state,
5389
+ code_challenge: codeChallenge,
5390
+ code_challenge_method: "S256",
5391
+ // Required: instructs Atlassian which API audience to issue tokens for.
5392
+ audience: ATLASSIAN_AUDIENCE,
5393
+ prompt: "consent"
5394
+ });
5395
+ return `${AUTHORIZATION_URL2}?${params.toString()}`;
5396
+ }
5397
+ async function exchangeCode(code, codeVerifier, redirectUri) {
5398
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5399
+ const response = await fetch(TOKEN_URL2, {
5400
+ method: "POST",
5401
+ headers: { "Content-Type": "application/json" },
5402
+ body: JSON.stringify({
5403
+ grant_type: "authorization_code",
5404
+ client_id: config.clientId,
5405
+ client_secret: config.clientSecret,
5406
+ code,
5407
+ code_verifier: codeVerifier,
5408
+ redirect_uri: effectiveRedirectUri
5409
+ })
5410
+ });
5411
+ if (!response.ok) {
5412
+ const text3 = await response.text();
5413
+ throw new Error(`Atlassian token exchange failed (${response.status}): ${text3}`);
5414
+ }
5415
+ const raw = await response.json();
5416
+ const data = raw;
5417
+ return {
5418
+ accessToken: data.access_token,
5419
+ refreshToken: data.refresh_token,
5420
+ expiresIn: data.expires_in,
5421
+ tokenType: data.token_type ?? "Bearer",
5422
+ raw
5423
+ };
5424
+ }
5425
+ async function getUserInfo(accessToken) {
5426
+ const response = await fetch(USER_INFO_URL, {
5427
+ headers: { Authorization: `Bearer ${accessToken}` }
5428
+ });
5429
+ if (!response.ok) {
5430
+ const text3 = await response.text();
5431
+ throw new Error(`Atlassian /me fetch failed (${response.status}): ${text3}`);
5432
+ }
5433
+ const raw = await response.json();
5434
+ return normalizeProfile(raw);
5435
+ }
5436
+ return {
5437
+ id: "atlassian",
5438
+ name: "Atlassian",
5439
+ authorizationUrl: AUTHORIZATION_URL2,
5440
+ tokenUrl: TOKEN_URL2,
5441
+ userInfoUrl: USER_INFO_URL,
5442
+ scopes,
5443
+ getAuthorizationUrl,
5444
+ exchangeCode,
5445
+ getUserInfo
5446
+ };
5447
+ }
5448
+ function mergeScopes2(defaults, extras) {
5449
+ if (!extras || extras.length === 0) return defaults;
5450
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5451
+ return [...merged];
5452
+ }
5453
+
5359
5454
  // src/auth/oauth/providers/discord.ts
5360
- var AUTHORIZATION_URL2 = "https://discord.com/api/oauth2/authorize";
5361
- var TOKEN_URL2 = "https://discord.com/api/oauth2/token";
5362
- var USER_INFO_URL = "https://discord.com/api/users/@me";
5455
+ var AUTHORIZATION_URL3 = "https://discord.com/api/oauth2/authorize";
5456
+ var TOKEN_URL3 = "https://discord.com/api/oauth2/token";
5457
+ var USER_INFO_URL2 = "https://discord.com/api/users/@me";
5363
5458
  var CDN_BASE = "https://cdn.discordapp.com";
5364
5459
  var DEFAULT_DISCORD_SCOPES = ["identify", "email"];
5365
5460
  var DEFAULT_SCOPES2 = DEFAULT_DISCORD_SCOPES;
5366
5461
  function createDiscordProvider(config) {
5367
- const scopes = mergeScopes2(DEFAULT_SCOPES2, config.scopes);
5462
+ const scopes = mergeScopes3(DEFAULT_SCOPES2, config.scopes);
5368
5463
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5369
5464
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5370
5465
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5377,7 +5472,7 @@ function createDiscordProvider(config) {
5377
5472
  code_challenge: codeChallenge,
5378
5473
  code_challenge_method: "S256"
5379
5474
  });
5380
- return `${AUTHORIZATION_URL2}?${params.toString()}`;
5475
+ return `${AUTHORIZATION_URL3}?${params.toString()}`;
5381
5476
  }
5382
5477
  async function exchangeCode(code, codeVerifier, redirectUri) {
5383
5478
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5389,7 +5484,7 @@ function createDiscordProvider(config) {
5389
5484
  code_verifier: codeVerifier,
5390
5485
  redirect_uri: effectiveRedirectUri
5391
5486
  });
5392
- const response = await fetch(TOKEN_URL2, {
5487
+ const response = await fetch(TOKEN_URL3, {
5393
5488
  method: "POST",
5394
5489
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5395
5490
  body: body.toString()
@@ -5409,7 +5504,7 @@ function createDiscordProvider(config) {
5409
5504
  };
5410
5505
  }
5411
5506
  async function getUserInfo(accessToken) {
5412
- const response = await fetch(USER_INFO_URL, {
5507
+ const response = await fetch(USER_INFO_URL2, {
5413
5508
  headers: { Authorization: `Bearer ${accessToken}` }
5414
5509
  });
5415
5510
  if (!response.ok) {
@@ -5437,15 +5532,33 @@ function createDiscordProvider(config) {
5437
5532
  return {
5438
5533
  id: "discord",
5439
5534
  name: "Discord",
5440
- authorizationUrl: AUTHORIZATION_URL2,
5441
- tokenUrl: TOKEN_URL2,
5442
- userInfoUrl: USER_INFO_URL,
5535
+ authorizationUrl: AUTHORIZATION_URL3,
5536
+ tokenUrl: TOKEN_URL3,
5537
+ userInfoUrl: USER_INFO_URL2,
5443
5538
  scopes,
5444
5539
  getAuthorizationUrl,
5445
5540
  exchangeCode,
5446
5541
  getUserInfo
5447
5542
  };
5448
5543
  }
5544
+ function normalizeProfile2(raw) {
5545
+ const data = raw;
5546
+ if (!data.id) {
5547
+ throw new Error("Discord user response missing required field: id");
5548
+ }
5549
+ if (!data.email) {
5550
+ throw new Error("Discord user response has no email. Ensure the `email` scope is granted.");
5551
+ }
5552
+ const avatar = buildAvatarUrl(data.id, data.avatar);
5553
+ const name = data.global_name ?? buildLegacyName(data.username, data.discriminator);
5554
+ return {
5555
+ id: data.id,
5556
+ email: data.email,
5557
+ name,
5558
+ avatar,
5559
+ raw
5560
+ };
5561
+ }
5449
5562
  function buildAvatarUrl(userId, avatarHash) {
5450
5563
  if (!avatarHash) return void 0;
5451
5564
  const ext = avatarHash.startsWith("a_") ? "gif" : "png";
@@ -5457,135 +5570,138 @@ function buildLegacyName(username, discriminator) {
5457
5570
  }
5458
5571
  return username;
5459
5572
  }
5460
- function mergeScopes2(defaults, extras) {
5573
+ function mergeScopes3(defaults, extras) {
5461
5574
  if (!extras || extras.length === 0) return defaults;
5462
5575
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5463
5576
  return [...merged];
5464
5577
  }
5465
5578
 
5466
- // src/auth/oauth/providers/github.ts
5467
- var AUTHORIZATION_URL3 = "https://github.com/login/oauth/authorize";
5468
- var TOKEN_URL3 = "https://github.com/login/oauth/access_token";
5469
- var USER_URL = "https://api.github.com/user";
5470
- var USER_EMAILS_URL = "https://api.github.com/user/emails";
5471
- var DEFAULT_SCOPES3 = ["user:email"];
5472
- function createGithubProvider(config) {
5473
- const scopes = mergeScopes3(DEFAULT_SCOPES3, config.scopes);
5579
+ // src/auth/oauth/providers/dropbox.ts
5580
+ var AUTHORIZATION_URL4 = "https://www.dropbox.com/oauth2/authorize";
5581
+ var TOKEN_URL4 = "https://api.dropboxapi.com/oauth2/token";
5582
+ var USER_INFO_URL3 = "https://api.dropboxapi.com/2/users/get_current_account";
5583
+ var DEFAULT_DROPBOX_SCOPES = ["account_info.read"];
5584
+ function normalizeProfile3(raw) {
5585
+ const data = raw;
5586
+ if (!data.account_id) {
5587
+ throw new Error("Dropbox user response missing required field: account_id");
5588
+ }
5589
+ if (!data.email) {
5590
+ throw new Error("Dropbox user response missing required field: email");
5591
+ }
5592
+ return {
5593
+ id: data.account_id,
5594
+ email: data.email,
5595
+ name: data.name?.display_name,
5596
+ avatar: data.profile_photo_url ?? data.profile_photo?.url,
5597
+ raw
5598
+ };
5599
+ }
5600
+ function createDropboxProvider(config) {
5601
+ const scopes = mergeScopes4(DEFAULT_DROPBOX_SCOPES, config.scopes);
5474
5602
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5475
5603
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5476
5604
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5477
5605
  const params = new URLSearchParams({
5478
5606
  client_id: config.clientId,
5479
5607
  redirect_uri: effectiveRedirectUri,
5608
+ response_type: "code",
5480
5609
  scope: scopes.join(" "),
5481
5610
  state,
5482
- // Included for symmetry; GitHub ignores unknown parameters.
5483
5611
  code_challenge: codeChallenge,
5484
- code_challenge_method: "S256"
5612
+ code_challenge_method: "S256",
5613
+ token_access_type: "offline"
5485
5614
  });
5486
- return `${AUTHORIZATION_URL3}?${params.toString()}`;
5615
+ return `${AUTHORIZATION_URL4}?${params.toString()}`;
5487
5616
  }
5488
5617
  async function exchangeCode(code, codeVerifier, redirectUri) {
5489
5618
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5490
5619
  const body = new URLSearchParams({
5620
+ grant_type: "authorization_code",
5491
5621
  client_id: config.clientId,
5492
5622
  client_secret: config.clientSecret,
5493
5623
  code,
5494
- redirect_uri: effectiveRedirectUri,
5495
- code_verifier: codeVerifier
5624
+ code_verifier: codeVerifier,
5625
+ redirect_uri: effectiveRedirectUri
5496
5626
  });
5497
- const response = await fetch(TOKEN_URL3, {
5627
+ const response = await fetch(TOKEN_URL4, {
5498
5628
  method: "POST",
5499
- headers: {
5500
- "Content-Type": "application/x-www-form-urlencoded",
5501
- Accept: "application/json"
5502
- },
5629
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
5503
5630
  body: body.toString()
5504
5631
  });
5505
5632
  if (!response.ok) {
5506
5633
  const text3 = await response.text();
5507
- throw new Error(`GitHub token exchange failed (${response.status}): ${text3}`);
5634
+ throw new Error(`Dropbox token exchange failed (${response.status}): ${text3}`);
5508
5635
  }
5509
5636
  const raw = await response.json();
5510
5637
  const data = raw;
5511
- if (data.error) {
5512
- throw new Error(
5513
- `GitHub token exchange error: ${data.error} \u2014 ${data.error_description ?? ""}`
5514
- );
5515
- }
5516
- if (!data.access_token) {
5517
- throw new Error("GitHub token exchange returned no access_token");
5518
- }
5519
5638
  return {
5520
5639
  accessToken: data.access_token,
5521
5640
  refreshToken: data.refresh_token,
5522
5641
  expiresIn: data.expires_in,
5523
- tokenType: data.token_type ?? "bearer",
5642
+ tokenType: data.token_type ?? "Bearer",
5524
5643
  raw
5525
5644
  };
5526
5645
  }
5527
5646
  async function getUserInfo(accessToken) {
5528
- const headers = {
5529
- Authorization: `Bearer ${accessToken}`,
5530
- Accept: "application/vnd.github+json",
5531
- "X-GitHub-Api-Version": "2022-11-28"
5532
- };
5533
- const profileResponse = await fetch(USER_URL, { headers });
5534
- if (!profileResponse.ok) {
5535
- const text3 = await profileResponse.text();
5536
- throw new Error(`GitHub user fetch failed (${profileResponse.status}): ${text3}`);
5537
- }
5538
- const raw = await profileResponse.json();
5539
- const profile = raw;
5540
- let email = typeof profile.email === "string" ? profile.email : null;
5541
- if (!email) {
5542
- email = await fetchPrimaryEmail(accessToken, headers);
5543
- }
5544
- if (!email) {
5647
+ const response = await fetch(USER_INFO_URL3, {
5648
+ method: "POST",
5649
+ headers: {
5650
+ Authorization: `Bearer ${accessToken}`,
5651
+ "Content-Type": "application/json"
5652
+ },
5653
+ body: "null"
5654
+ });
5655
+ if (!response.ok) {
5656
+ const text3 = await response.text();
5545
5657
  throw new Error(
5546
- "GitHub user has no accessible email. Ensure the user:email scope is granted."
5658
+ `Dropbox /2/users/get_current_account fetch failed (${response.status}): ${text3}`
5547
5659
  );
5548
5660
  }
5549
- return {
5550
- id: String(profile.id),
5551
- email,
5552
- name: profile.name ?? profile.login,
5553
- avatar: profile.avatar_url,
5554
- raw
5555
- };
5661
+ const raw = await response.json();
5662
+ return normalizeProfile3(raw);
5556
5663
  }
5557
5664
  return {
5558
- id: "github",
5559
- name: "GitHub",
5560
- authorizationUrl: AUTHORIZATION_URL3,
5561
- tokenUrl: TOKEN_URL3,
5562
- userInfoUrl: USER_URL,
5665
+ id: "dropbox",
5666
+ name: "Dropbox",
5667
+ authorizationUrl: AUTHORIZATION_URL4,
5668
+ tokenUrl: TOKEN_URL4,
5669
+ userInfoUrl: USER_INFO_URL3,
5563
5670
  scopes,
5564
5671
  getAuthorizationUrl,
5565
5672
  exchangeCode,
5566
5673
  getUserInfo
5567
5674
  };
5568
5675
  }
5569
- async function fetchPrimaryEmail(_accessToken, headers) {
5570
- const response = await fetch(USER_EMAILS_URL, { headers });
5571
- if (!response.ok) return null;
5572
- const emails = await response.json();
5573
- const primary = emails.find((e) => e.primary && e.verified);
5574
- return primary?.email ?? null;
5575
- }
5576
- function mergeScopes3(defaults, extras) {
5676
+ function mergeScopes4(defaults, extras) {
5577
5677
  if (!extras || extras.length === 0) return defaults;
5578
5678
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5579
5679
  return [...merged];
5580
5680
  }
5581
5681
 
5582
- // src/auth/oauth/providers/gitlab.ts
5583
- var AUTHORIZATION_URL4 = "https://gitlab.com/oauth/authorize";
5584
- var TOKEN_URL4 = "https://gitlab.com/oauth/token";
5585
- var USER_INFO_URL2 = "https://gitlab.com/api/v4/user";
5586
- var DEFAULT_SCOPES4 = ["read_user"];
5587
- function createGitlabProvider(config) {
5588
- const scopes = mergeScopes4(DEFAULT_SCOPES4, config.scopes);
5682
+ // src/auth/oauth/providers/figma.ts
5683
+ var AUTHORIZATION_URL5 = "https://www.figma.com/oauth";
5684
+ var TOKEN_URL5 = "https://api.figma.com/v1/oauth/token";
5685
+ var USER_INFO_URL4 = "https://api.figma.com/v1/me";
5686
+ var DEFAULT_FIGMA_SCOPES = ["file_read"];
5687
+ function normalizeProfile4(raw) {
5688
+ const data = raw;
5689
+ if (!data.id) {
5690
+ throw new Error("Figma user response missing required field: id");
5691
+ }
5692
+ if (!data.email) {
5693
+ throw new Error("Figma user response missing required field: email");
5694
+ }
5695
+ return {
5696
+ id: data.id,
5697
+ email: data.email,
5698
+ name: data.handle,
5699
+ avatar: data.img_url,
5700
+ raw
5701
+ };
5702
+ }
5703
+ function createFigmaProvider(config) {
5704
+ const scopes = mergeScopes5(DEFAULT_FIGMA_SCOPES, config.scopes);
5589
5705
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5590
5706
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5591
5707
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5598,26 +5714,26 @@ function createGitlabProvider(config) {
5598
5714
  code_challenge: codeChallenge,
5599
5715
  code_challenge_method: "S256"
5600
5716
  });
5601
- return `${AUTHORIZATION_URL4}?${params.toString()}`;
5717
+ return `${AUTHORIZATION_URL5}?${params.toString()}`;
5602
5718
  }
5603
5719
  async function exchangeCode(code, codeVerifier, redirectUri) {
5604
5720
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5605
5721
  const body = new URLSearchParams({
5606
- grant_type: "authorization_code",
5607
5722
  client_id: config.clientId,
5608
5723
  client_secret: config.clientSecret,
5724
+ redirect_uri: effectiveRedirectUri,
5609
5725
  code,
5610
5726
  code_verifier: codeVerifier,
5611
- redirect_uri: effectiveRedirectUri
5727
+ grant_type: "authorization_code"
5612
5728
  });
5613
- const response = await fetch(TOKEN_URL4, {
5729
+ const response = await fetch(TOKEN_URL5, {
5614
5730
  method: "POST",
5615
5731
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5616
5732
  body: body.toString()
5617
5733
  });
5618
5734
  if (!response.ok) {
5619
5735
  const text3 = await response.text();
5620
- throw new Error(`GitLab token exchange failed (${response.status}): ${text3}`);
5736
+ throw new Error(`Figma token exchange failed (${response.status}): ${text3}`);
5621
5737
  }
5622
5738
  const raw = await response.json();
5623
5739
  const data = raw;
@@ -5630,58 +5746,84 @@ function createGitlabProvider(config) {
5630
5746
  };
5631
5747
  }
5632
5748
  async function getUserInfo(accessToken) {
5633
- const response = await fetch(USER_INFO_URL2, {
5749
+ const response = await fetch(USER_INFO_URL4, {
5634
5750
  headers: { Authorization: `Bearer ${accessToken}` }
5635
5751
  });
5636
5752
  if (!response.ok) {
5637
5753
  const text3 = await response.text();
5638
- throw new Error(`GitLab /api/v4/user fetch failed (${response.status}): ${text3}`);
5754
+ throw new Error(`Figma /v1/me fetch failed (${response.status}): ${text3}`);
5639
5755
  }
5640
5756
  const raw = await response.json();
5641
- const data = raw;
5642
- if (!data.id) {
5643
- throw new Error("GitLab user response missing required field: id");
5644
- }
5645
- const email = data.email ?? data.public_email;
5646
- if (!email) {
5647
- throw new Error(
5648
- "GitLab user response has no email. The account may have restricted email visibility."
5649
- );
5650
- }
5651
- return {
5652
- id: String(data.id),
5653
- email,
5654
- name: data.name ?? data.username,
5655
- avatar: data.avatar_url,
5656
- raw
5657
- };
5757
+ return normalizeProfile4(raw);
5658
5758
  }
5659
5759
  return {
5660
- id: "gitlab",
5661
- name: "GitLab",
5662
- authorizationUrl: AUTHORIZATION_URL4,
5663
- tokenUrl: TOKEN_URL4,
5664
- userInfoUrl: USER_INFO_URL2,
5760
+ id: "figma",
5761
+ name: "Figma",
5762
+ authorizationUrl: AUTHORIZATION_URL5,
5763
+ tokenUrl: TOKEN_URL5,
5764
+ userInfoUrl: USER_INFO_URL4,
5665
5765
  scopes,
5666
5766
  getAuthorizationUrl,
5667
5767
  exchangeCode,
5668
5768
  getUserInfo
5669
5769
  };
5670
5770
  }
5671
- function mergeScopes4(defaults, extras) {
5771
+ function mergeScopes5(defaults, extras) {
5672
5772
  if (!extras || extras.length === 0) return defaults;
5673
5773
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5674
5774
  return [...merged];
5675
5775
  }
5676
5776
 
5677
- // src/auth/oauth/providers/google.ts
5678
- var AUTHORIZATION_URL5 = "https://accounts.google.com/o/oauth2/v2/auth";
5679
- var TOKEN_URL5 = "https://oauth2.googleapis.com/token";
5680
- var USER_INFO_URL3 = "https://www.googleapis.com/oauth2/v3/userinfo";
5681
- var DEFAULT_SCOPES5 = ["openid", "email", "profile"];
5682
- function createGoogleProvider(config) {
5683
- const scopes = mergeScopes5(DEFAULT_SCOPES5, config.scopes);
5777
+ // src/auth/oauth/providers/generic.ts
5778
+ var discoveryCache = /* @__PURE__ */ new Map();
5779
+ async function discoverEndpoints(issuer) {
5780
+ const cached = discoveryCache.get(issuer);
5781
+ if (cached) return cached;
5782
+ const url = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
5783
+ const response = await fetch(url);
5784
+ if (!response.ok) {
5785
+ throw new Error(
5786
+ `OIDC discovery failed for issuer "${issuer}" (${response.status}). Provide explicit authorizationUrl / tokenUrl / userinfoUrl to skip discovery.`
5787
+ );
5788
+ }
5789
+ const doc = await response.json();
5790
+ const authorizationUrl = assertString(
5791
+ doc.authorization_endpoint,
5792
+ "authorization_endpoint",
5793
+ issuer
5794
+ );
5795
+ const tokenUrl = assertString(doc.token_endpoint, "token_endpoint", issuer);
5796
+ const userinfoUrl = assertString(doc.userinfo_endpoint, "userinfo_endpoint", issuer);
5797
+ const endpoints = { authorizationUrl, tokenUrl, userinfoUrl };
5798
+ discoveryCache.set(issuer, endpoints);
5799
+ return endpoints;
5800
+ }
5801
+ function assertString(value, field, issuer) {
5802
+ if (typeof value !== "string" || value.length === 0) {
5803
+ throw new Error(`OIDC discovery for "${issuer}" returned no "${field}" field.`);
5804
+ }
5805
+ return value;
5806
+ }
5807
+ function genericOIDC(config) {
5808
+ const defaultScopes = ["openid", "email", "profile"];
5809
+ const scopes = mergeScopes6(defaultScopes, config.scopes);
5810
+ async function resolveEndpoints() {
5811
+ if (config.authorizationUrl && config.tokenUrl && config.userinfoUrl) {
5812
+ return {
5813
+ authorizationUrl: config.authorizationUrl,
5814
+ tokenUrl: config.tokenUrl,
5815
+ userinfoUrl: config.userinfoUrl
5816
+ };
5817
+ }
5818
+ const discovered = await discoverEndpoints(config.issuer);
5819
+ return {
5820
+ authorizationUrl: config.authorizationUrl ?? discovered.authorizationUrl,
5821
+ tokenUrl: config.tokenUrl ?? discovered.tokenUrl,
5822
+ userinfoUrl: config.userinfoUrl ?? discovered.userinfoUrl
5823
+ };
5824
+ }
5684
5825
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5826
+ const endpoints = await resolveEndpoints();
5685
5827
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5686
5828
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5687
5829
  const params = new URLSearchParams({
@@ -5691,15 +5833,12 @@ function createGoogleProvider(config) {
5691
5833
  scope: scopes.join(" "),
5692
5834
  state,
5693
5835
  code_challenge: codeChallenge,
5694
- code_challenge_method: "S256",
5695
- access_type: "offline",
5696
- // request refresh token
5697
- prompt: "consent"
5698
- // always show consent to get refresh token reliably
5836
+ code_challenge_method: "S256"
5699
5837
  });
5700
- return `${AUTHORIZATION_URL5}?${params.toString()}`;
5838
+ return `${endpoints.authorizationUrl}?${params.toString()}`;
5701
5839
  }
5702
5840
  async function exchangeCode(code, codeVerifier, redirectUri) {
5841
+ const endpoints = await resolveEndpoints();
5703
5842
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5704
5843
  const body = new URLSearchParams({
5705
5844
  grant_type: "authorization_code",
@@ -5709,14 +5848,1104 @@ function createGoogleProvider(config) {
5709
5848
  code_verifier: codeVerifier,
5710
5849
  redirect_uri: effectiveRedirectUri
5711
5850
  });
5712
- const response = await fetch(TOKEN_URL5, {
5851
+ const response = await fetch(endpoints.tokenUrl, {
5713
5852
  method: "POST",
5714
5853
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5715
5854
  body: body.toString()
5716
5855
  });
5717
5856
  if (!response.ok) {
5718
5857
  const text3 = await response.text();
5719
- throw new Error(`Google token exchange failed (${response.status}): ${text3}`);
5858
+ throw new Error(`${config.name} token exchange failed (${response.status}): ${text3}`);
5859
+ }
5860
+ const raw = await response.json();
5861
+ const data = raw;
5862
+ return {
5863
+ accessToken: data.access_token,
5864
+ refreshToken: data.refresh_token,
5865
+ expiresIn: data.expires_in,
5866
+ tokenType: data.token_type ?? "Bearer",
5867
+ raw
5868
+ };
5869
+ }
5870
+ async function getUserInfo(accessToken) {
5871
+ const endpoints = await resolveEndpoints();
5872
+ const response = await fetch(endpoints.userinfoUrl, {
5873
+ headers: { Authorization: `Bearer ${accessToken}` }
5874
+ });
5875
+ if (!response.ok) {
5876
+ const text3 = await response.text();
5877
+ throw new Error(`${config.name} userinfo fetch failed (${response.status}): ${text3}`);
5878
+ }
5879
+ const raw = await response.json();
5880
+ const data = raw;
5881
+ if (!data.sub) {
5882
+ throw new Error(`${config.name} userinfo response missing required "sub" field.`);
5883
+ }
5884
+ const email = data.email ?? "";
5885
+ if (!email) {
5886
+ throw new Error(
5887
+ `${config.name} userinfo response missing "email". Ensure the 'email' scope is included.`
5888
+ );
5889
+ }
5890
+ return {
5891
+ id: data.sub,
5892
+ email,
5893
+ name: data.name,
5894
+ avatar: data.picture,
5895
+ raw
5896
+ };
5897
+ }
5898
+ const staticAuthUrl = config.authorizationUrl ?? `${config.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
5899
+ return {
5900
+ id: config.id,
5901
+ name: config.name,
5902
+ authorizationUrl: staticAuthUrl,
5903
+ tokenUrl: config.tokenUrl ?? config.issuer,
5904
+ userInfoUrl: config.userinfoUrl ?? config.issuer,
5905
+ scopes,
5906
+ getAuthorizationUrl,
5907
+ exchangeCode,
5908
+ getUserInfo
5909
+ };
5910
+ }
5911
+ function mergeScopes6(defaults, extras) {
5912
+ if (!extras || extras.length === 0) return defaults;
5913
+ return [.../* @__PURE__ */ new Set([...defaults, ...extras])];
5914
+ }
5915
+
5916
+ // src/auth/oauth/providers/github.ts
5917
+ var AUTHORIZATION_URL6 = "https://github.com/login/oauth/authorize";
5918
+ var TOKEN_URL6 = "https://github.com/login/oauth/access_token";
5919
+ var USER_URL = "https://api.github.com/user";
5920
+ var USER_EMAILS_URL = "https://api.github.com/user/emails";
5921
+ var DEFAULT_SCOPES3 = ["user:email"];
5922
+ function createGithubProvider(config) {
5923
+ const scopes = mergeScopes7(DEFAULT_SCOPES3, config.scopes);
5924
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5925
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
5926
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5927
+ const params = new URLSearchParams({
5928
+ client_id: config.clientId,
5929
+ redirect_uri: effectiveRedirectUri,
5930
+ scope: scopes.join(" "),
5931
+ state,
5932
+ // Included for symmetry; GitHub ignores unknown parameters.
5933
+ code_challenge: codeChallenge,
5934
+ code_challenge_method: "S256"
5935
+ });
5936
+ return `${AUTHORIZATION_URL6}?${params.toString()}`;
5937
+ }
5938
+ async function exchangeCode(code, codeVerifier, redirectUri) {
5939
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5940
+ const body = new URLSearchParams({
5941
+ client_id: config.clientId,
5942
+ client_secret: config.clientSecret,
5943
+ code,
5944
+ redirect_uri: effectiveRedirectUri,
5945
+ code_verifier: codeVerifier
5946
+ });
5947
+ const response = await fetch(TOKEN_URL6, {
5948
+ method: "POST",
5949
+ headers: {
5950
+ "Content-Type": "application/x-www-form-urlencoded",
5951
+ Accept: "application/json"
5952
+ },
5953
+ body: body.toString()
5954
+ });
5955
+ if (!response.ok) {
5956
+ const text3 = await response.text();
5957
+ throw new Error(`GitHub token exchange failed (${response.status}): ${text3}`);
5958
+ }
5959
+ const raw = await response.json();
5960
+ const data = raw;
5961
+ if (data.error) {
5962
+ throw new Error(
5963
+ `GitHub token exchange error: ${data.error} \u2014 ${data.error_description ?? ""}`
5964
+ );
5965
+ }
5966
+ if (!data.access_token) {
5967
+ throw new Error("GitHub token exchange returned no access_token");
5968
+ }
5969
+ return {
5970
+ accessToken: data.access_token,
5971
+ refreshToken: data.refresh_token,
5972
+ expiresIn: data.expires_in,
5973
+ tokenType: data.token_type ?? "bearer",
5974
+ raw
5975
+ };
5976
+ }
5977
+ async function getUserInfo(accessToken) {
5978
+ const headers = {
5979
+ Authorization: `Bearer ${accessToken}`,
5980
+ Accept: "application/vnd.github+json",
5981
+ "X-GitHub-Api-Version": "2022-11-28"
5982
+ };
5983
+ const profileResponse = await fetch(USER_URL, { headers });
5984
+ if (!profileResponse.ok) {
5985
+ const text3 = await profileResponse.text();
5986
+ throw new Error(`GitHub user fetch failed (${profileResponse.status}): ${text3}`);
5987
+ }
5988
+ const raw = await profileResponse.json();
5989
+ const profile = raw;
5990
+ let email = typeof profile.email === "string" ? profile.email : null;
5991
+ if (!email) {
5992
+ email = await fetchPrimaryEmail(accessToken, headers);
5993
+ }
5994
+ if (!email) {
5995
+ throw new Error(
5996
+ "GitHub user has no accessible email. Ensure the user:email scope is granted."
5997
+ );
5998
+ }
5999
+ return {
6000
+ id: String(profile.id),
6001
+ email,
6002
+ name: profile.name ?? profile.login,
6003
+ avatar: profile.avatar_url,
6004
+ raw
6005
+ };
6006
+ }
6007
+ return {
6008
+ id: "github",
6009
+ name: "GitHub",
6010
+ authorizationUrl: AUTHORIZATION_URL6,
6011
+ tokenUrl: TOKEN_URL6,
6012
+ userInfoUrl: USER_URL,
6013
+ scopes,
6014
+ getAuthorizationUrl,
6015
+ exchangeCode,
6016
+ getUserInfo
6017
+ };
6018
+ }
6019
+ async function fetchPrimaryEmail(_accessToken, headers) {
6020
+ const response = await fetch(USER_EMAILS_URL, { headers });
6021
+ if (!response.ok) return null;
6022
+ const emails = await response.json();
6023
+ const primary = emails.find((e) => e.primary && e.verified);
6024
+ return primary?.email ?? null;
6025
+ }
6026
+ function mergeScopes7(defaults, extras) {
6027
+ if (!extras || extras.length === 0) return defaults;
6028
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6029
+ return [...merged];
6030
+ }
6031
+
6032
+ // src/auth/oauth/providers/gitlab.ts
6033
+ var AUTHORIZATION_URL7 = "https://gitlab.com/oauth/authorize";
6034
+ var TOKEN_URL7 = "https://gitlab.com/oauth/token";
6035
+ var USER_INFO_URL5 = "https://gitlab.com/api/v4/user";
6036
+ var DEFAULT_SCOPES4 = ["read_user"];
6037
+ function createGitlabProvider(config) {
6038
+ const scopes = mergeScopes8(DEFAULT_SCOPES4, config.scopes);
6039
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6040
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6041
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6042
+ const params = new URLSearchParams({
6043
+ client_id: config.clientId,
6044
+ redirect_uri: effectiveRedirectUri,
6045
+ response_type: "code",
6046
+ scope: scopes.join(" "),
6047
+ state,
6048
+ code_challenge: codeChallenge,
6049
+ code_challenge_method: "S256"
6050
+ });
6051
+ return `${AUTHORIZATION_URL7}?${params.toString()}`;
6052
+ }
6053
+ async function exchangeCode(code, codeVerifier, redirectUri) {
6054
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6055
+ const body = new URLSearchParams({
6056
+ grant_type: "authorization_code",
6057
+ client_id: config.clientId,
6058
+ client_secret: config.clientSecret,
6059
+ code,
6060
+ code_verifier: codeVerifier,
6061
+ redirect_uri: effectiveRedirectUri
6062
+ });
6063
+ const response = await fetch(TOKEN_URL7, {
6064
+ method: "POST",
6065
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6066
+ body: body.toString()
6067
+ });
6068
+ if (!response.ok) {
6069
+ const text3 = await response.text();
6070
+ throw new Error(`GitLab token exchange failed (${response.status}): ${text3}`);
6071
+ }
6072
+ const raw = await response.json();
6073
+ const data = raw;
6074
+ return {
6075
+ accessToken: data.access_token,
6076
+ refreshToken: data.refresh_token,
6077
+ expiresIn: data.expires_in,
6078
+ tokenType: data.token_type ?? "Bearer",
6079
+ raw
6080
+ };
6081
+ }
6082
+ async function getUserInfo(accessToken) {
6083
+ const response = await fetch(USER_INFO_URL5, {
6084
+ headers: { Authorization: `Bearer ${accessToken}` }
6085
+ });
6086
+ if (!response.ok) {
6087
+ const text3 = await response.text();
6088
+ throw new Error(`GitLab /api/v4/user fetch failed (${response.status}): ${text3}`);
6089
+ }
6090
+ const raw = await response.json();
6091
+ const data = raw;
6092
+ if (!data.id) {
6093
+ throw new Error("GitLab user response missing required field: id");
6094
+ }
6095
+ const email = data.email ?? data.public_email;
6096
+ if (!email) {
6097
+ throw new Error(
6098
+ "GitLab user response has no email. The account may have restricted email visibility."
6099
+ );
6100
+ }
6101
+ return {
6102
+ id: String(data.id),
6103
+ email,
6104
+ name: data.name ?? data.username,
6105
+ avatar: data.avatar_url,
6106
+ raw
6107
+ };
6108
+ }
6109
+ return {
6110
+ id: "gitlab",
6111
+ name: "GitLab",
6112
+ authorizationUrl: AUTHORIZATION_URL7,
6113
+ tokenUrl: TOKEN_URL7,
6114
+ userInfoUrl: USER_INFO_URL5,
6115
+ scopes,
6116
+ getAuthorizationUrl,
6117
+ exchangeCode,
6118
+ getUserInfo
6119
+ };
6120
+ }
6121
+ function mergeScopes8(defaults, extras) {
6122
+ if (!extras || extras.length === 0) return defaults;
6123
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6124
+ return [...merged];
6125
+ }
6126
+
6127
+ // src/auth/oauth/providers/google.ts
6128
+ var AUTHORIZATION_URL8 = "https://accounts.google.com/o/oauth2/v2/auth";
6129
+ var TOKEN_URL8 = "https://oauth2.googleapis.com/token";
6130
+ var USER_INFO_URL6 = "https://www.googleapis.com/oauth2/v3/userinfo";
6131
+ var DEFAULT_SCOPES5 = ["openid", "email", "profile"];
6132
+ function createGoogleProvider(config) {
6133
+ const scopes = mergeScopes9(DEFAULT_SCOPES5, config.scopes);
6134
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6135
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6136
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6137
+ const params = new URLSearchParams({
6138
+ client_id: config.clientId,
6139
+ redirect_uri: effectiveRedirectUri,
6140
+ response_type: "code",
6141
+ scope: scopes.join(" "),
6142
+ state,
6143
+ code_challenge: codeChallenge,
6144
+ code_challenge_method: "S256",
6145
+ access_type: "offline",
6146
+ // request refresh token
6147
+ prompt: "consent"
6148
+ // always show consent to get refresh token reliably
6149
+ });
6150
+ return `${AUTHORIZATION_URL8}?${params.toString()}`;
6151
+ }
6152
+ async function exchangeCode(code, codeVerifier, redirectUri) {
6153
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6154
+ const body = new URLSearchParams({
6155
+ grant_type: "authorization_code",
6156
+ client_id: config.clientId,
6157
+ client_secret: config.clientSecret,
6158
+ code,
6159
+ code_verifier: codeVerifier,
6160
+ redirect_uri: effectiveRedirectUri
6161
+ });
6162
+ const response = await fetch(TOKEN_URL8, {
6163
+ method: "POST",
6164
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6165
+ body: body.toString()
6166
+ });
6167
+ if (!response.ok) {
6168
+ const text3 = await response.text();
6169
+ throw new Error(`Google token exchange failed (${response.status}): ${text3}`);
6170
+ }
6171
+ const raw = await response.json();
6172
+ const data = raw;
6173
+ return {
6174
+ accessToken: data.access_token,
6175
+ refreshToken: data.refresh_token,
6176
+ expiresIn: data.expires_in,
6177
+ tokenType: data.token_type ?? "Bearer",
6178
+ raw
6179
+ };
6180
+ }
6181
+ async function getUserInfo(accessToken) {
6182
+ const response = await fetch(USER_INFO_URL6, {
6183
+ headers: { Authorization: `Bearer ${accessToken}` }
6184
+ });
6185
+ if (!response.ok) {
6186
+ const text3 = await response.text();
6187
+ throw new Error(`Google userinfo fetch failed (${response.status}): ${text3}`);
6188
+ }
6189
+ const raw = await response.json();
6190
+ const data = raw;
6191
+ if (!data.sub || !data.email) {
6192
+ throw new Error("Google userinfo response missing required fields: sub, email");
6193
+ }
6194
+ return {
6195
+ id: data.sub,
6196
+ email: data.email,
6197
+ name: data.name,
6198
+ avatar: data.picture,
6199
+ raw
6200
+ };
6201
+ }
6202
+ return {
6203
+ id: "google",
6204
+ name: "Google",
6205
+ authorizationUrl: AUTHORIZATION_URL8,
6206
+ tokenUrl: TOKEN_URL8,
6207
+ userInfoUrl: USER_INFO_URL6,
6208
+ scopes,
6209
+ getAuthorizationUrl,
6210
+ exchangeCode,
6211
+ getUserInfo
6212
+ };
6213
+ }
6214
+ function mergeScopes9(defaults, extras) {
6215
+ if (!extras || extras.length === 0) return defaults;
6216
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6217
+ return [...merged];
6218
+ }
6219
+
6220
+ // src/auth/oauth/providers/linkedin.ts
6221
+ var AUTHORIZATION_URL9 = "https://www.linkedin.com/oauth/v2/authorization";
6222
+ var TOKEN_URL9 = "https://www.linkedin.com/oauth/v2/accessToken";
6223
+ var USER_INFO_URL7 = "https://api.linkedin.com/v2/userinfo";
6224
+ var DEFAULT_SCOPES6 = ["openid", "profile", "email"];
6225
+ function createLinkedInProvider(config) {
6226
+ const scopes = mergeScopes10(DEFAULT_SCOPES6, config.scopes);
6227
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6228
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6229
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6230
+ const params = new URLSearchParams({
6231
+ response_type: "code",
6232
+ client_id: config.clientId,
6233
+ redirect_uri: effectiveRedirectUri,
6234
+ scope: scopes.join(" "),
6235
+ state,
6236
+ code_challenge: codeChallenge,
6237
+ code_challenge_method: "S256"
6238
+ });
6239
+ return `${AUTHORIZATION_URL9}?${params.toString()}`;
6240
+ }
6241
+ async function exchangeCode(code, codeVerifier, redirectUri) {
6242
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6243
+ const body = new URLSearchParams({
6244
+ grant_type: "authorization_code",
6245
+ client_id: config.clientId,
6246
+ client_secret: config.clientSecret,
6247
+ code,
6248
+ redirect_uri: effectiveRedirectUri,
6249
+ code_verifier: codeVerifier
6250
+ });
6251
+ const response = await fetch(TOKEN_URL9, {
6252
+ method: "POST",
6253
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6254
+ body: body.toString()
6255
+ });
6256
+ if (!response.ok) {
6257
+ const text3 = await response.text();
6258
+ throw new Error(`LinkedIn token exchange failed (${response.status}): ${text3}`);
6259
+ }
6260
+ const raw = await response.json();
6261
+ const data = raw;
6262
+ return {
6263
+ accessToken: data.access_token,
6264
+ refreshToken: data.refresh_token,
6265
+ expiresIn: data.expires_in,
6266
+ tokenType: data.token_type ?? "Bearer",
6267
+ raw
6268
+ };
6269
+ }
6270
+ async function getUserInfo(accessToken) {
6271
+ const response = await fetch(USER_INFO_URL7, {
6272
+ headers: { Authorization: `Bearer ${accessToken}` }
6273
+ });
6274
+ if (!response.ok) {
6275
+ const text3 = await response.text();
6276
+ throw new Error(`LinkedIn /v2/userinfo fetch failed (${response.status}): ${text3}`);
6277
+ }
6278
+ const raw = await response.json();
6279
+ const data = raw;
6280
+ if (!data.sub) {
6281
+ throw new Error("LinkedIn userinfo response missing required field: sub");
6282
+ }
6283
+ if (!data.email) {
6284
+ throw new Error(
6285
+ "LinkedIn userinfo response has no email. Ensure the `email` and `openid` scopes are granted."
6286
+ );
6287
+ }
6288
+ const name = data.name ?? ([data.given_name, data.family_name].filter(Boolean).join(" ") || void 0);
6289
+ return {
6290
+ id: data.sub,
6291
+ email: data.email,
6292
+ name,
6293
+ avatar: data.picture,
6294
+ raw
6295
+ };
6296
+ }
6297
+ return {
6298
+ id: "linkedin",
6299
+ name: "LinkedIn",
6300
+ authorizationUrl: AUTHORIZATION_URL9,
6301
+ tokenUrl: TOKEN_URL9,
6302
+ userInfoUrl: USER_INFO_URL7,
6303
+ scopes,
6304
+ getAuthorizationUrl,
6305
+ exchangeCode,
6306
+ getUserInfo
6307
+ };
6308
+ }
6309
+ function mergeScopes10(defaults, extras) {
6310
+ if (!extras || extras.length === 0) return defaults;
6311
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6312
+ return [...merged];
6313
+ }
6314
+
6315
+ // src/auth/oauth/providers/microsoft.ts
6316
+ var AUTHORIZATION_URL10 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
6317
+ var TOKEN_URL10 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
6318
+ var USER_INFO_URL8 = "https://graph.microsoft.com/v1.0/me";
6319
+ var DEFAULT_SCOPES7 = ["openid", "profile", "email", "User.Read"];
6320
+ function createMicrosoftProvider(config) {
6321
+ const scopes = mergeScopes11(DEFAULT_SCOPES7, config.scopes);
6322
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6323
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6324
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6325
+ const params = new URLSearchParams({
6326
+ client_id: config.clientId,
6327
+ redirect_uri: effectiveRedirectUri,
6328
+ response_type: "code",
6329
+ scope: scopes.join(" "),
6330
+ state,
6331
+ code_challenge: codeChallenge,
6332
+ code_challenge_method: "S256"
6333
+ });
6334
+ return `${AUTHORIZATION_URL10}?${params.toString()}`;
6335
+ }
6336
+ async function exchangeCode(code, codeVerifier, redirectUri) {
6337
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6338
+ const body = new URLSearchParams({
6339
+ grant_type: "authorization_code",
6340
+ client_id: config.clientId,
6341
+ client_secret: config.clientSecret,
6342
+ code,
6343
+ code_verifier: codeVerifier,
6344
+ redirect_uri: effectiveRedirectUri
6345
+ });
6346
+ const response = await fetch(TOKEN_URL10, {
6347
+ method: "POST",
6348
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6349
+ body: body.toString()
6350
+ });
6351
+ if (!response.ok) {
6352
+ const text3 = await response.text();
6353
+ throw new Error(`Microsoft token exchange failed (${response.status}): ${text3}`);
6354
+ }
6355
+ const raw = await response.json();
6356
+ const data = raw;
6357
+ return {
6358
+ accessToken: data.access_token,
6359
+ refreshToken: data.refresh_token,
6360
+ expiresIn: data.expires_in,
6361
+ tokenType: data.token_type ?? "Bearer",
6362
+ raw
6363
+ };
6364
+ }
6365
+ async function getUserInfo(accessToken) {
6366
+ const response = await fetch(USER_INFO_URL8, {
6367
+ headers: { Authorization: `Bearer ${accessToken}` }
6368
+ });
6369
+ if (!response.ok) {
6370
+ const text3 = await response.text();
6371
+ throw new Error(`Microsoft Graph /me fetch failed (${response.status}): ${text3}`);
6372
+ }
6373
+ const raw = await response.json();
6374
+ const data = raw;
6375
+ if (!data.id) {
6376
+ throw new Error("Microsoft Graph /me response missing required field: id");
6377
+ }
6378
+ const email = data.mail ?? data.userPrincipalName;
6379
+ if (!email) {
6380
+ throw new Error(
6381
+ "Microsoft Graph /me response has no email or userPrincipalName. Ensure the `email` and `User.Read` scopes are granted."
6382
+ );
6383
+ }
6384
+ const name = data.displayName ?? ([data.givenName, data.surname].filter(Boolean).join(" ") || void 0);
6385
+ return {
6386
+ id: data.id,
6387
+ email,
6388
+ name,
6389
+ avatar: void 0,
6390
+ // Graph photo requires a separate /me/photo/$value call
6391
+ raw
6392
+ };
6393
+ }
6394
+ return {
6395
+ id: "microsoft",
6396
+ name: "Microsoft",
6397
+ authorizationUrl: AUTHORIZATION_URL10,
6398
+ tokenUrl: TOKEN_URL10,
6399
+ userInfoUrl: USER_INFO_URL8,
6400
+ scopes,
6401
+ getAuthorizationUrl,
6402
+ exchangeCode,
6403
+ getUserInfo
6404
+ };
6405
+ }
6406
+ function mergeScopes11(defaults, extras) {
6407
+ if (!extras || extras.length === 0) return defaults;
6408
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6409
+ return [...merged];
6410
+ }
6411
+
6412
+ // src/auth/oauth/providers/notion.ts
6413
+ var AUTHORIZATION_URL11 = "https://api.notion.com/v1/oauth/authorize";
6414
+ var TOKEN_URL11 = "https://api.notion.com/v1/oauth/token";
6415
+ var NOTION_VERSION = "2022-06-28";
6416
+ var DEFAULT_NOTION_SCOPES = [];
6417
+ function normalizeProfile5(raw) {
6418
+ const tokenData = raw;
6419
+ const user = tokenData?.owner?.user;
6420
+ if (!user?.id) {
6421
+ throw new Error(
6422
+ "Notion token response missing owner.user.id. Ensure the integration is authorized by a person, not a workspace bot."
6423
+ );
6424
+ }
6425
+ return {
6426
+ id: user.id,
6427
+ email: user.person?.email,
6428
+ name: user.name,
6429
+ avatar: user.avatar_url ?? void 0,
6430
+ raw
6431
+ };
6432
+ }
6433
+ function createNotionProvider(config) {
6434
+ const scopes = [];
6435
+ let lastTokenRaw = null;
6436
+ async function getAuthorizationUrl(state, _codeVerifier, redirectUri) {
6437
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6438
+ const params = new URLSearchParams({
6439
+ client_id: config.clientId,
6440
+ redirect_uri: effectiveRedirectUri,
6441
+ response_type: "code",
6442
+ owner: "user",
6443
+ state
6444
+ });
6445
+ return `${AUTHORIZATION_URL11}?${params.toString()}`;
6446
+ }
6447
+ async function exchangeCode(code, _codeVerifier, redirectUri) {
6448
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6449
+ const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
6450
+ const response = await fetch(TOKEN_URL11, {
6451
+ method: "POST",
6452
+ headers: {
6453
+ "Content-Type": "application/json",
6454
+ Authorization: `Basic ${credentials}`,
6455
+ "Notion-Version": NOTION_VERSION
6456
+ },
6457
+ body: JSON.stringify({
6458
+ grant_type: "authorization_code",
6459
+ code,
6460
+ redirect_uri: effectiveRedirectUri
6461
+ })
6462
+ });
6463
+ if (!response.ok) {
6464
+ const text3 = await response.text();
6465
+ throw new Error(`Notion token exchange failed (${response.status}): ${text3}`);
6466
+ }
6467
+ const raw = await response.json();
6468
+ const data = raw;
6469
+ lastTokenRaw = raw;
6470
+ return {
6471
+ accessToken: data.access_token,
6472
+ tokenType: data.token_type ?? "Bearer",
6473
+ raw
6474
+ };
6475
+ }
6476
+ async function getUserInfo(_accessToken) {
6477
+ if (!lastTokenRaw) {
6478
+ throw new Error(
6479
+ "Notion getUserInfo called before exchangeCode. Call exchangeCode first to obtain the token response."
6480
+ );
6481
+ }
6482
+ const tokenData = lastTokenRaw;
6483
+ const user = tokenData?.owner?.user;
6484
+ if (!user?.id) {
6485
+ throw new Error(
6486
+ "Notion token response missing owner.user.id. Ensure the integration is authorized by a person, not a workspace bot."
6487
+ );
6488
+ }
6489
+ const email = user.person?.email;
6490
+ const avatar = user.avatar_url ?? void 0;
6491
+ return {
6492
+ id: user.id,
6493
+ email,
6494
+ name: user.name,
6495
+ avatar,
6496
+ raw: lastTokenRaw
6497
+ };
6498
+ }
6499
+ return {
6500
+ id: "notion",
6501
+ name: "Notion",
6502
+ authorizationUrl: AUTHORIZATION_URL11,
6503
+ tokenUrl: TOKEN_URL11,
6504
+ // No separate UserInfo URL; identity comes from the token response.
6505
+ userInfoUrl: void 0,
6506
+ scopes,
6507
+ getAuthorizationUrl,
6508
+ exchangeCode,
6509
+ getUserInfo
6510
+ };
6511
+ }
6512
+
6513
+ // src/auth/oauth/providers/presets.ts
6514
+ function facebookProvider(clientId, clientSecret, scopes) {
6515
+ return genericOIDC({
6516
+ id: "facebook",
6517
+ name: "Facebook",
6518
+ issuer: "https://www.facebook.com",
6519
+ clientId,
6520
+ clientSecret,
6521
+ scopes: scopes ?? ["email", "public_profile"],
6522
+ authorizationUrl: "https://www.facebook.com/v18.0/dialog/oauth",
6523
+ tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
6524
+ userinfoUrl: "https://graph.facebook.com/me?fields=id,email,name,picture"
6525
+ });
6526
+ }
6527
+ function spotifyProvider(clientId, clientSecret, scopes) {
6528
+ return genericOIDC({
6529
+ id: "spotify",
6530
+ name: "Spotify",
6531
+ issuer: "https://accounts.spotify.com",
6532
+ clientId,
6533
+ clientSecret,
6534
+ scopes: scopes ?? ["user-read-email", "user-read-private"],
6535
+ authorizationUrl: "https://accounts.spotify.com/authorize",
6536
+ tokenUrl: "https://accounts.spotify.com/api/token",
6537
+ userinfoUrl: "https://api.spotify.com/v1/me"
6538
+ });
6539
+ }
6540
+ function twitchProvider(clientId, clientSecret, scopes) {
6541
+ return genericOIDC({
6542
+ id: "twitch",
6543
+ name: "Twitch",
6544
+ issuer: "https://id.twitch.tv/oauth2",
6545
+ clientId,
6546
+ clientSecret,
6547
+ scopes: scopes ?? ["openid", "user:read:email"],
6548
+ authorizationUrl: "https://id.twitch.tv/oauth2/authorize",
6549
+ tokenUrl: "https://id.twitch.tv/oauth2/token",
6550
+ userinfoUrl: "https://id.twitch.tv/oauth2/userinfo"
6551
+ });
6552
+ }
6553
+ function redditProvider(clientId, clientSecret, scopes) {
6554
+ return genericOIDC({
6555
+ id: "reddit",
6556
+ name: "Reddit",
6557
+ issuer: "https://www.reddit.com",
6558
+ clientId,
6559
+ clientSecret,
6560
+ scopes: scopes ?? ["identity"],
6561
+ authorizationUrl: "https://www.reddit.com/api/v1/authorize",
6562
+ tokenUrl: "https://www.reddit.com/api/v1/access_token",
6563
+ userinfoUrl: "https://oauth.reddit.com/api/v1/me"
6564
+ });
6565
+ }
6566
+ function dropboxProvider(clientId, clientSecret, scopes) {
6567
+ return genericOIDC({
6568
+ id: "dropbox",
6569
+ name: "Dropbox",
6570
+ issuer: "https://www.dropbox.com",
6571
+ clientId,
6572
+ clientSecret,
6573
+ scopes: scopes ?? ["account_info.read"],
6574
+ authorizationUrl: "https://www.dropbox.com/oauth2/authorize",
6575
+ tokenUrl: "https://api.dropboxapi.com/oauth2/token",
6576
+ userinfoUrl: "https://api.dropboxapi.com/2/users/get_current_account"
6577
+ });
6578
+ }
6579
+ function zoomProvider(clientId, clientSecret, scopes) {
6580
+ return genericOIDC({
6581
+ id: "zoom",
6582
+ name: "Zoom",
6583
+ issuer: "https://zoom.us",
6584
+ clientId,
6585
+ clientSecret,
6586
+ scopes: scopes ?? ["openid", "profile", "email"],
6587
+ authorizationUrl: "https://zoom.us/oauth/authorize",
6588
+ tokenUrl: "https://zoom.us/oauth/token",
6589
+ userinfoUrl: "https://api.zoom.us/v2/users/me"
6590
+ });
6591
+ }
6592
+ function notionProvider(clientId, clientSecret, scopes) {
6593
+ return genericOIDC({
6594
+ id: "notion",
6595
+ name: "Notion",
6596
+ issuer: "https://api.notion.com",
6597
+ clientId,
6598
+ clientSecret,
6599
+ scopes: scopes ?? [],
6600
+ authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
6601
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
6602
+ userinfoUrl: "https://api.notion.com/v1/users/me"
6603
+ });
6604
+ }
6605
+ function figmaProvider(clientId, clientSecret, scopes) {
6606
+ return genericOIDC({
6607
+ id: "figma",
6608
+ name: "Figma",
6609
+ issuer: "https://www.figma.com",
6610
+ clientId,
6611
+ clientSecret,
6612
+ scopes: scopes ?? ["file_read"],
6613
+ authorizationUrl: "https://www.figma.com/oauth",
6614
+ tokenUrl: "https://api.figma.com/v1/oauth/token",
6615
+ userinfoUrl: "https://api.figma.com/v1/me"
6616
+ });
6617
+ }
6618
+ function bitbucketProvider(clientId, clientSecret, scopes) {
6619
+ return genericOIDC({
6620
+ id: "bitbucket",
6621
+ name: "Bitbucket",
6622
+ issuer: "https://bitbucket.org",
6623
+ clientId,
6624
+ clientSecret,
6625
+ scopes: scopes ?? ["account", "email"],
6626
+ authorizationUrl: "https://bitbucket.org/site/oauth2/authorize",
6627
+ tokenUrl: "https://bitbucket.org/site/oauth2/access_token",
6628
+ userinfoUrl: "https://api.bitbucket.org/2.0/user"
6629
+ });
6630
+ }
6631
+ function atlassianProvider(clientId, clientSecret, scopes) {
6632
+ return genericOIDC({
6633
+ id: "atlassian",
6634
+ name: "Atlassian",
6635
+ issuer: "https://auth.atlassian.com",
6636
+ clientId,
6637
+ clientSecret,
6638
+ scopes: scopes ?? ["read:me", "offline_access"],
6639
+ authorizationUrl: "https://auth.atlassian.com/authorize",
6640
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
6641
+ userinfoUrl: "https://api.atlassian.com/me"
6642
+ });
6643
+ }
6644
+ function yahooProvider(clientId, clientSecret, scopes) {
6645
+ return genericOIDC({
6646
+ id: "yahoo",
6647
+ name: "Yahoo",
6648
+ issuer: "https://api.login.yahoo.com",
6649
+ clientId,
6650
+ clientSecret,
6651
+ scopes: scopes ?? ["openid", "profile", "email"],
6652
+ authorizationUrl: "https://api.login.yahoo.com/oauth2/request_auth",
6653
+ tokenUrl: "https://api.login.yahoo.com/oauth2/get_token",
6654
+ userinfoUrl: "https://api.login.yahoo.com/openid/v1/userinfo"
6655
+ });
6656
+ }
6657
+ function lineProvider(clientId, clientSecret, scopes) {
6658
+ return genericOIDC({
6659
+ id: "line",
6660
+ name: "LINE",
6661
+ issuer: "https://access.line.me",
6662
+ clientId,
6663
+ clientSecret,
6664
+ scopes: scopes ?? ["openid", "profile", "email"],
6665
+ authorizationUrl: "https://access.line.me/oauth2/v2.1/authorize",
6666
+ tokenUrl: "https://api.line.me/oauth2/v2.1/token",
6667
+ userinfoUrl: "https://api.line.me/v2/profile"
6668
+ });
6669
+ }
6670
+ function coinbaseProvider(clientId, clientSecret, scopes) {
6671
+ return genericOIDC({
6672
+ id: "coinbase",
6673
+ name: "Coinbase",
6674
+ issuer: "https://login.coinbase.com",
6675
+ clientId,
6676
+ clientSecret,
6677
+ scopes: scopes ?? ["wallet:user:read", "wallet:user:email"],
6678
+ authorizationUrl: "https://login.coinbase.com/oauth2/auth",
6679
+ tokenUrl: "https://login.coinbase.com/oauth2/token",
6680
+ userinfoUrl: "https://api.coinbase.com/v2/user"
6681
+ });
6682
+ }
6683
+ function tiktokProvider(clientId, clientSecret, scopes) {
6684
+ return genericOIDC({
6685
+ id: "tiktok",
6686
+ name: "TikTok",
6687
+ issuer: "https://www.tiktok.com",
6688
+ clientId,
6689
+ clientSecret,
6690
+ scopes: scopes ?? ["user.info.basic"],
6691
+ authorizationUrl: "https://www.tiktok.com/v2/auth/authorize/",
6692
+ tokenUrl: "https://open.tiktokapis.com/v2/oauth/token/",
6693
+ userinfoUrl: "https://open.tiktokapis.com/v2/user/info/"
6694
+ });
6695
+ }
6696
+ function paypalProvider(clientId, clientSecret, scopes) {
6697
+ return genericOIDC({
6698
+ id: "paypal",
6699
+ name: "PayPal",
6700
+ issuer: "https://www.paypal.com",
6701
+ clientId,
6702
+ clientSecret,
6703
+ scopes: scopes ?? ["openid", "email"],
6704
+ authorizationUrl: "https://www.paypal.com/signin/authorize",
6705
+ tokenUrl: "https://api-m.paypal.com/v1/oauth2/token",
6706
+ userinfoUrl: "https://api-m.paypal.com/v1/identity/openidconnect/userinfo?schema=openid"
6707
+ });
6708
+ }
6709
+ function salesforceProvider(clientId, clientSecret, scopes) {
6710
+ return genericOIDC({
6711
+ id: "salesforce",
6712
+ name: "Salesforce",
6713
+ issuer: "https://login.salesforce.com",
6714
+ clientId,
6715
+ clientSecret,
6716
+ scopes: scopes ?? ["openid", "email", "profile"],
6717
+ authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
6718
+ tokenUrl: "https://login.salesforce.com/services/oauth2/token",
6719
+ userinfoUrl: "https://login.salesforce.com/services/oauth2/userinfo"
6720
+ });
6721
+ }
6722
+ function vkProvider(clientId, clientSecret, scopes) {
6723
+ return genericOIDC({
6724
+ id: "vk",
6725
+ name: "VK",
6726
+ issuer: "https://id.vk.com",
6727
+ clientId,
6728
+ clientSecret,
6729
+ scopes: scopes ?? ["email"],
6730
+ authorizationUrl: "https://id.vk.com/authorize",
6731
+ tokenUrl: "https://id.vk.com/oauth2/auth",
6732
+ userinfoUrl: "https://id.vk.com/oauth2/user_info"
6733
+ });
6734
+ }
6735
+ function kakaoProvider(clientId, clientSecret, scopes) {
6736
+ return genericOIDC({
6737
+ id: "kakao",
6738
+ name: "Kakao",
6739
+ issuer: "https://kauth.kakao.com",
6740
+ clientId,
6741
+ clientSecret,
6742
+ scopes: scopes ?? ["account_email", "profile_nickname"],
6743
+ authorizationUrl: "https://kauth.kakao.com/oauth/authorize",
6744
+ tokenUrl: "https://kauth.kakao.com/oauth/token",
6745
+ userinfoUrl: "https://kapi.kakao.com/v2/user/me"
6746
+ });
6747
+ }
6748
+ function naverProvider(clientId, clientSecret, scopes) {
6749
+ return genericOIDC({
6750
+ id: "naver",
6751
+ name: "Naver",
6752
+ issuer: "https://nid.naver.com",
6753
+ clientId,
6754
+ clientSecret,
6755
+ scopes: scopes ?? ["email", "profile"],
6756
+ authorizationUrl: "https://nid.naver.com/oauth2.0/authorize",
6757
+ tokenUrl: "https://nid.naver.com/oauth2.0/token",
6758
+ userinfoUrl: "https://openapi.naver.com/v1/nid/me"
6759
+ });
6760
+ }
6761
+ function huggingfaceProvider(clientId, clientSecret, scopes) {
6762
+ return genericOIDC({
6763
+ id: "huggingface",
6764
+ name: "Hugging Face",
6765
+ issuer: "https://huggingface.co",
6766
+ clientId,
6767
+ clientSecret,
6768
+ scopes: scopes ?? ["openid", "profile", "email"],
6769
+ authorizationUrl: "https://huggingface.co/oauth/authorize",
6770
+ tokenUrl: "https://huggingface.co/oauth/token",
6771
+ userinfoUrl: "https://huggingface.co/oauth/userinfo"
6772
+ });
6773
+ }
6774
+ function robloxProvider(clientId, clientSecret, scopes) {
6775
+ return genericOIDC({
6776
+ id: "roblox",
6777
+ name: "Roblox",
6778
+ issuer: "https://apis.roblox.com/oauth",
6779
+ clientId,
6780
+ clientSecret,
6781
+ scopes: scopes ?? ["openid", "profile"],
6782
+ authorizationUrl: "https://apis.roblox.com/oauth/v1/authorize",
6783
+ tokenUrl: "https://apis.roblox.com/oauth/v1/token",
6784
+ userinfoUrl: "https://apis.roblox.com/oauth/v1/userinfo"
6785
+ });
6786
+ }
6787
+ function vercelProvider(clientId, clientSecret, scopes) {
6788
+ return genericOIDC({
6789
+ id: "vercel",
6790
+ name: "Vercel",
6791
+ issuer: "https://vercel.com",
6792
+ clientId,
6793
+ clientSecret,
6794
+ scopes: scopes ?? ["openid", "email", "profile"],
6795
+ authorizationUrl: "https://vercel.com/integrations/oauth/authorize",
6796
+ tokenUrl: "https://api.vercel.com/v2/oauth/access_token",
6797
+ userinfoUrl: "https://api.vercel.com/v2/user"
6798
+ });
6799
+ }
6800
+ function linearProvider(clientId, clientSecret, scopes) {
6801
+ return genericOIDC({
6802
+ id: "linear",
6803
+ name: "Linear",
6804
+ issuer: "https://linear.app",
6805
+ clientId,
6806
+ clientSecret,
6807
+ scopes: scopes ?? ["read"],
6808
+ authorizationUrl: "https://linear.app/oauth/authorize",
6809
+ tokenUrl: "https://api.linear.app/oauth/token",
6810
+ userinfoUrl: "https://api.linear.app/graphql"
6811
+ });
6812
+ }
6813
+ function railwayProvider(clientId, clientSecret, scopes) {
6814
+ return genericOIDC({
6815
+ id: "railway",
6816
+ name: "Railway",
6817
+ issuer: "https://railway.com",
6818
+ clientId,
6819
+ clientSecret,
6820
+ scopes: scopes ?? ["read:user", "read:project"],
6821
+ authorizationUrl: "https://railway.com/oauth/authorize",
6822
+ tokenUrl: "https://railway.com/oauth/token",
6823
+ userinfoUrl: "https://backboard.railway.com/graphql/v2"
6824
+ });
6825
+ }
6826
+ function kickProvider(clientId, clientSecret, scopes) {
6827
+ return genericOIDC({
6828
+ id: "kick",
6829
+ name: "Kick",
6830
+ issuer: "https://id.kick.com",
6831
+ clientId,
6832
+ clientSecret,
6833
+ scopes: scopes ?? ["user:read"],
6834
+ authorizationUrl: "https://id.kick.com/oauth/authorize",
6835
+ tokenUrl: "https://id.kick.com/oauth/token",
6836
+ userinfoUrl: "https://id.kick.com/oauth/userinfo"
6837
+ });
6838
+ }
6839
+ function wechatProvider(clientId, clientSecret, scopes) {
6840
+ return genericOIDC({
6841
+ id: "wechat",
6842
+ name: "WeChat",
6843
+ issuer: "https://open.weixin.qq.com",
6844
+ clientId,
6845
+ clientSecret,
6846
+ scopes: scopes ?? ["snsapi_login"],
6847
+ authorizationUrl: "https://open.weixin.qq.com/connect/qrconnect",
6848
+ tokenUrl: "https://api.weixin.qq.com/sns/oauth2/access_token",
6849
+ userinfoUrl: "https://api.weixin.qq.com/sns/userinfo"
6850
+ });
6851
+ }
6852
+ function polarProvider(clientId, clientSecret, scopes) {
6853
+ return genericOIDC({
6854
+ id: "polar",
6855
+ name: "Polar",
6856
+ issuer: "https://polar.sh",
6857
+ clientId,
6858
+ clientSecret,
6859
+ scopes: scopes ?? ["openid", "email", "profile"],
6860
+ authorizationUrl: "https://polar.sh/oauth2/authorize",
6861
+ tokenUrl: "https://api.polar.sh/v1/oauth2/token",
6862
+ userinfoUrl: "https://api.polar.sh/v1/oauth2/userinfo"
6863
+ });
6864
+ }
6865
+ function auth0Provider(domain, clientId, clientSecret, scopes) {
6866
+ const issuer = `https://${domain.replace(/^https?:\/\//, "")}`;
6867
+ return genericOIDC({
6868
+ id: "auth0",
6869
+ name: "Auth0",
6870
+ issuer,
6871
+ clientId,
6872
+ clientSecret,
6873
+ scopes
6874
+ });
6875
+ }
6876
+ function oktaProvider(domain, clientId, clientSecret, scopes) {
6877
+ const issuer = `https://${domain.replace(/^https?:\/\//, "")}/oauth2/default`;
6878
+ return genericOIDC({
6879
+ id: "okta",
6880
+ name: "Okta",
6881
+ issuer,
6882
+ clientId,
6883
+ clientSecret,
6884
+ scopes
6885
+ });
6886
+ }
6887
+ function cognitoProvider(domain, clientId, clientSecret, scopes) {
6888
+ const host = domain.replace(/^https?:\/\//, "").replace(/\/$/, "");
6889
+ const issuer = `https://${host}`;
6890
+ return genericOIDC({
6891
+ id: "cognito",
6892
+ name: "AWS Cognito",
6893
+ issuer,
6894
+ clientId,
6895
+ clientSecret,
6896
+ scopes: scopes ?? ["openid", "email", "profile"],
6897
+ authorizationUrl: `${issuer}/oauth2/authorize`,
6898
+ tokenUrl: `${issuer}/oauth2/token`,
6899
+ userinfoUrl: `${issuer}/oauth2/userInfo`
6900
+ });
6901
+ }
6902
+
6903
+ // src/auth/oauth/providers/reddit.ts
6904
+ var AUTHORIZATION_URL12 = "https://www.reddit.com/api/v1/authorize";
6905
+ var TOKEN_URL12 = "https://www.reddit.com/api/v1/access_token";
6906
+ var USER_INFO_URL9 = "https://oauth.reddit.com/api/v1/me";
6907
+ var DEFAULT_USER_AGENT = "web:kavachos-oauth:v1 (by /u/kavachos)";
6908
+ var DEFAULT_REDDIT_SCOPES = ["identity"];
6909
+ var DEFAULT_SCOPES8 = DEFAULT_REDDIT_SCOPES;
6910
+ function createRedditProvider(config) {
6911
+ const scopes = mergeScopes12(DEFAULT_SCOPES8, config.scopes);
6912
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6913
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6914
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6915
+ const params = new URLSearchParams({
6916
+ client_id: config.clientId,
6917
+ redirect_uri: effectiveRedirectUri,
6918
+ response_type: "code",
6919
+ scope: scopes.join(" "),
6920
+ state,
6921
+ code_challenge: codeChallenge,
6922
+ code_challenge_method: "S256",
6923
+ // Reddit requires duration=permanent to receive a refresh token.
6924
+ duration: "permanent"
6925
+ });
6926
+ return `${AUTHORIZATION_URL12}?${params.toString()}`;
6927
+ }
6928
+ async function exchangeCode(code, codeVerifier, redirectUri) {
6929
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6930
+ const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
6931
+ const body = new URLSearchParams({
6932
+ grant_type: "authorization_code",
6933
+ code,
6934
+ redirect_uri: effectiveRedirectUri,
6935
+ code_verifier: codeVerifier
6936
+ });
6937
+ const response = await fetch(TOKEN_URL12, {
6938
+ method: "POST",
6939
+ headers: {
6940
+ "Content-Type": "application/x-www-form-urlencoded",
6941
+ Authorization: `Basic ${credentials}`,
6942
+ "User-Agent": DEFAULT_USER_AGENT
6943
+ },
6944
+ body: body.toString()
6945
+ });
6946
+ if (!response.ok) {
6947
+ const text3 = await response.text();
6948
+ throw new Error(`Reddit token exchange failed (${response.status}): ${text3}`);
5720
6949
  }
5721
6950
  const raw = await response.json();
5722
6951
  const data = raw;
@@ -5729,64 +6958,100 @@ function createGoogleProvider(config) {
5729
6958
  };
5730
6959
  }
5731
6960
  async function getUserInfo(accessToken) {
5732
- const response = await fetch(USER_INFO_URL3, {
5733
- headers: { Authorization: `Bearer ${accessToken}` }
6961
+ const response = await fetch(USER_INFO_URL9, {
6962
+ headers: {
6963
+ Authorization: `Bearer ${accessToken}`,
6964
+ "User-Agent": DEFAULT_USER_AGENT
6965
+ }
5734
6966
  });
5735
6967
  if (!response.ok) {
5736
6968
  const text3 = await response.text();
5737
- throw new Error(`Google userinfo fetch failed (${response.status}): ${text3}`);
6969
+ throw new Error(`Reddit /api/v1/me fetch failed (${response.status}): ${text3}`);
5738
6970
  }
5739
6971
  const raw = await response.json();
5740
6972
  const data = raw;
5741
- if (!data.sub || !data.email) {
5742
- throw new Error("Google userinfo response missing required fields: sub, email");
6973
+ if (!data.id) {
6974
+ throw new Error("Reddit user response missing required field: id");
5743
6975
  }
6976
+ if (!data.name) {
6977
+ throw new Error("Reddit user response missing required field: name");
6978
+ }
6979
+ const avatar = data.icon_img ? stripQueryParams(data.icon_img) : void 0;
5744
6980
  return {
5745
- id: data.sub,
5746
- email: data.email,
6981
+ id: data.id,
6982
+ // No email — caller must handle the undefined case.
6983
+ email: void 0,
5747
6984
  name: data.name,
5748
- avatar: data.picture,
6985
+ avatar,
5749
6986
  raw
5750
6987
  };
5751
6988
  }
5752
6989
  return {
5753
- id: "google",
5754
- name: "Google",
5755
- authorizationUrl: AUTHORIZATION_URL5,
5756
- tokenUrl: TOKEN_URL5,
5757
- userInfoUrl: USER_INFO_URL3,
6990
+ id: "reddit",
6991
+ name: "Reddit",
6992
+ authorizationUrl: AUTHORIZATION_URL12,
6993
+ tokenUrl: TOKEN_URL12,
6994
+ userInfoUrl: USER_INFO_URL9,
5758
6995
  scopes,
5759
6996
  getAuthorizationUrl,
5760
6997
  exchangeCode,
5761
6998
  getUserInfo
5762
6999
  };
5763
7000
  }
5764
- function mergeScopes5(defaults, extras) {
7001
+ function normalizeProfile6(raw) {
7002
+ const data = raw;
7003
+ if (!data.id) {
7004
+ throw new Error("Reddit user response missing required field: id");
7005
+ }
7006
+ if (!data.name) {
7007
+ throw new Error("Reddit user response missing required field: name");
7008
+ }
7009
+ const avatar = data.icon_img ? stripQueryParams(data.icon_img) : void 0;
7010
+ return {
7011
+ id: data.id,
7012
+ // Reddit does not return email through OAuth; callers must handle this.
7013
+ email: void 0,
7014
+ name: data.name,
7015
+ avatar,
7016
+ raw
7017
+ };
7018
+ }
7019
+ function stripQueryParams(url) {
7020
+ try {
7021
+ const parsed = new URL(url);
7022
+ parsed.search = "";
7023
+ return parsed.toString();
7024
+ } catch {
7025
+ return url;
7026
+ }
7027
+ }
7028
+ function mergeScopes12(defaults, extras) {
5765
7029
  if (!extras || extras.length === 0) return defaults;
5766
7030
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5767
7031
  return [...merged];
5768
7032
  }
5769
7033
 
5770
- // src/auth/oauth/providers/linkedin.ts
5771
- var AUTHORIZATION_URL6 = "https://www.linkedin.com/oauth/v2/authorization";
5772
- var TOKEN_URL6 = "https://www.linkedin.com/oauth/v2/accessToken";
5773
- var USER_INFO_URL4 = "https://api.linkedin.com/v2/userinfo";
5774
- var DEFAULT_SCOPES6 = ["openid", "profile", "email"];
5775
- function createLinkedInProvider(config) {
5776
- const scopes = mergeScopes6(DEFAULT_SCOPES6, config.scopes);
7034
+ // src/auth/oauth/providers/slack.ts
7035
+ var AUTHORIZATION_URL13 = "https://slack.com/oauth/v2/authorize";
7036
+ var TOKEN_URL13 = "https://slack.com/api/oauth.v2.access";
7037
+ var USER_INFO_URL10 = "https://slack.com/api/openid.connect.userInfo";
7038
+ var DEFAULT_SLACK_SCOPES = ["openid", "profile", "email"];
7039
+ var DEFAULT_SCOPES9 = DEFAULT_SLACK_SCOPES;
7040
+ function createSlackProvider(config) {
7041
+ const scopes = mergeScopes13(DEFAULT_SCOPES9, config.scopes);
5777
7042
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5778
7043
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5779
7044
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
5780
7045
  const params = new URLSearchParams({
5781
- response_type: "code",
5782
7046
  client_id: config.clientId,
5783
7047
  redirect_uri: effectiveRedirectUri,
7048
+ response_type: "code",
5784
7049
  scope: scopes.join(" "),
5785
7050
  state,
5786
7051
  code_challenge: codeChallenge,
5787
7052
  code_challenge_method: "S256"
5788
7053
  });
5789
- return `${AUTHORIZATION_URL6}?${params.toString()}`;
7054
+ return `${AUTHORIZATION_URL13}?${params.toString()}`;
5790
7055
  }
5791
7056
  async function exchangeCode(code, codeVerifier, redirectUri) {
5792
7057
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5798,77 +7063,120 @@ function createLinkedInProvider(config) {
5798
7063
  redirect_uri: effectiveRedirectUri,
5799
7064
  code_verifier: codeVerifier
5800
7065
  });
5801
- const response = await fetch(TOKEN_URL6, {
7066
+ const response = await fetch(TOKEN_URL13, {
5802
7067
  method: "POST",
5803
7068
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5804
7069
  body: body.toString()
5805
7070
  });
5806
7071
  if (!response.ok) {
5807
7072
  const text3 = await response.text();
5808
- throw new Error(`LinkedIn token exchange failed (${response.status}): ${text3}`);
7073
+ throw new Error(`Slack token exchange failed (${response.status}): ${text3}`);
5809
7074
  }
5810
7075
  const raw = await response.json();
5811
7076
  const data = raw;
7077
+ if (!data.ok) {
7078
+ throw new Error(`Slack token exchange error: ${data.error ?? "unknown"}`);
7079
+ }
7080
+ const accessToken = data.authed_user?.access_token ?? data.access_token;
7081
+ if (!accessToken) {
7082
+ throw new Error("Slack token exchange returned no access_token");
7083
+ }
5812
7084
  return {
5813
- accessToken: data.access_token,
5814
- refreshToken: data.refresh_token,
5815
- expiresIn: data.expires_in,
5816
- tokenType: data.token_type ?? "Bearer",
7085
+ accessToken,
7086
+ refreshToken: void 0,
7087
+ expiresIn: void 0,
7088
+ tokenType: data.authed_user?.token_type ?? data.token_type ?? "Bearer",
5817
7089
  raw
5818
7090
  };
5819
7091
  }
5820
7092
  async function getUserInfo(accessToken) {
5821
- const response = await fetch(USER_INFO_URL4, {
7093
+ const response = await fetch(USER_INFO_URL10, {
5822
7094
  headers: { Authorization: `Bearer ${accessToken}` }
5823
7095
  });
5824
7096
  if (!response.ok) {
5825
7097
  const text3 = await response.text();
5826
- throw new Error(`LinkedIn /v2/userinfo fetch failed (${response.status}): ${text3}`);
7098
+ throw new Error(`Slack openid.connect.userInfo fetch failed (${response.status}): ${text3}`);
5827
7099
  }
5828
7100
  const raw = await response.json();
5829
7101
  const data = raw;
5830
- if (!data.sub) {
5831
- throw new Error("LinkedIn userinfo response missing required field: sub");
7102
+ if (!data.ok) {
7103
+ throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
7104
+ }
7105
+ const id = data.sub ?? data["https://slack.com/user_id"];
7106
+ if (!id) {
7107
+ throw new Error("Slack userInfo response missing required field: sub");
5832
7108
  }
5833
7109
  if (!data.email) {
5834
- throw new Error(
5835
- "LinkedIn userinfo response has no email. Ensure the `email` and `openid` scopes are granted."
5836
- );
7110
+ throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
5837
7111
  }
5838
- const name = data.name ?? ([data.given_name, data.family_name].filter(Boolean).join(" ") || void 0);
5839
7112
  return {
5840
- id: data.sub,
7113
+ id,
5841
7114
  email: data.email,
5842
- name,
7115
+ name: data.name,
5843
7116
  avatar: data.picture,
5844
7117
  raw
5845
7118
  };
5846
7119
  }
5847
7120
  return {
5848
- id: "linkedin",
5849
- name: "LinkedIn",
5850
- authorizationUrl: AUTHORIZATION_URL6,
5851
- tokenUrl: TOKEN_URL6,
5852
- userInfoUrl: USER_INFO_URL4,
7121
+ id: "slack",
7122
+ name: "Slack",
7123
+ authorizationUrl: AUTHORIZATION_URL13,
7124
+ tokenUrl: TOKEN_URL13,
7125
+ userInfoUrl: USER_INFO_URL10,
5853
7126
  scopes,
5854
7127
  getAuthorizationUrl,
5855
7128
  exchangeCode,
5856
7129
  getUserInfo
5857
7130
  };
5858
7131
  }
5859
- function mergeScopes6(defaults, extras) {
7132
+ function normalizeProfile7(raw) {
7133
+ const data = raw;
7134
+ if (!data.ok) {
7135
+ throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
7136
+ }
7137
+ const id = data.sub ?? data["https://slack.com/user_id"];
7138
+ if (!id) {
7139
+ throw new Error("Slack userInfo response missing required field: sub");
7140
+ }
7141
+ if (!data.email) {
7142
+ throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
7143
+ }
7144
+ return {
7145
+ id,
7146
+ email: data.email,
7147
+ name: data.name,
7148
+ avatar: data.picture,
7149
+ raw
7150
+ };
7151
+ }
7152
+ function mergeScopes13(defaults, extras) {
5860
7153
  if (!extras || extras.length === 0) return defaults;
5861
7154
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5862
7155
  return [...merged];
5863
7156
  }
5864
7157
 
5865
- // src/auth/oauth/providers/microsoft.ts
5866
- var AUTHORIZATION_URL7 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
5867
- var TOKEN_URL7 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
5868
- var USER_INFO_URL5 = "https://graph.microsoft.com/v1.0/me";
5869
- var DEFAULT_SCOPES7 = ["openid", "profile", "email", "User.Read"];
5870
- function createMicrosoftProvider(config) {
5871
- const scopes = mergeScopes7(DEFAULT_SCOPES7, config.scopes);
7158
+ // src/auth/oauth/providers/spotify.ts
7159
+ var AUTHORIZATION_URL14 = "https://accounts.spotify.com/authorize";
7160
+ var TOKEN_URL14 = "https://accounts.spotify.com/api/token";
7161
+ var USER_INFO_URL11 = "https://api.spotify.com/v1/me";
7162
+ var DEFAULT_SPOTIFY_SCOPES = ["user-read-email", "user-read-private"];
7163
+ function normalizeProfile8(raw) {
7164
+ const data = raw;
7165
+ if (!data.id) {
7166
+ throw new Error("Spotify user response missing required field: id");
7167
+ }
7168
+ const avatar = Array.isArray(data.images) && data.images.length > 0 ? data.images[0]?.url : void 0;
7169
+ return {
7170
+ id: data.id,
7171
+ email: data.email,
7172
+ // emailVerified is not provided by Spotify
7173
+ name: data.display_name ?? void 0,
7174
+ avatar,
7175
+ raw
7176
+ };
7177
+ }
7178
+ function createSpotifyProvider(config) {
7179
+ const scopes = mergeScopes14(DEFAULT_SPOTIFY_SCOPES, config.scopes);
5872
7180
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5873
7181
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5874
7182
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5881,7 +7189,7 @@ function createMicrosoftProvider(config) {
5881
7189
  code_challenge: codeChallenge,
5882
7190
  code_challenge_method: "S256"
5883
7191
  });
5884
- return `${AUTHORIZATION_URL7}?${params.toString()}`;
7192
+ return `${AUTHORIZATION_URL14}?${params.toString()}`;
5885
7193
  }
5886
7194
  async function exchangeCode(code, codeVerifier, redirectUri) {
5887
7195
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5893,14 +7201,14 @@ function createMicrosoftProvider(config) {
5893
7201
  code_verifier: codeVerifier,
5894
7202
  redirect_uri: effectiveRedirectUri
5895
7203
  });
5896
- const response = await fetch(TOKEN_URL7, {
7204
+ const response = await fetch(TOKEN_URL14, {
5897
7205
  method: "POST",
5898
7206
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5899
7207
  body: body.toString()
5900
7208
  });
5901
7209
  if (!response.ok) {
5902
7210
  const text3 = await response.text();
5903
- throw new Error(`Microsoft token exchange failed (${response.status}): ${text3}`);
7211
+ throw new Error(`Spotify token exchange failed (${response.status}): ${text3}`);
5904
7212
  }
5905
7213
  const raw = await response.json();
5906
7214
  const data = raw;
@@ -5913,60 +7221,42 @@ function createMicrosoftProvider(config) {
5913
7221
  };
5914
7222
  }
5915
7223
  async function getUserInfo(accessToken) {
5916
- const response = await fetch(USER_INFO_URL5, {
7224
+ const response = await fetch(USER_INFO_URL11, {
5917
7225
  headers: { Authorization: `Bearer ${accessToken}` }
5918
7226
  });
5919
7227
  if (!response.ok) {
5920
7228
  const text3 = await response.text();
5921
- throw new Error(`Microsoft Graph /me fetch failed (${response.status}): ${text3}`);
7229
+ throw new Error(`Spotify /v1/me fetch failed (${response.status}): ${text3}`);
5922
7230
  }
5923
7231
  const raw = await response.json();
5924
- const data = raw;
5925
- if (!data.id) {
5926
- throw new Error("Microsoft Graph /me response missing required field: id");
5927
- }
5928
- const email = data.mail ?? data.userPrincipalName;
5929
- if (!email) {
5930
- throw new Error(
5931
- "Microsoft Graph /me response has no email or userPrincipalName. Ensure the `email` and `User.Read` scopes are granted."
5932
- );
5933
- }
5934
- const name = data.displayName ?? ([data.givenName, data.surname].filter(Boolean).join(" ") || void 0);
5935
- return {
5936
- id: data.id,
5937
- email,
5938
- name,
5939
- avatar: void 0,
5940
- // Graph photo requires a separate /me/photo/$value call
5941
- raw
5942
- };
7232
+ return normalizeProfile8(raw);
5943
7233
  }
5944
7234
  return {
5945
- id: "microsoft",
5946
- name: "Microsoft",
5947
- authorizationUrl: AUTHORIZATION_URL7,
5948
- tokenUrl: TOKEN_URL7,
5949
- userInfoUrl: USER_INFO_URL5,
7235
+ id: "spotify",
7236
+ name: "Spotify",
7237
+ authorizationUrl: AUTHORIZATION_URL14,
7238
+ tokenUrl: TOKEN_URL14,
7239
+ userInfoUrl: USER_INFO_URL11,
5950
7240
  scopes,
5951
7241
  getAuthorizationUrl,
5952
7242
  exchangeCode,
5953
7243
  getUserInfo
5954
7244
  };
5955
7245
  }
5956
- function mergeScopes7(defaults, extras) {
7246
+ function mergeScopes14(defaults, extras) {
5957
7247
  if (!extras || extras.length === 0) return defaults;
5958
7248
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
5959
7249
  return [...merged];
5960
7250
  }
5961
7251
 
5962
- // src/auth/oauth/providers/slack.ts
5963
- var AUTHORIZATION_URL8 = "https://slack.com/oauth/v2/authorize";
5964
- var TOKEN_URL8 = "https://slack.com/api/oauth.v2.access";
5965
- var USER_INFO_URL6 = "https://slack.com/api/openid.connect.userInfo";
5966
- var DEFAULT_SLACK_SCOPES = ["openid", "profile", "email"];
5967
- var DEFAULT_SCOPES8 = DEFAULT_SLACK_SCOPES;
5968
- function createSlackProvider(config) {
5969
- const scopes = mergeScopes8(DEFAULT_SCOPES8, config.scopes);
7252
+ // src/auth/oauth/providers/twitch.ts
7253
+ var AUTHORIZATION_URL15 = "https://id.twitch.tv/oauth2/authorize";
7254
+ var TOKEN_URL15 = "https://id.twitch.tv/oauth2/token";
7255
+ var USER_INFO_URL12 = "https://api.twitch.tv/helix/users";
7256
+ var DEFAULT_TWITCH_SCOPES = ["user:read:email"];
7257
+ var DEFAULT_SCOPES10 = DEFAULT_TWITCH_SCOPES;
7258
+ function createTwitchProvider(config) {
7259
+ const scopes = mergeScopes15(DEFAULT_SCOPES10, config.scopes);
5970
7260
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
5971
7261
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
5972
7262
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5979,7 +7269,7 @@ function createSlackProvider(config) {
5979
7269
  code_challenge: codeChallenge,
5980
7270
  code_challenge_method: "S256"
5981
7271
  });
5982
- return `${AUTHORIZATION_URL8}?${params.toString()}`;
7272
+ return `${AUTHORIZATION_URL15}?${params.toString()}`;
5983
7273
  }
5984
7274
  async function exchangeCode(code, codeVerifier, redirectUri) {
5985
7275
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -5988,89 +7278,103 @@ function createSlackProvider(config) {
5988
7278
  client_id: config.clientId,
5989
7279
  client_secret: config.clientSecret,
5990
7280
  code,
5991
- redirect_uri: effectiveRedirectUri,
5992
- code_verifier: codeVerifier
7281
+ code_verifier: codeVerifier,
7282
+ redirect_uri: effectiveRedirectUri
5993
7283
  });
5994
- const response = await fetch(TOKEN_URL8, {
7284
+ const response = await fetch(TOKEN_URL15, {
5995
7285
  method: "POST",
5996
7286
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
5997
7287
  body: body.toString()
5998
7288
  });
5999
7289
  if (!response.ok) {
6000
7290
  const text3 = await response.text();
6001
- throw new Error(`Slack token exchange failed (${response.status}): ${text3}`);
7291
+ throw new Error(`Twitch token exchange failed (${response.status}): ${text3}`);
6002
7292
  }
6003
7293
  const raw = await response.json();
6004
7294
  const data = raw;
6005
- if (!data.ok) {
6006
- throw new Error(`Slack token exchange error: ${data.error ?? "unknown"}`);
6007
- }
6008
- const accessToken = data.authed_user?.access_token ?? data.access_token;
6009
- if (!accessToken) {
6010
- throw new Error("Slack token exchange returned no access_token");
6011
- }
6012
7295
  return {
6013
- accessToken,
6014
- refreshToken: void 0,
6015
- expiresIn: void 0,
6016
- tokenType: data.authed_user?.token_type ?? data.token_type ?? "Bearer",
7296
+ accessToken: data.access_token,
7297
+ refreshToken: data.refresh_token,
7298
+ expiresIn: data.expires_in,
7299
+ tokenType: data.token_type ?? "Bearer",
6017
7300
  raw
6018
7301
  };
6019
7302
  }
6020
7303
  async function getUserInfo(accessToken) {
6021
- const response = await fetch(USER_INFO_URL6, {
6022
- headers: { Authorization: `Bearer ${accessToken}` }
7304
+ const response = await fetch(USER_INFO_URL12, {
7305
+ headers: {
7306
+ Authorization: `Bearer ${accessToken}`,
7307
+ "Client-ID": config.clientId
7308
+ }
6023
7309
  });
6024
7310
  if (!response.ok) {
6025
7311
  const text3 = await response.text();
6026
- throw new Error(`Slack openid.connect.userInfo fetch failed (${response.status}): ${text3}`);
7312
+ throw new Error(`Twitch /helix/users fetch failed (${response.status}): ${text3}`);
6027
7313
  }
6028
7314
  const raw = await response.json();
6029
- const data = raw;
6030
- if (!data.ok) {
6031
- throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
6032
- }
6033
- const id = data.sub ?? data["https://slack.com/user_id"];
6034
- if (!id) {
6035
- throw new Error("Slack userInfo response missing required field: sub");
7315
+ const body = raw;
7316
+ const user = body.data?.[0];
7317
+ if (!user?.id) {
7318
+ throw new Error("Twitch user response missing required field: id");
6036
7319
  }
6037
- if (!data.email) {
6038
- throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
7320
+ if (!user.email) {
7321
+ throw new Error(
7322
+ "Twitch user response has no email. Ensure the `user:read:email` scope is granted."
7323
+ );
6039
7324
  }
6040
7325
  return {
6041
- id,
6042
- email: data.email,
6043
- name: data.name,
6044
- avatar: data.picture,
7326
+ id: user.id,
7327
+ email: user.email,
7328
+ name: user.display_name,
7329
+ avatar: user.profile_image_url,
6045
7330
  raw
6046
7331
  };
6047
7332
  }
6048
7333
  return {
6049
- id: "slack",
6050
- name: "Slack",
6051
- authorizationUrl: AUTHORIZATION_URL8,
6052
- tokenUrl: TOKEN_URL8,
6053
- userInfoUrl: USER_INFO_URL6,
7334
+ id: "twitch",
7335
+ name: "Twitch",
7336
+ authorizationUrl: AUTHORIZATION_URL15,
7337
+ tokenUrl: TOKEN_URL15,
7338
+ userInfoUrl: USER_INFO_URL12,
6054
7339
  scopes,
6055
7340
  getAuthorizationUrl,
6056
7341
  exchangeCode,
6057
7342
  getUserInfo
6058
7343
  };
6059
7344
  }
6060
- function mergeScopes8(defaults, extras) {
7345
+ function normalizeProfile9(raw) {
7346
+ const body = raw;
7347
+ const user = body.data?.[0];
7348
+ if (!user?.id) {
7349
+ throw new Error("Twitch user response missing required field: id");
7350
+ }
7351
+ if (!user.email) {
7352
+ throw new Error(
7353
+ "Twitch user response has no email. Ensure the `user:read:email` scope is granted."
7354
+ );
7355
+ }
7356
+ return {
7357
+ id: user.id,
7358
+ email: user.email,
7359
+ name: user.display_name,
7360
+ avatar: user.profile_image_url,
7361
+ raw
7362
+ };
7363
+ }
7364
+ function mergeScopes15(defaults, extras) {
6061
7365
  if (!extras || extras.length === 0) return defaults;
6062
7366
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6063
7367
  return [...merged];
6064
7368
  }
6065
7369
 
6066
7370
  // src/auth/oauth/providers/twitter.ts
6067
- var AUTHORIZATION_URL9 = "https://twitter.com/i/oauth2/authorize";
6068
- var TOKEN_URL9 = "https://api.twitter.com/2/oauth2/token";
6069
- var USER_INFO_URL7 = "https://api.twitter.com/2/users/me";
7371
+ var AUTHORIZATION_URL16 = "https://twitter.com/i/oauth2/authorize";
7372
+ var TOKEN_URL16 = "https://api.twitter.com/2/oauth2/token";
7373
+ var USER_INFO_URL13 = "https://api.twitter.com/2/users/me";
6070
7374
  var USER_FIELDS = "id,name,username,profile_image_url";
6071
- var DEFAULT_SCOPES9 = ["users.read", "tweet.read"];
7375
+ var DEFAULT_SCOPES11 = ["users.read", "tweet.read"];
6072
7376
  function createTwitterProvider(config) {
6073
- const scopes = mergeScopes9(DEFAULT_SCOPES9, config.scopes);
7377
+ const scopes = mergeScopes16(DEFAULT_SCOPES11, config.scopes);
6074
7378
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6075
7379
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6076
7380
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6083,7 +7387,7 @@ function createTwitterProvider(config) {
6083
7387
  code_challenge: codeChallenge,
6084
7388
  code_challenge_method: "S256"
6085
7389
  });
6086
- return `${AUTHORIZATION_URL9}?${params.toString()}`;
7390
+ return `${AUTHORIZATION_URL16}?${params.toString()}`;
6087
7391
  }
6088
7392
  async function exchangeCode(code, codeVerifier, redirectUri) {
6089
7393
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6094,7 +7398,7 @@ function createTwitterProvider(config) {
6094
7398
  redirect_uri: effectiveRedirectUri
6095
7399
  });
6096
7400
  const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
6097
- const response = await fetch(TOKEN_URL9, {
7401
+ const response = await fetch(TOKEN_URL16, {
6098
7402
  method: "POST",
6099
7403
  headers: {
6100
7404
  "Content-Type": "application/x-www-form-urlencoded",
@@ -6117,7 +7421,7 @@ function createTwitterProvider(config) {
6117
7421
  };
6118
7422
  }
6119
7423
  async function getUserInfo(accessToken) {
6120
- const url = `${USER_INFO_URL7}?user.fields=${USER_FIELDS}`;
7424
+ const url = `${USER_INFO_URL13}?user.fields=${USER_FIELDS}`;
6121
7425
  const response = await fetch(url, {
6122
7426
  headers: { Authorization: `Bearer ${accessToken}` }
6123
7427
  });
@@ -6149,16 +7453,113 @@ function createTwitterProvider(config) {
6149
7453
  return {
6150
7454
  id: "twitter",
6151
7455
  name: "Twitter",
6152
- authorizationUrl: AUTHORIZATION_URL9,
6153
- tokenUrl: TOKEN_URL9,
6154
- userInfoUrl: USER_INFO_URL7,
7456
+ authorizationUrl: AUTHORIZATION_URL16,
7457
+ tokenUrl: TOKEN_URL16,
7458
+ userInfoUrl: USER_INFO_URL13,
6155
7459
  scopes,
6156
7460
  getAuthorizationUrl,
6157
7461
  exchangeCode,
6158
7462
  getUserInfo
6159
7463
  };
6160
7464
  }
6161
- function mergeScopes9(defaults, extras) {
7465
+ function mergeScopes16(defaults, extras) {
7466
+ if (!extras || extras.length === 0) return defaults;
7467
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
7468
+ return [...merged];
7469
+ }
7470
+
7471
+ // src/auth/oauth/providers/zoom.ts
7472
+ var AUTHORIZATION_URL17 = "https://zoom.us/oauth/authorize";
7473
+ var TOKEN_URL17 = "https://zoom.us/oauth/token";
7474
+ var USER_INFO_URL14 = "https://api.zoom.us/v2/users/me";
7475
+ var DEFAULT_ZOOM_SCOPES = ["user:read"];
7476
+ function normalizeProfile10(raw) {
7477
+ const data = raw;
7478
+ if (!data.id) {
7479
+ throw new Error("Zoom user response missing required field: id");
7480
+ }
7481
+ if (!data.email) {
7482
+ throw new Error("Zoom user response missing required field: email");
7483
+ }
7484
+ const joinedName = [data.first_name, data.last_name].filter(Boolean).join(" ");
7485
+ const name = data.display_name ?? (joinedName.length > 0 ? joinedName : void 0);
7486
+ return {
7487
+ id: data.id,
7488
+ email: data.email,
7489
+ name,
7490
+ avatar: data.pic_url,
7491
+ raw
7492
+ };
7493
+ }
7494
+ function createZoomProvider(config) {
7495
+ const scopes = mergeScopes17(DEFAULT_ZOOM_SCOPES, config.scopes);
7496
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
7497
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
7498
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7499
+ const params = new URLSearchParams({
7500
+ client_id: config.clientId,
7501
+ redirect_uri: effectiveRedirectUri,
7502
+ response_type: "code",
7503
+ scope: scopes.join(" "),
7504
+ state,
7505
+ code_challenge: codeChallenge,
7506
+ code_challenge_method: "S256"
7507
+ });
7508
+ return `${AUTHORIZATION_URL17}?${params.toString()}`;
7509
+ }
7510
+ async function exchangeCode(code, codeVerifier, redirectUri) {
7511
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7512
+ const body = new URLSearchParams({
7513
+ grant_type: "authorization_code",
7514
+ client_id: config.clientId,
7515
+ client_secret: config.clientSecret,
7516
+ code,
7517
+ code_verifier: codeVerifier,
7518
+ redirect_uri: effectiveRedirectUri
7519
+ });
7520
+ const response = await fetch(TOKEN_URL17, {
7521
+ method: "POST",
7522
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
7523
+ body: body.toString()
7524
+ });
7525
+ if (!response.ok) {
7526
+ const text3 = await response.text();
7527
+ throw new Error(`Zoom token exchange failed (${response.status}): ${text3}`);
7528
+ }
7529
+ const raw = await response.json();
7530
+ const data = raw;
7531
+ return {
7532
+ accessToken: data.access_token,
7533
+ refreshToken: data.refresh_token,
7534
+ expiresIn: data.expires_in,
7535
+ tokenType: data.token_type ?? "Bearer",
7536
+ raw
7537
+ };
7538
+ }
7539
+ async function getUserInfo(accessToken) {
7540
+ const response = await fetch(USER_INFO_URL14, {
7541
+ headers: { Authorization: `Bearer ${accessToken}` }
7542
+ });
7543
+ if (!response.ok) {
7544
+ const text3 = await response.text();
7545
+ throw new Error(`Zoom /v2/users/me fetch failed (${response.status}): ${text3}`);
7546
+ }
7547
+ const raw = await response.json();
7548
+ return normalizeProfile10(raw);
7549
+ }
7550
+ return {
7551
+ id: "zoom",
7552
+ name: "Zoom",
7553
+ authorizationUrl: AUTHORIZATION_URL17,
7554
+ tokenUrl: TOKEN_URL17,
7555
+ userInfoUrl: USER_INFO_URL14,
7556
+ scopes,
7557
+ getAuthorizationUrl,
7558
+ exchangeCode,
7559
+ getUserInfo
7560
+ };
7561
+ }
7562
+ function mergeScopes17(defaults, extras) {
6162
7563
  if (!extras || extras.length === 0) return defaults;
6163
7564
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6164
7565
  return [...merged];
@@ -6433,7 +7834,7 @@ var DEFAULT_REFRESH_TOKEN_TTL = 86400 * 30;
6433
7834
  var DEFAULT_AUTH_CODE_TTL = 600;
6434
7835
  var DEFAULT_ID_TOKEN_TTL = 3600;
6435
7836
  var DEFAULT_SIGNING_ALG = "RS256";
6436
- var DEFAULT_SCOPES10 = ["openid", "profile", "email"];
7837
+ var DEFAULT_SCOPES12 = ["openid", "profile", "email"];
6437
7838
  var CLIENT_ID_BYTE_LENGTH = 16;
6438
7839
  var CLIENT_SECRET_BYTE_LENGTH = 32;
6439
7840
  var AUTH_CODE_BYTE_LENGTH = 32;
@@ -6460,7 +7861,7 @@ function createOidcProviderModule(config, db, getUserClaims) {
6460
7861
  const refreshTokenTtl = config.refreshTokenTtl ?? DEFAULT_REFRESH_TOKEN_TTL;
6461
7862
  const authCodeTtl = config.authCodeTtl ?? DEFAULT_AUTH_CODE_TTL;
6462
7863
  const idTokenTtl = config.idTokenTtl ?? DEFAULT_ID_TOKEN_TTL;
6463
- const supportedScopes = config.supportedScopes ?? DEFAULT_SCOPES10;
7864
+ const supportedScopes = config.supportedScopes ?? DEFAULT_SCOPES12;
6464
7865
  let signingKeyPromise;
6465
7866
  let publicJwkPromise;
6466
7867
  async function getSigningKey() {
@@ -14733,6 +16134,6 @@ function createWebhookModule(configs) {
14733
16134
  return { emit, addEndpoint, listEndpoints };
14734
16135
  }
14735
16136
 
14736
- export { EVENT_TYPES, HibpApiError, HibpBreachedError, KVStore, MemoryStore, OAuthProxyError, OneTapVerifyError, SSO_ERROR, SsoError, additionalFields, admin, anonymousAuth, apiKeys2 as apiKeys, bearerAuth, createAdditionalFieldsModule, createAdminModule, createAnonymousAuthModule, createApiKeyManagerModule, createAppleProvider, createCaptchaModule, createCostAttributionModule, createCustomSessionModule, createDeviceAuthModule, createDiscordProvider, createEmailOtpModule, createEmailVerificationModule, createEphemeralSessionModule, createEventStreamModule, createFederationModule, createGdprModule, createGithubProvider, createGitlabProvider, createGoogleProvider, createHibpModule, createJwtSessionModule, createLastLoginModule, createLinkedInProvider, createMagicLinkModule, createMicrosoftProvider, createOAuthModule, createOAuthProxyModule, createOidcProviderModule, createOneTapModule, createOneTimeTokenModule, createOpenApiModule, createOrgModule, createPasskeyModule, createPasswordResetModule, createPhoneAuthModule, createPolarModule, createRateLimiter, createReBACModule, createScimModule, createSiweModule, createSlackProvider, createSsoModule, createStripeModule, createTotpModule, createTrustedDeviceModule, createTwitterProvider, createUsernameAuthModule, createWebhookModule, customAuth, customSession, deviceAuth, deviceLabelFromRequest, emailOtp, gdpr, headerAuth, kvStore, magicLink, oauth, oauthProxy, oneTap, organization, passkey, polar, rateLimit, scim, siwe, stripe, twoFactor, withRateLimit };
16137
+ export { DEFAULT_ATLASSIAN_SCOPES, DEFAULT_DISCORD_SCOPES, DEFAULT_DROPBOX_SCOPES, DEFAULT_FIGMA_SCOPES, DEFAULT_NOTION_SCOPES, DEFAULT_REDDIT_SCOPES, DEFAULT_SLACK_SCOPES, DEFAULT_SPOTIFY_SCOPES, DEFAULT_TWITCH_SCOPES, DEFAULT_ZOOM_SCOPES, EVENT_TYPES, HibpApiError, HibpBreachedError, KVStore, MemoryStore, OAuthProxyError, OneTapVerifyError, SSO_ERROR, SsoError, additionalFields, admin, anonymousAuth, apiKeys2 as apiKeys, atlassianProvider, auth0Provider, bearerAuth, bitbucketProvider, cognitoProvider, coinbaseProvider, createAdditionalFieldsModule, createAdminModule, createAnonymousAuthModule, createApiKeyManagerModule, createAppleProvider, createAtlassianProvider, createCaptchaModule, createCostAttributionModule, createCustomSessionModule, createDeviceAuthModule, createDiscordProvider, createDropboxProvider, createEmailOtpModule, createEmailVerificationModule, createEphemeralSessionModule, createEventStreamModule, createFederationModule, createFigmaProvider, createGdprModule, createGithubProvider, createGitlabProvider, createGoogleProvider, createHibpModule, createJwtSessionModule, createLastLoginModule, createLinkedInProvider, createMagicLinkModule, createMicrosoftProvider, createNotionProvider, createOAuthModule, createOAuthProxyModule, createOidcProviderModule, createOneTapModule, createOneTimeTokenModule, createOpenApiModule, createOrgModule, createPasskeyModule, createPasswordResetModule, createPhoneAuthModule, createPolarModule, createRateLimiter, createReBACModule, createRedditProvider, createScimModule, createSiweModule, createSlackProvider, createSpotifyProvider, createSsoModule, createStripeModule, createTotpModule, createTrustedDeviceModule, createTwitchProvider, createTwitterProvider, createUsernameAuthModule, createWebhookModule, createZoomProvider, customAuth, customSession, deviceAuth, deviceLabelFromRequest, dropboxProvider, emailOtp, facebookProvider, figmaProvider, gdpr, genericOIDC, headerAuth, huggingfaceProvider, kakaoProvider, kickProvider, kvStore, lineProvider, linearProvider, magicLink, naverProvider, normalizeProfile as normalizeAtlassianProfile, normalizeProfile2 as normalizeDiscordProfile, normalizeProfile3 as normalizeDropboxProfile, normalizeProfile4 as normalizeFigmaProfile, normalizeProfile5 as normalizeNotionProfile, normalizeProfile6 as normalizeRedditProfile, normalizeProfile7 as normalizeSlackProfile, normalizeProfile8 as normalizeSpotifyProfile, normalizeProfile9 as normalizeTwitchProfile, normalizeProfile10 as normalizeZoomProfile, notionProvider, oauth, oauthProxy, oktaProvider, oneTap, organization, passkey, paypalProvider, polar, polarProvider, railwayProvider, rateLimit, redditProvider, robloxProvider, salesforceProvider, scim, siwe, spotifyProvider, stripe, tiktokProvider, twitchProvider, twoFactor, vercelProvider, vkProvider, wechatProvider, withRateLimit, yahooProvider, zoomProvider };
14737
16138
  //# sourceMappingURL=index.js.map
14738
16139
  //# sourceMappingURL=index.js.map