kavachos 0.4.0 → 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.
package/dist/index.js CHANGED
@@ -6036,15 +6036,110 @@ function mergeScopes(defaults, extras) {
6036
6036
  return [...merged];
6037
6037
  }
6038
6038
 
6039
+ // src/auth/oauth/providers/atlassian.ts
6040
+ var AUTHORIZATION_URL2 = "https://auth.atlassian.com/authorize";
6041
+ var TOKEN_URL2 = "https://auth.atlassian.com/oauth/token";
6042
+ var USER_INFO_URL = "https://api.atlassian.com/me";
6043
+ var ATLASSIAN_AUDIENCE = "api.atlassian.com";
6044
+ var DEFAULT_ATLASSIAN_SCOPES = ["read:me"];
6045
+ function normalizeProfile(raw) {
6046
+ const data = raw;
6047
+ if (!data.account_id) {
6048
+ throw new Error("Atlassian user response missing required field: account_id");
6049
+ }
6050
+ return {
6051
+ id: data.account_id,
6052
+ email: data.email,
6053
+ name: data.name,
6054
+ avatar: data.picture,
6055
+ raw
6056
+ };
6057
+ }
6058
+ function createAtlassianProvider(config) {
6059
+ const scopes = mergeScopes2(DEFAULT_ATLASSIAN_SCOPES, config.scopes);
6060
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6061
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6062
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6063
+ const params = new URLSearchParams({
6064
+ client_id: config.clientId,
6065
+ redirect_uri: effectiveRedirectUri,
6066
+ response_type: "code",
6067
+ scope: scopes.join(" "),
6068
+ state,
6069
+ code_challenge: codeChallenge,
6070
+ code_challenge_method: "S256",
6071
+ // Required: instructs Atlassian which API audience to issue tokens for.
6072
+ audience: ATLASSIAN_AUDIENCE,
6073
+ prompt: "consent"
6074
+ });
6075
+ return `${AUTHORIZATION_URL2}?${params.toString()}`;
6076
+ }
6077
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
6078
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6079
+ const response = await fetch(TOKEN_URL2, {
6080
+ method: "POST",
6081
+ headers: { "Content-Type": "application/json" },
6082
+ body: JSON.stringify({
6083
+ grant_type: "authorization_code",
6084
+ client_id: config.clientId,
6085
+ client_secret: config.clientSecret,
6086
+ code: code2,
6087
+ code_verifier: codeVerifier,
6088
+ redirect_uri: effectiveRedirectUri
6089
+ })
6090
+ });
6091
+ if (!response.ok) {
6092
+ const text3 = await response.text();
6093
+ throw new Error(`Atlassian token exchange failed (${response.status}): ${text3}`);
6094
+ }
6095
+ const raw = await response.json();
6096
+ const data = raw;
6097
+ return {
6098
+ accessToken: data.access_token,
6099
+ refreshToken: data.refresh_token,
6100
+ expiresIn: data.expires_in,
6101
+ tokenType: data.token_type ?? "Bearer",
6102
+ raw
6103
+ };
6104
+ }
6105
+ async function getUserInfo(accessToken) {
6106
+ const response = await fetch(USER_INFO_URL, {
6107
+ headers: { Authorization: `Bearer ${accessToken}` }
6108
+ });
6109
+ if (!response.ok) {
6110
+ const text3 = await response.text();
6111
+ throw new Error(`Atlassian /me fetch failed (${response.status}): ${text3}`);
6112
+ }
6113
+ const raw = await response.json();
6114
+ return normalizeProfile(raw);
6115
+ }
6116
+ return {
6117
+ id: "atlassian",
6118
+ name: "Atlassian",
6119
+ authorizationUrl: AUTHORIZATION_URL2,
6120
+ tokenUrl: TOKEN_URL2,
6121
+ userInfoUrl: USER_INFO_URL,
6122
+ scopes,
6123
+ getAuthorizationUrl,
6124
+ exchangeCode,
6125
+ getUserInfo
6126
+ };
6127
+ }
6128
+ function mergeScopes2(defaults, extras) {
6129
+ if (!extras || extras.length === 0) return defaults;
6130
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6131
+ return [...merged];
6132
+ }
6133
+
6039
6134
  // src/auth/oauth/providers/discord.ts
6040
- var AUTHORIZATION_URL2 = "https://discord.com/api/oauth2/authorize";
6041
- var TOKEN_URL2 = "https://discord.com/api/oauth2/token";
6042
- var USER_INFO_URL = "https://discord.com/api/users/@me";
6135
+ var AUTHORIZATION_URL3 = "https://discord.com/api/oauth2/authorize";
6136
+ var TOKEN_URL3 = "https://discord.com/api/oauth2/token";
6137
+ var USER_INFO_URL2 = "https://discord.com/api/users/@me";
6043
6138
  var CDN_BASE = "https://cdn.discordapp.com";
6044
6139
  var DEFAULT_DISCORD_SCOPES = ["identify", "email"];
6045
6140
  var DEFAULT_SCOPES2 = DEFAULT_DISCORD_SCOPES;
6046
6141
  function createDiscordProvider(config) {
6047
- const scopes = mergeScopes2(DEFAULT_SCOPES2, config.scopes);
6142
+ const scopes = mergeScopes3(DEFAULT_SCOPES2, config.scopes);
6048
6143
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6049
6144
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6050
6145
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6057,7 +6152,7 @@ function createDiscordProvider(config) {
6057
6152
  code_challenge: codeChallenge,
6058
6153
  code_challenge_method: "S256"
6059
6154
  });
6060
- return `${AUTHORIZATION_URL2}?${params.toString()}`;
6155
+ return `${AUTHORIZATION_URL3}?${params.toString()}`;
6061
6156
  }
6062
6157
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6063
6158
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6069,7 +6164,7 @@ function createDiscordProvider(config) {
6069
6164
  code_verifier: codeVerifier,
6070
6165
  redirect_uri: effectiveRedirectUri
6071
6166
  });
6072
- const response = await fetch(TOKEN_URL2, {
6167
+ const response = await fetch(TOKEN_URL3, {
6073
6168
  method: "POST",
6074
6169
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6075
6170
  body: body.toString()
@@ -6089,7 +6184,7 @@ function createDiscordProvider(config) {
6089
6184
  };
6090
6185
  }
6091
6186
  async function getUserInfo(accessToken) {
6092
- const response = await fetch(USER_INFO_URL, {
6187
+ const response = await fetch(USER_INFO_URL2, {
6093
6188
  headers: { Authorization: `Bearer ${accessToken}` }
6094
6189
  });
6095
6190
  if (!response.ok) {
@@ -6117,15 +6212,33 @@ function createDiscordProvider(config) {
6117
6212
  return {
6118
6213
  id: "discord",
6119
6214
  name: "Discord",
6120
- authorizationUrl: AUTHORIZATION_URL2,
6121
- tokenUrl: TOKEN_URL2,
6122
- userInfoUrl: USER_INFO_URL,
6215
+ authorizationUrl: AUTHORIZATION_URL3,
6216
+ tokenUrl: TOKEN_URL3,
6217
+ userInfoUrl: USER_INFO_URL2,
6123
6218
  scopes,
6124
6219
  getAuthorizationUrl,
6125
6220
  exchangeCode,
6126
6221
  getUserInfo
6127
6222
  };
6128
6223
  }
6224
+ function normalizeProfile2(raw) {
6225
+ const data = raw;
6226
+ if (!data.id) {
6227
+ throw new Error("Discord user response missing required field: id");
6228
+ }
6229
+ if (!data.email) {
6230
+ throw new Error("Discord user response has no email. Ensure the `email` scope is granted.");
6231
+ }
6232
+ const avatar = buildAvatarUrl(data.id, data.avatar);
6233
+ const name = data.global_name ?? buildLegacyName(data.username, data.discriminator);
6234
+ return {
6235
+ id: data.id,
6236
+ email: data.email,
6237
+ name,
6238
+ avatar,
6239
+ raw
6240
+ };
6241
+ }
6129
6242
  function buildAvatarUrl(userId, avatarHash) {
6130
6243
  if (!avatarHash) return void 0;
6131
6244
  const ext = avatarHash.startsWith("a_") ? "gif" : "png";
@@ -6137,135 +6250,138 @@ function buildLegacyName(username, discriminator) {
6137
6250
  }
6138
6251
  return username;
6139
6252
  }
6140
- function mergeScopes2(defaults, extras) {
6253
+ function mergeScopes3(defaults, extras) {
6141
6254
  if (!extras || extras.length === 0) return defaults;
6142
6255
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6143
6256
  return [...merged];
6144
6257
  }
6145
6258
 
6146
- // src/auth/oauth/providers/github.ts
6147
- var AUTHORIZATION_URL3 = "https://github.com/login/oauth/authorize";
6148
- var TOKEN_URL3 = "https://github.com/login/oauth/access_token";
6149
- var USER_URL = "https://api.github.com/user";
6150
- var USER_EMAILS_URL = "https://api.github.com/user/emails";
6151
- var DEFAULT_SCOPES3 = ["user:email"];
6152
- function createGithubProvider(config) {
6153
- const scopes = mergeScopes3(DEFAULT_SCOPES3, config.scopes);
6259
+ // src/auth/oauth/providers/dropbox.ts
6260
+ var AUTHORIZATION_URL4 = "https://www.dropbox.com/oauth2/authorize";
6261
+ var TOKEN_URL4 = "https://api.dropboxapi.com/oauth2/token";
6262
+ var USER_INFO_URL3 = "https://api.dropboxapi.com/2/users/get_current_account";
6263
+ var DEFAULT_DROPBOX_SCOPES = ["account_info.read"];
6264
+ function normalizeProfile3(raw) {
6265
+ const data = raw;
6266
+ if (!data.account_id) {
6267
+ throw new Error("Dropbox user response missing required field: account_id");
6268
+ }
6269
+ if (!data.email) {
6270
+ throw new Error("Dropbox user response missing required field: email");
6271
+ }
6272
+ return {
6273
+ id: data.account_id,
6274
+ email: data.email,
6275
+ name: data.name?.display_name,
6276
+ avatar: data.profile_photo_url ?? data.profile_photo?.url,
6277
+ raw
6278
+ };
6279
+ }
6280
+ function createDropboxProvider(config) {
6281
+ const scopes = mergeScopes4(DEFAULT_DROPBOX_SCOPES, config.scopes);
6154
6282
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6155
6283
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6156
6284
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6157
6285
  const params = new URLSearchParams({
6158
6286
  client_id: config.clientId,
6159
6287
  redirect_uri: effectiveRedirectUri,
6288
+ response_type: "code",
6160
6289
  scope: scopes.join(" "),
6161
6290
  state,
6162
- // Included for symmetry; GitHub ignores unknown parameters.
6163
6291
  code_challenge: codeChallenge,
6164
- code_challenge_method: "S256"
6292
+ code_challenge_method: "S256",
6293
+ token_access_type: "offline"
6165
6294
  });
6166
- return `${AUTHORIZATION_URL3}?${params.toString()}`;
6295
+ return `${AUTHORIZATION_URL4}?${params.toString()}`;
6167
6296
  }
6168
6297
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6169
6298
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6170
6299
  const body = new URLSearchParams({
6300
+ grant_type: "authorization_code",
6171
6301
  client_id: config.clientId,
6172
6302
  client_secret: config.clientSecret,
6173
6303
  code: code2,
6174
- redirect_uri: effectiveRedirectUri,
6175
- code_verifier: codeVerifier
6304
+ code_verifier: codeVerifier,
6305
+ redirect_uri: effectiveRedirectUri
6176
6306
  });
6177
- const response = await fetch(TOKEN_URL3, {
6307
+ const response = await fetch(TOKEN_URL4, {
6178
6308
  method: "POST",
6179
- headers: {
6180
- "Content-Type": "application/x-www-form-urlencoded",
6181
- Accept: "application/json"
6182
- },
6309
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6183
6310
  body: body.toString()
6184
6311
  });
6185
6312
  if (!response.ok) {
6186
6313
  const text3 = await response.text();
6187
- throw new Error(`GitHub token exchange failed (${response.status}): ${text3}`);
6314
+ throw new Error(`Dropbox token exchange failed (${response.status}): ${text3}`);
6188
6315
  }
6189
6316
  const raw = await response.json();
6190
6317
  const data = raw;
6191
- if (data.error) {
6192
- throw new Error(
6193
- `GitHub token exchange error: ${data.error} \u2014 ${data.error_description ?? ""}`
6194
- );
6195
- }
6196
- if (!data.access_token) {
6197
- throw new Error("GitHub token exchange returned no access_token");
6198
- }
6199
6318
  return {
6200
6319
  accessToken: data.access_token,
6201
6320
  refreshToken: data.refresh_token,
6202
6321
  expiresIn: data.expires_in,
6203
- tokenType: data.token_type ?? "bearer",
6322
+ tokenType: data.token_type ?? "Bearer",
6204
6323
  raw
6205
6324
  };
6206
6325
  }
6207
6326
  async function getUserInfo(accessToken) {
6208
- const headers = {
6209
- Authorization: `Bearer ${accessToken}`,
6210
- Accept: "application/vnd.github+json",
6211
- "X-GitHub-Api-Version": "2022-11-28"
6212
- };
6213
- const profileResponse = await fetch(USER_URL, { headers });
6214
- if (!profileResponse.ok) {
6215
- const text3 = await profileResponse.text();
6216
- throw new Error(`GitHub user fetch failed (${profileResponse.status}): ${text3}`);
6217
- }
6218
- const raw = await profileResponse.json();
6219
- const profile = raw;
6220
- let email = typeof profile.email === "string" ? profile.email : null;
6221
- if (!email) {
6222
- email = await fetchPrimaryEmail(accessToken, headers);
6223
- }
6224
- if (!email) {
6327
+ const response = await fetch(USER_INFO_URL3, {
6328
+ method: "POST",
6329
+ headers: {
6330
+ Authorization: `Bearer ${accessToken}`,
6331
+ "Content-Type": "application/json"
6332
+ },
6333
+ body: "null"
6334
+ });
6335
+ if (!response.ok) {
6336
+ const text3 = await response.text();
6225
6337
  throw new Error(
6226
- "GitHub user has no accessible email. Ensure the user:email scope is granted."
6338
+ `Dropbox /2/users/get_current_account fetch failed (${response.status}): ${text3}`
6227
6339
  );
6228
6340
  }
6229
- return {
6230
- id: String(profile.id),
6231
- email,
6232
- name: profile.name ?? profile.login,
6233
- avatar: profile.avatar_url,
6234
- raw
6235
- };
6341
+ const raw = await response.json();
6342
+ return normalizeProfile3(raw);
6236
6343
  }
6237
6344
  return {
6238
- id: "github",
6239
- name: "GitHub",
6240
- authorizationUrl: AUTHORIZATION_URL3,
6241
- tokenUrl: TOKEN_URL3,
6242
- userInfoUrl: USER_URL,
6345
+ id: "dropbox",
6346
+ name: "Dropbox",
6347
+ authorizationUrl: AUTHORIZATION_URL4,
6348
+ tokenUrl: TOKEN_URL4,
6349
+ userInfoUrl: USER_INFO_URL3,
6243
6350
  scopes,
6244
6351
  getAuthorizationUrl,
6245
6352
  exchangeCode,
6246
6353
  getUserInfo
6247
6354
  };
6248
6355
  }
6249
- async function fetchPrimaryEmail(_accessToken, headers) {
6250
- const response = await fetch(USER_EMAILS_URL, { headers });
6251
- if (!response.ok) return null;
6252
- const emails = await response.json();
6253
- const primary = emails.find((e) => e.primary && e.verified);
6254
- return primary?.email ?? null;
6255
- }
6256
- function mergeScopes3(defaults, extras) {
6356
+ function mergeScopes4(defaults, extras) {
6257
6357
  if (!extras || extras.length === 0) return defaults;
6258
6358
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6259
6359
  return [...merged];
6260
6360
  }
6261
6361
 
6262
- // src/auth/oauth/providers/gitlab.ts
6263
- var AUTHORIZATION_URL4 = "https://gitlab.com/oauth/authorize";
6264
- var TOKEN_URL4 = "https://gitlab.com/oauth/token";
6265
- var USER_INFO_URL2 = "https://gitlab.com/api/v4/user";
6266
- var DEFAULT_SCOPES4 = ["read_user"];
6267
- function createGitlabProvider(config) {
6268
- const scopes = mergeScopes4(DEFAULT_SCOPES4, config.scopes);
6362
+ // src/auth/oauth/providers/figma.ts
6363
+ var AUTHORIZATION_URL5 = "https://www.figma.com/oauth";
6364
+ var TOKEN_URL5 = "https://api.figma.com/v1/oauth/token";
6365
+ var USER_INFO_URL4 = "https://api.figma.com/v1/me";
6366
+ var DEFAULT_FIGMA_SCOPES = ["file_read"];
6367
+ function normalizeProfile4(raw) {
6368
+ const data = raw;
6369
+ if (!data.id) {
6370
+ throw new Error("Figma user response missing required field: id");
6371
+ }
6372
+ if (!data.email) {
6373
+ throw new Error("Figma user response missing required field: email");
6374
+ }
6375
+ return {
6376
+ id: data.id,
6377
+ email: data.email,
6378
+ name: data.handle,
6379
+ avatar: data.img_url,
6380
+ raw
6381
+ };
6382
+ }
6383
+ function createFigmaProvider(config) {
6384
+ const scopes = mergeScopes5(DEFAULT_FIGMA_SCOPES, config.scopes);
6269
6385
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6270
6386
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6271
6387
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6278,26 +6394,26 @@ function createGitlabProvider(config) {
6278
6394
  code_challenge: codeChallenge,
6279
6395
  code_challenge_method: "S256"
6280
6396
  });
6281
- return `${AUTHORIZATION_URL4}?${params.toString()}`;
6397
+ return `${AUTHORIZATION_URL5}?${params.toString()}`;
6282
6398
  }
6283
6399
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6284
6400
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6285
6401
  const body = new URLSearchParams({
6286
- grant_type: "authorization_code",
6287
6402
  client_id: config.clientId,
6288
6403
  client_secret: config.clientSecret,
6404
+ redirect_uri: effectiveRedirectUri,
6289
6405
  code: code2,
6290
6406
  code_verifier: codeVerifier,
6291
- redirect_uri: effectiveRedirectUri
6407
+ grant_type: "authorization_code"
6292
6408
  });
6293
- const response = await fetch(TOKEN_URL4, {
6409
+ const response = await fetch(TOKEN_URL5, {
6294
6410
  method: "POST",
6295
6411
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6296
6412
  body: body.toString()
6297
6413
  });
6298
6414
  if (!response.ok) {
6299
6415
  const text3 = await response.text();
6300
- throw new Error(`GitLab token exchange failed (${response.status}): ${text3}`);
6416
+ throw new Error(`Figma token exchange failed (${response.status}): ${text3}`);
6301
6417
  }
6302
6418
  const raw = await response.json();
6303
6419
  const data = raw;
@@ -6310,58 +6426,84 @@ function createGitlabProvider(config) {
6310
6426
  };
6311
6427
  }
6312
6428
  async function getUserInfo(accessToken) {
6313
- const response = await fetch(USER_INFO_URL2, {
6429
+ const response = await fetch(USER_INFO_URL4, {
6314
6430
  headers: { Authorization: `Bearer ${accessToken}` }
6315
6431
  });
6316
6432
  if (!response.ok) {
6317
6433
  const text3 = await response.text();
6318
- throw new Error(`GitLab /api/v4/user fetch failed (${response.status}): ${text3}`);
6434
+ throw new Error(`Figma /v1/me fetch failed (${response.status}): ${text3}`);
6319
6435
  }
6320
6436
  const raw = await response.json();
6321
- const data = raw;
6322
- if (!data.id) {
6323
- throw new Error("GitLab user response missing required field: id");
6324
- }
6325
- const email = data.email ?? data.public_email;
6326
- if (!email) {
6327
- throw new Error(
6328
- "GitLab user response has no email. The account may have restricted email visibility."
6329
- );
6330
- }
6331
- return {
6332
- id: String(data.id),
6333
- email,
6334
- name: data.name ?? data.username,
6335
- avatar: data.avatar_url,
6336
- raw
6337
- };
6437
+ return normalizeProfile4(raw);
6338
6438
  }
6339
6439
  return {
6340
- id: "gitlab",
6341
- name: "GitLab",
6342
- authorizationUrl: AUTHORIZATION_URL4,
6343
- tokenUrl: TOKEN_URL4,
6344
- userInfoUrl: USER_INFO_URL2,
6440
+ id: "figma",
6441
+ name: "Figma",
6442
+ authorizationUrl: AUTHORIZATION_URL5,
6443
+ tokenUrl: TOKEN_URL5,
6444
+ userInfoUrl: USER_INFO_URL4,
6345
6445
  scopes,
6346
6446
  getAuthorizationUrl,
6347
6447
  exchangeCode,
6348
6448
  getUserInfo
6349
6449
  };
6350
6450
  }
6351
- function mergeScopes4(defaults, extras) {
6451
+ function mergeScopes5(defaults, extras) {
6352
6452
  if (!extras || extras.length === 0) return defaults;
6353
6453
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6354
6454
  return [...merged];
6355
6455
  }
6356
6456
 
6357
- // src/auth/oauth/providers/google.ts
6358
- var AUTHORIZATION_URL5 = "https://accounts.google.com/o/oauth2/v2/auth";
6359
- var TOKEN_URL5 = "https://oauth2.googleapis.com/token";
6360
- var USER_INFO_URL3 = "https://www.googleapis.com/oauth2/v3/userinfo";
6361
- var DEFAULT_SCOPES5 = ["openid", "email", "profile"];
6362
- function createGoogleProvider(config) {
6363
- const scopes = mergeScopes5(DEFAULT_SCOPES5, config.scopes);
6457
+ // src/auth/oauth/providers/generic.ts
6458
+ var discoveryCache = /* @__PURE__ */ new Map();
6459
+ async function discoverEndpoints(issuer) {
6460
+ const cached = discoveryCache.get(issuer);
6461
+ if (cached) return cached;
6462
+ const url = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
6463
+ const response = await fetch(url);
6464
+ if (!response.ok) {
6465
+ throw new Error(
6466
+ `OIDC discovery failed for issuer "${issuer}" (${response.status}). Provide explicit authorizationUrl / tokenUrl / userinfoUrl to skip discovery.`
6467
+ );
6468
+ }
6469
+ const doc = await response.json();
6470
+ const authorizationUrl = assertString(
6471
+ doc.authorization_endpoint,
6472
+ "authorization_endpoint",
6473
+ issuer
6474
+ );
6475
+ const tokenUrl = assertString(doc.token_endpoint, "token_endpoint", issuer);
6476
+ const userinfoUrl = assertString(doc.userinfo_endpoint, "userinfo_endpoint", issuer);
6477
+ const endpoints = { authorizationUrl, tokenUrl, userinfoUrl };
6478
+ discoveryCache.set(issuer, endpoints);
6479
+ return endpoints;
6480
+ }
6481
+ function assertString(value, field, issuer) {
6482
+ if (typeof value !== "string" || value.length === 0) {
6483
+ throw new Error(`OIDC discovery for "${issuer}" returned no "${field}" field.`);
6484
+ }
6485
+ return value;
6486
+ }
6487
+ function genericOIDC(config) {
6488
+ const defaultScopes = ["openid", "email", "profile"];
6489
+ const scopes = mergeScopes6(defaultScopes, config.scopes);
6490
+ async function resolveEndpoints() {
6491
+ if (config.authorizationUrl && config.tokenUrl && config.userinfoUrl) {
6492
+ return {
6493
+ authorizationUrl: config.authorizationUrl,
6494
+ tokenUrl: config.tokenUrl,
6495
+ userinfoUrl: config.userinfoUrl
6496
+ };
6497
+ }
6498
+ const discovered = await discoverEndpoints(config.issuer);
6499
+ return {
6500
+ authorizationUrl: config.authorizationUrl ?? discovered.authorizationUrl,
6501
+ tokenUrl: config.tokenUrl ?? discovered.tokenUrl,
6502
+ userinfoUrl: config.userinfoUrl ?? discovered.userinfoUrl
6503
+ };
6504
+ }
6364
6505
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6506
+ const endpoints = await resolveEndpoints();
6365
6507
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6366
6508
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6367
6509
  const params = new URLSearchParams({
@@ -6371,15 +6513,12 @@ function createGoogleProvider(config) {
6371
6513
  scope: scopes.join(" "),
6372
6514
  state,
6373
6515
  code_challenge: codeChallenge,
6374
- code_challenge_method: "S256",
6375
- access_type: "offline",
6376
- // request refresh token
6377
- prompt: "consent"
6378
- // always show consent to get refresh token reliably
6516
+ code_challenge_method: "S256"
6379
6517
  });
6380
- return `${AUTHORIZATION_URL5}?${params.toString()}`;
6518
+ return `${endpoints.authorizationUrl}?${params.toString()}`;
6381
6519
  }
6382
6520
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6521
+ const endpoints = await resolveEndpoints();
6383
6522
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6384
6523
  const body = new URLSearchParams({
6385
6524
  grant_type: "authorization_code",
@@ -6389,14 +6528,14 @@ function createGoogleProvider(config) {
6389
6528
  code_verifier: codeVerifier,
6390
6529
  redirect_uri: effectiveRedirectUri
6391
6530
  });
6392
- const response = await fetch(TOKEN_URL5, {
6531
+ const response = await fetch(endpoints.tokenUrl, {
6393
6532
  method: "POST",
6394
6533
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6395
6534
  body: body.toString()
6396
6535
  });
6397
6536
  if (!response.ok) {
6398
6537
  const text3 = await response.text();
6399
- throw new Error(`Google token exchange failed (${response.status}): ${text3}`);
6538
+ throw new Error(`${config.name} token exchange failed (${response.status}): ${text3}`);
6400
6539
  }
6401
6540
  const raw = await response.json();
6402
6541
  const data = raw;
@@ -6409,64 +6548,1190 @@ function createGoogleProvider(config) {
6409
6548
  };
6410
6549
  }
6411
6550
  async function getUserInfo(accessToken) {
6412
- const response = await fetch(USER_INFO_URL3, {
6551
+ const endpoints = await resolveEndpoints();
6552
+ const response = await fetch(endpoints.userinfoUrl, {
6413
6553
  headers: { Authorization: `Bearer ${accessToken}` }
6414
6554
  });
6415
6555
  if (!response.ok) {
6416
6556
  const text3 = await response.text();
6417
- throw new Error(`Google userinfo fetch failed (${response.status}): ${text3}`);
6557
+ throw new Error(`${config.name} userinfo fetch failed (${response.status}): ${text3}`);
6558
+ }
6559
+ const raw = await response.json();
6560
+ const data = raw;
6561
+ if (!data.sub) {
6562
+ throw new Error(`${config.name} userinfo response missing required "sub" field.`);
6563
+ }
6564
+ const email = data.email ?? "";
6565
+ if (!email) {
6566
+ throw new Error(
6567
+ `${config.name} userinfo response missing "email". Ensure the 'email' scope is included.`
6568
+ );
6569
+ }
6570
+ return {
6571
+ id: data.sub,
6572
+ email,
6573
+ name: data.name,
6574
+ avatar: data.picture,
6575
+ raw
6576
+ };
6577
+ }
6578
+ const staticAuthUrl = config.authorizationUrl ?? `${config.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
6579
+ return {
6580
+ id: config.id,
6581
+ name: config.name,
6582
+ authorizationUrl: staticAuthUrl,
6583
+ tokenUrl: config.tokenUrl ?? config.issuer,
6584
+ userInfoUrl: config.userinfoUrl ?? config.issuer,
6585
+ scopes,
6586
+ getAuthorizationUrl,
6587
+ exchangeCode,
6588
+ getUserInfo
6589
+ };
6590
+ }
6591
+ function mergeScopes6(defaults, extras) {
6592
+ if (!extras || extras.length === 0) return defaults;
6593
+ return [.../* @__PURE__ */ new Set([...defaults, ...extras])];
6594
+ }
6595
+
6596
+ // src/auth/oauth/providers/github.ts
6597
+ var AUTHORIZATION_URL6 = "https://github.com/login/oauth/authorize";
6598
+ var TOKEN_URL6 = "https://github.com/login/oauth/access_token";
6599
+ var USER_URL = "https://api.github.com/user";
6600
+ var USER_EMAILS_URL = "https://api.github.com/user/emails";
6601
+ var DEFAULT_SCOPES3 = ["user:email"];
6602
+ function createGithubProvider(config) {
6603
+ const scopes = mergeScopes7(DEFAULT_SCOPES3, config.scopes);
6604
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6605
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6606
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6607
+ const params = new URLSearchParams({
6608
+ client_id: config.clientId,
6609
+ redirect_uri: effectiveRedirectUri,
6610
+ scope: scopes.join(" "),
6611
+ state,
6612
+ // Included for symmetry; GitHub ignores unknown parameters.
6613
+ code_challenge: codeChallenge,
6614
+ code_challenge_method: "S256"
6615
+ });
6616
+ return `${AUTHORIZATION_URL6}?${params.toString()}`;
6617
+ }
6618
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
6619
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6620
+ const body = new URLSearchParams({
6621
+ client_id: config.clientId,
6622
+ client_secret: config.clientSecret,
6623
+ code: code2,
6624
+ redirect_uri: effectiveRedirectUri,
6625
+ code_verifier: codeVerifier
6626
+ });
6627
+ const response = await fetch(TOKEN_URL6, {
6628
+ method: "POST",
6629
+ headers: {
6630
+ "Content-Type": "application/x-www-form-urlencoded",
6631
+ Accept: "application/json"
6632
+ },
6633
+ body: body.toString()
6634
+ });
6635
+ if (!response.ok) {
6636
+ const text3 = await response.text();
6637
+ throw new Error(`GitHub token exchange failed (${response.status}): ${text3}`);
6638
+ }
6639
+ const raw = await response.json();
6640
+ const data = raw;
6641
+ if (data.error) {
6642
+ throw new Error(
6643
+ `GitHub token exchange error: ${data.error} \u2014 ${data.error_description ?? ""}`
6644
+ );
6645
+ }
6646
+ if (!data.access_token) {
6647
+ throw new Error("GitHub token exchange returned no access_token");
6648
+ }
6649
+ return {
6650
+ accessToken: data.access_token,
6651
+ refreshToken: data.refresh_token,
6652
+ expiresIn: data.expires_in,
6653
+ tokenType: data.token_type ?? "bearer",
6654
+ raw
6655
+ };
6656
+ }
6657
+ async function getUserInfo(accessToken) {
6658
+ const headers = {
6659
+ Authorization: `Bearer ${accessToken}`,
6660
+ Accept: "application/vnd.github+json",
6661
+ "X-GitHub-Api-Version": "2022-11-28"
6662
+ };
6663
+ const profileResponse = await fetch(USER_URL, { headers });
6664
+ if (!profileResponse.ok) {
6665
+ const text3 = await profileResponse.text();
6666
+ throw new Error(`GitHub user fetch failed (${profileResponse.status}): ${text3}`);
6667
+ }
6668
+ const raw = await profileResponse.json();
6669
+ const profile = raw;
6670
+ let email = typeof profile.email === "string" ? profile.email : null;
6671
+ if (!email) {
6672
+ email = await fetchPrimaryEmail(accessToken, headers);
6673
+ }
6674
+ if (!email) {
6675
+ throw new Error(
6676
+ "GitHub user has no accessible email. Ensure the user:email scope is granted."
6677
+ );
6678
+ }
6679
+ return {
6680
+ id: String(profile.id),
6681
+ email,
6682
+ name: profile.name ?? profile.login,
6683
+ avatar: profile.avatar_url,
6684
+ raw
6685
+ };
6686
+ }
6687
+ return {
6688
+ id: "github",
6689
+ name: "GitHub",
6690
+ authorizationUrl: AUTHORIZATION_URL6,
6691
+ tokenUrl: TOKEN_URL6,
6692
+ userInfoUrl: USER_URL,
6693
+ scopes,
6694
+ getAuthorizationUrl,
6695
+ exchangeCode,
6696
+ getUserInfo
6697
+ };
6698
+ }
6699
+ async function fetchPrimaryEmail(_accessToken, headers) {
6700
+ const response = await fetch(USER_EMAILS_URL, { headers });
6701
+ if (!response.ok) return null;
6702
+ const emails = await response.json();
6703
+ const primary = emails.find((e) => e.primary && e.verified);
6704
+ return primary?.email ?? null;
6705
+ }
6706
+ function mergeScopes7(defaults, extras) {
6707
+ if (!extras || extras.length === 0) return defaults;
6708
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6709
+ return [...merged];
6710
+ }
6711
+
6712
+ // src/auth/oauth/providers/gitlab.ts
6713
+ var AUTHORIZATION_URL7 = "https://gitlab.com/oauth/authorize";
6714
+ var TOKEN_URL7 = "https://gitlab.com/oauth/token";
6715
+ var USER_INFO_URL5 = "https://gitlab.com/api/v4/user";
6716
+ var DEFAULT_SCOPES4 = ["read_user"];
6717
+ function createGitlabProvider(config) {
6718
+ const scopes = mergeScopes8(DEFAULT_SCOPES4, config.scopes);
6719
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6720
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6721
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6722
+ const params = new URLSearchParams({
6723
+ client_id: config.clientId,
6724
+ redirect_uri: effectiveRedirectUri,
6725
+ response_type: "code",
6726
+ scope: scopes.join(" "),
6727
+ state,
6728
+ code_challenge: codeChallenge,
6729
+ code_challenge_method: "S256"
6730
+ });
6731
+ return `${AUTHORIZATION_URL7}?${params.toString()}`;
6732
+ }
6733
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
6734
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6735
+ const body = new URLSearchParams({
6736
+ grant_type: "authorization_code",
6737
+ client_id: config.clientId,
6738
+ client_secret: config.clientSecret,
6739
+ code: code2,
6740
+ code_verifier: codeVerifier,
6741
+ redirect_uri: effectiveRedirectUri
6742
+ });
6743
+ const response = await fetch(TOKEN_URL7, {
6744
+ method: "POST",
6745
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6746
+ body: body.toString()
6747
+ });
6748
+ if (!response.ok) {
6749
+ const text3 = await response.text();
6750
+ throw new Error(`GitLab token exchange failed (${response.status}): ${text3}`);
6751
+ }
6752
+ const raw = await response.json();
6753
+ const data = raw;
6754
+ return {
6755
+ accessToken: data.access_token,
6756
+ refreshToken: data.refresh_token,
6757
+ expiresIn: data.expires_in,
6758
+ tokenType: data.token_type ?? "Bearer",
6759
+ raw
6760
+ };
6761
+ }
6762
+ async function getUserInfo(accessToken) {
6763
+ const response = await fetch(USER_INFO_URL5, {
6764
+ headers: { Authorization: `Bearer ${accessToken}` }
6765
+ });
6766
+ if (!response.ok) {
6767
+ const text3 = await response.text();
6768
+ throw new Error(`GitLab /api/v4/user fetch failed (${response.status}): ${text3}`);
6769
+ }
6770
+ const raw = await response.json();
6771
+ const data = raw;
6772
+ if (!data.id) {
6773
+ throw new Error("GitLab user response missing required field: id");
6774
+ }
6775
+ const email = data.email ?? data.public_email;
6776
+ if (!email) {
6777
+ throw new Error(
6778
+ "GitLab user response has no email. The account may have restricted email visibility."
6779
+ );
6780
+ }
6781
+ return {
6782
+ id: String(data.id),
6783
+ email,
6784
+ name: data.name ?? data.username,
6785
+ avatar: data.avatar_url,
6786
+ raw
6787
+ };
6788
+ }
6789
+ return {
6790
+ id: "gitlab",
6791
+ name: "GitLab",
6792
+ authorizationUrl: AUTHORIZATION_URL7,
6793
+ tokenUrl: TOKEN_URL7,
6794
+ userInfoUrl: USER_INFO_URL5,
6795
+ scopes,
6796
+ getAuthorizationUrl,
6797
+ exchangeCode,
6798
+ getUserInfo
6799
+ };
6800
+ }
6801
+ function mergeScopes8(defaults, extras) {
6802
+ if (!extras || extras.length === 0) return defaults;
6803
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6804
+ return [...merged];
6805
+ }
6806
+
6807
+ // src/auth/oauth/providers/google.ts
6808
+ var AUTHORIZATION_URL8 = "https://accounts.google.com/o/oauth2/v2/auth";
6809
+ var TOKEN_URL8 = "https://oauth2.googleapis.com/token";
6810
+ var USER_INFO_URL6 = "https://www.googleapis.com/oauth2/v3/userinfo";
6811
+ var DEFAULT_SCOPES5 = ["openid", "email", "profile"];
6812
+ function createGoogleProvider(config) {
6813
+ const scopes = mergeScopes9(DEFAULT_SCOPES5, config.scopes);
6814
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6815
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6816
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6817
+ const params = new URLSearchParams({
6818
+ client_id: config.clientId,
6819
+ redirect_uri: effectiveRedirectUri,
6820
+ response_type: "code",
6821
+ scope: scopes.join(" "),
6822
+ state,
6823
+ code_challenge: codeChallenge,
6824
+ code_challenge_method: "S256",
6825
+ access_type: "offline",
6826
+ // request refresh token
6827
+ prompt: "consent"
6828
+ // always show consent to get refresh token reliably
6829
+ });
6830
+ return `${AUTHORIZATION_URL8}?${params.toString()}`;
6831
+ }
6832
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
6833
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6834
+ const body = new URLSearchParams({
6835
+ grant_type: "authorization_code",
6836
+ client_id: config.clientId,
6837
+ client_secret: config.clientSecret,
6838
+ code: code2,
6839
+ code_verifier: codeVerifier,
6840
+ redirect_uri: effectiveRedirectUri
6841
+ });
6842
+ const response = await fetch(TOKEN_URL8, {
6843
+ method: "POST",
6844
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6845
+ body: body.toString()
6846
+ });
6847
+ if (!response.ok) {
6848
+ const text3 = await response.text();
6849
+ throw new Error(`Google token exchange failed (${response.status}): ${text3}`);
6850
+ }
6851
+ const raw = await response.json();
6852
+ const data = raw;
6853
+ return {
6854
+ accessToken: data.access_token,
6855
+ refreshToken: data.refresh_token,
6856
+ expiresIn: data.expires_in,
6857
+ tokenType: data.token_type ?? "Bearer",
6858
+ raw
6859
+ };
6860
+ }
6861
+ async function getUserInfo(accessToken) {
6862
+ const response = await fetch(USER_INFO_URL6, {
6863
+ headers: { Authorization: `Bearer ${accessToken}` }
6864
+ });
6865
+ if (!response.ok) {
6866
+ const text3 = await response.text();
6867
+ throw new Error(`Google userinfo fetch failed (${response.status}): ${text3}`);
6868
+ }
6869
+ const raw = await response.json();
6870
+ const data = raw;
6871
+ if (!data.sub || !data.email) {
6872
+ throw new Error("Google userinfo response missing required fields: sub, email");
6873
+ }
6874
+ return {
6875
+ id: data.sub,
6876
+ email: data.email,
6877
+ name: data.name,
6878
+ avatar: data.picture,
6879
+ raw
6880
+ };
6881
+ }
6882
+ return {
6883
+ id: "google",
6884
+ name: "Google",
6885
+ authorizationUrl: AUTHORIZATION_URL8,
6886
+ tokenUrl: TOKEN_URL8,
6887
+ userInfoUrl: USER_INFO_URL6,
6888
+ scopes,
6889
+ getAuthorizationUrl,
6890
+ exchangeCode,
6891
+ getUserInfo
6892
+ };
6893
+ }
6894
+ function mergeScopes9(defaults, extras) {
6895
+ if (!extras || extras.length === 0) return defaults;
6896
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6897
+ return [...merged];
6898
+ }
6899
+
6900
+ // src/auth/oauth/providers/linkedin.ts
6901
+ var AUTHORIZATION_URL9 = "https://www.linkedin.com/oauth/v2/authorization";
6902
+ var TOKEN_URL9 = "https://www.linkedin.com/oauth/v2/accessToken";
6903
+ var USER_INFO_URL7 = "https://api.linkedin.com/v2/userinfo";
6904
+ var DEFAULT_SCOPES6 = ["openid", "profile", "email"];
6905
+ function createLinkedInProvider(config) {
6906
+ const scopes = mergeScopes10(DEFAULT_SCOPES6, config.scopes);
6907
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6908
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
6909
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6910
+ const params = new URLSearchParams({
6911
+ response_type: "code",
6912
+ client_id: config.clientId,
6913
+ redirect_uri: effectiveRedirectUri,
6914
+ scope: scopes.join(" "),
6915
+ state,
6916
+ code_challenge: codeChallenge,
6917
+ code_challenge_method: "S256"
6918
+ });
6919
+ return `${AUTHORIZATION_URL9}?${params.toString()}`;
6920
+ }
6921
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
6922
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6923
+ const body = new URLSearchParams({
6924
+ grant_type: "authorization_code",
6925
+ client_id: config.clientId,
6926
+ client_secret: config.clientSecret,
6927
+ code: code2,
6928
+ redirect_uri: effectiveRedirectUri,
6929
+ code_verifier: codeVerifier
6930
+ });
6931
+ const response = await fetch(TOKEN_URL9, {
6932
+ method: "POST",
6933
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6934
+ body: body.toString()
6935
+ });
6936
+ if (!response.ok) {
6937
+ const text3 = await response.text();
6938
+ throw new Error(`LinkedIn token exchange failed (${response.status}): ${text3}`);
6939
+ }
6940
+ const raw = await response.json();
6941
+ const data = raw;
6942
+ return {
6943
+ accessToken: data.access_token,
6944
+ refreshToken: data.refresh_token,
6945
+ expiresIn: data.expires_in,
6946
+ tokenType: data.token_type ?? "Bearer",
6947
+ raw
6948
+ };
6949
+ }
6950
+ async function getUserInfo(accessToken) {
6951
+ const response = await fetch(USER_INFO_URL7, {
6952
+ headers: { Authorization: `Bearer ${accessToken}` }
6953
+ });
6954
+ if (!response.ok) {
6955
+ const text3 = await response.text();
6956
+ throw new Error(`LinkedIn /v2/userinfo fetch failed (${response.status}): ${text3}`);
6957
+ }
6958
+ const raw = await response.json();
6959
+ const data = raw;
6960
+ if (!data.sub) {
6961
+ throw new Error("LinkedIn userinfo response missing required field: sub");
6962
+ }
6963
+ if (!data.email) {
6964
+ throw new Error(
6965
+ "LinkedIn userinfo response has no email. Ensure the `email` and `openid` scopes are granted."
6966
+ );
6967
+ }
6968
+ const name = data.name ?? ([data.given_name, data.family_name].filter(Boolean).join(" ") || void 0);
6969
+ return {
6970
+ id: data.sub,
6971
+ email: data.email,
6972
+ name,
6973
+ avatar: data.picture,
6974
+ raw
6975
+ };
6976
+ }
6977
+ return {
6978
+ id: "linkedin",
6979
+ name: "LinkedIn",
6980
+ authorizationUrl: AUTHORIZATION_URL9,
6981
+ tokenUrl: TOKEN_URL9,
6982
+ userInfoUrl: USER_INFO_URL7,
6983
+ scopes,
6984
+ getAuthorizationUrl,
6985
+ exchangeCode,
6986
+ getUserInfo
6987
+ };
6988
+ }
6989
+ function mergeScopes10(defaults, extras) {
6990
+ if (!extras || extras.length === 0) return defaults;
6991
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6992
+ return [...merged];
6993
+ }
6994
+
6995
+ // src/auth/oauth/providers/microsoft.ts
6996
+ var AUTHORIZATION_URL10 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
6997
+ var TOKEN_URL10 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
6998
+ var USER_INFO_URL8 = "https://graph.microsoft.com/v1.0/me";
6999
+ var DEFAULT_SCOPES7 = ["openid", "profile", "email", "User.Read"];
7000
+ function createMicrosoftProvider(config) {
7001
+ const scopes = mergeScopes11(DEFAULT_SCOPES7, config.scopes);
7002
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
7003
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
7004
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7005
+ const params = new URLSearchParams({
7006
+ client_id: config.clientId,
7007
+ redirect_uri: effectiveRedirectUri,
7008
+ response_type: "code",
7009
+ scope: scopes.join(" "),
7010
+ state,
7011
+ code_challenge: codeChallenge,
7012
+ code_challenge_method: "S256"
7013
+ });
7014
+ return `${AUTHORIZATION_URL10}?${params.toString()}`;
7015
+ }
7016
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
7017
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7018
+ const body = new URLSearchParams({
7019
+ grant_type: "authorization_code",
7020
+ client_id: config.clientId,
7021
+ client_secret: config.clientSecret,
7022
+ code: code2,
7023
+ code_verifier: codeVerifier,
7024
+ redirect_uri: effectiveRedirectUri
7025
+ });
7026
+ const response = await fetch(TOKEN_URL10, {
7027
+ method: "POST",
7028
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
7029
+ body: body.toString()
7030
+ });
7031
+ if (!response.ok) {
7032
+ const text3 = await response.text();
7033
+ throw new Error(`Microsoft token exchange failed (${response.status}): ${text3}`);
7034
+ }
7035
+ const raw = await response.json();
7036
+ const data = raw;
7037
+ return {
7038
+ accessToken: data.access_token,
7039
+ refreshToken: data.refresh_token,
7040
+ expiresIn: data.expires_in,
7041
+ tokenType: data.token_type ?? "Bearer",
7042
+ raw
7043
+ };
7044
+ }
7045
+ async function getUserInfo(accessToken) {
7046
+ const response = await fetch(USER_INFO_URL8, {
7047
+ headers: { Authorization: `Bearer ${accessToken}` }
7048
+ });
7049
+ if (!response.ok) {
7050
+ const text3 = await response.text();
7051
+ throw new Error(`Microsoft Graph /me fetch failed (${response.status}): ${text3}`);
7052
+ }
7053
+ const raw = await response.json();
7054
+ const data = raw;
7055
+ if (!data.id) {
7056
+ throw new Error("Microsoft Graph /me response missing required field: id");
7057
+ }
7058
+ const email = data.mail ?? data.userPrincipalName;
7059
+ if (!email) {
7060
+ throw new Error(
7061
+ "Microsoft Graph /me response has no email or userPrincipalName. Ensure the `email` and `User.Read` scopes are granted."
7062
+ );
7063
+ }
7064
+ const name = data.displayName ?? ([data.givenName, data.surname].filter(Boolean).join(" ") || void 0);
7065
+ return {
7066
+ id: data.id,
7067
+ email,
7068
+ name,
7069
+ avatar: void 0,
7070
+ // Graph photo requires a separate /me/photo/$value call
7071
+ raw
7072
+ };
7073
+ }
7074
+ return {
7075
+ id: "microsoft",
7076
+ name: "Microsoft",
7077
+ authorizationUrl: AUTHORIZATION_URL10,
7078
+ tokenUrl: TOKEN_URL10,
7079
+ userInfoUrl: USER_INFO_URL8,
7080
+ scopes,
7081
+ getAuthorizationUrl,
7082
+ exchangeCode,
7083
+ getUserInfo
7084
+ };
7085
+ }
7086
+ function mergeScopes11(defaults, extras) {
7087
+ if (!extras || extras.length === 0) return defaults;
7088
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
7089
+ return [...merged];
7090
+ }
7091
+
7092
+ // src/auth/oauth/providers/notion.ts
7093
+ var AUTHORIZATION_URL11 = "https://api.notion.com/v1/oauth/authorize";
7094
+ var TOKEN_URL11 = "https://api.notion.com/v1/oauth/token";
7095
+ var NOTION_VERSION = "2022-06-28";
7096
+ var DEFAULT_NOTION_SCOPES = [];
7097
+ function normalizeProfile5(raw) {
7098
+ const tokenData = raw;
7099
+ const user = tokenData?.owner?.user;
7100
+ if (!user?.id) {
7101
+ throw new Error(
7102
+ "Notion token response missing owner.user.id. Ensure the integration is authorized by a person, not a workspace bot."
7103
+ );
7104
+ }
7105
+ return {
7106
+ id: user.id,
7107
+ email: user.person?.email,
7108
+ name: user.name,
7109
+ avatar: user.avatar_url ?? void 0,
7110
+ raw
7111
+ };
7112
+ }
7113
+ function createNotionProvider(config) {
7114
+ const scopes = [];
7115
+ let lastTokenRaw = null;
7116
+ async function getAuthorizationUrl(state, _codeVerifier, redirectUri) {
7117
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7118
+ const params = new URLSearchParams({
7119
+ client_id: config.clientId,
7120
+ redirect_uri: effectiveRedirectUri,
7121
+ response_type: "code",
7122
+ owner: "user",
7123
+ state
7124
+ });
7125
+ return `${AUTHORIZATION_URL11}?${params.toString()}`;
7126
+ }
7127
+ async function exchangeCode(code2, _codeVerifier, redirectUri) {
7128
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7129
+ const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
7130
+ const response = await fetch(TOKEN_URL11, {
7131
+ method: "POST",
7132
+ headers: {
7133
+ "Content-Type": "application/json",
7134
+ Authorization: `Basic ${credentials}`,
7135
+ "Notion-Version": NOTION_VERSION
7136
+ },
7137
+ body: JSON.stringify({
7138
+ grant_type: "authorization_code",
7139
+ code: code2,
7140
+ redirect_uri: effectiveRedirectUri
7141
+ })
7142
+ });
7143
+ if (!response.ok) {
7144
+ const text3 = await response.text();
7145
+ throw new Error(`Notion token exchange failed (${response.status}): ${text3}`);
7146
+ }
7147
+ const raw = await response.json();
7148
+ const data = raw;
7149
+ lastTokenRaw = raw;
7150
+ return {
7151
+ accessToken: data.access_token,
7152
+ tokenType: data.token_type ?? "Bearer",
7153
+ raw
7154
+ };
7155
+ }
7156
+ async function getUserInfo(_accessToken) {
7157
+ if (!lastTokenRaw) {
7158
+ throw new Error(
7159
+ "Notion getUserInfo called before exchangeCode. Call exchangeCode first to obtain the token response."
7160
+ );
7161
+ }
7162
+ const tokenData = lastTokenRaw;
7163
+ const user = tokenData?.owner?.user;
7164
+ if (!user?.id) {
7165
+ throw new Error(
7166
+ "Notion token response missing owner.user.id. Ensure the integration is authorized by a person, not a workspace bot."
7167
+ );
7168
+ }
7169
+ const email = user.person?.email;
7170
+ const avatar = user.avatar_url ?? void 0;
7171
+ return {
7172
+ id: user.id,
7173
+ email,
7174
+ name: user.name,
7175
+ avatar,
7176
+ raw: lastTokenRaw
7177
+ };
7178
+ }
7179
+ return {
7180
+ id: "notion",
7181
+ name: "Notion",
7182
+ authorizationUrl: AUTHORIZATION_URL11,
7183
+ tokenUrl: TOKEN_URL11,
7184
+ // No separate UserInfo URL; identity comes from the token response.
7185
+ userInfoUrl: void 0,
7186
+ scopes,
7187
+ getAuthorizationUrl,
7188
+ exchangeCode,
7189
+ getUserInfo
7190
+ };
7191
+ }
7192
+
7193
+ // src/auth/oauth/providers/presets.ts
7194
+ function facebookProvider(clientId, clientSecret, scopes) {
7195
+ return genericOIDC({
7196
+ id: "facebook",
7197
+ name: "Facebook",
7198
+ issuer: "https://www.facebook.com",
7199
+ clientId,
7200
+ clientSecret,
7201
+ scopes: scopes ?? ["email", "public_profile"],
7202
+ authorizationUrl: "https://www.facebook.com/v18.0/dialog/oauth",
7203
+ tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
7204
+ userinfoUrl: "https://graph.facebook.com/me?fields=id,email,name,picture"
7205
+ });
7206
+ }
7207
+ function spotifyProvider(clientId, clientSecret, scopes) {
7208
+ return genericOIDC({
7209
+ id: "spotify",
7210
+ name: "Spotify",
7211
+ issuer: "https://accounts.spotify.com",
7212
+ clientId,
7213
+ clientSecret,
7214
+ scopes: scopes ?? ["user-read-email", "user-read-private"],
7215
+ authorizationUrl: "https://accounts.spotify.com/authorize",
7216
+ tokenUrl: "https://accounts.spotify.com/api/token",
7217
+ userinfoUrl: "https://api.spotify.com/v1/me"
7218
+ });
7219
+ }
7220
+ function twitchProvider(clientId, clientSecret, scopes) {
7221
+ return genericOIDC({
7222
+ id: "twitch",
7223
+ name: "Twitch",
7224
+ issuer: "https://id.twitch.tv/oauth2",
7225
+ clientId,
7226
+ clientSecret,
7227
+ scopes: scopes ?? ["openid", "user:read:email"],
7228
+ authorizationUrl: "https://id.twitch.tv/oauth2/authorize",
7229
+ tokenUrl: "https://id.twitch.tv/oauth2/token",
7230
+ userinfoUrl: "https://id.twitch.tv/oauth2/userinfo"
7231
+ });
7232
+ }
7233
+ function redditProvider(clientId, clientSecret, scopes) {
7234
+ return genericOIDC({
7235
+ id: "reddit",
7236
+ name: "Reddit",
7237
+ issuer: "https://www.reddit.com",
7238
+ clientId,
7239
+ clientSecret,
7240
+ scopes: scopes ?? ["identity"],
7241
+ authorizationUrl: "https://www.reddit.com/api/v1/authorize",
7242
+ tokenUrl: "https://www.reddit.com/api/v1/access_token",
7243
+ userinfoUrl: "https://oauth.reddit.com/api/v1/me"
7244
+ });
7245
+ }
7246
+ function dropboxProvider(clientId, clientSecret, scopes) {
7247
+ return genericOIDC({
7248
+ id: "dropbox",
7249
+ name: "Dropbox",
7250
+ issuer: "https://www.dropbox.com",
7251
+ clientId,
7252
+ clientSecret,
7253
+ scopes: scopes ?? ["account_info.read"],
7254
+ authorizationUrl: "https://www.dropbox.com/oauth2/authorize",
7255
+ tokenUrl: "https://api.dropboxapi.com/oauth2/token",
7256
+ userinfoUrl: "https://api.dropboxapi.com/2/users/get_current_account"
7257
+ });
7258
+ }
7259
+ function zoomProvider(clientId, clientSecret, scopes) {
7260
+ return genericOIDC({
7261
+ id: "zoom",
7262
+ name: "Zoom",
7263
+ issuer: "https://zoom.us",
7264
+ clientId,
7265
+ clientSecret,
7266
+ scopes: scopes ?? ["openid", "profile", "email"],
7267
+ authorizationUrl: "https://zoom.us/oauth/authorize",
7268
+ tokenUrl: "https://zoom.us/oauth/token",
7269
+ userinfoUrl: "https://api.zoom.us/v2/users/me"
7270
+ });
7271
+ }
7272
+ function notionProvider(clientId, clientSecret, scopes) {
7273
+ return genericOIDC({
7274
+ id: "notion",
7275
+ name: "Notion",
7276
+ issuer: "https://api.notion.com",
7277
+ clientId,
7278
+ clientSecret,
7279
+ scopes: scopes ?? [],
7280
+ authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
7281
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
7282
+ userinfoUrl: "https://api.notion.com/v1/users/me"
7283
+ });
7284
+ }
7285
+ function figmaProvider(clientId, clientSecret, scopes) {
7286
+ return genericOIDC({
7287
+ id: "figma",
7288
+ name: "Figma",
7289
+ issuer: "https://www.figma.com",
7290
+ clientId,
7291
+ clientSecret,
7292
+ scopes: scopes ?? ["file_read"],
7293
+ authorizationUrl: "https://www.figma.com/oauth",
7294
+ tokenUrl: "https://api.figma.com/v1/oauth/token",
7295
+ userinfoUrl: "https://api.figma.com/v1/me"
7296
+ });
7297
+ }
7298
+ function bitbucketProvider(clientId, clientSecret, scopes) {
7299
+ return genericOIDC({
7300
+ id: "bitbucket",
7301
+ name: "Bitbucket",
7302
+ issuer: "https://bitbucket.org",
7303
+ clientId,
7304
+ clientSecret,
7305
+ scopes: scopes ?? ["account", "email"],
7306
+ authorizationUrl: "https://bitbucket.org/site/oauth2/authorize",
7307
+ tokenUrl: "https://bitbucket.org/site/oauth2/access_token",
7308
+ userinfoUrl: "https://api.bitbucket.org/2.0/user"
7309
+ });
7310
+ }
7311
+ function atlassianProvider(clientId, clientSecret, scopes) {
7312
+ return genericOIDC({
7313
+ id: "atlassian",
7314
+ name: "Atlassian",
7315
+ issuer: "https://auth.atlassian.com",
7316
+ clientId,
7317
+ clientSecret,
7318
+ scopes: scopes ?? ["read:me", "offline_access"],
7319
+ authorizationUrl: "https://auth.atlassian.com/authorize",
7320
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
7321
+ userinfoUrl: "https://api.atlassian.com/me"
7322
+ });
7323
+ }
7324
+ function yahooProvider(clientId, clientSecret, scopes) {
7325
+ return genericOIDC({
7326
+ id: "yahoo",
7327
+ name: "Yahoo",
7328
+ issuer: "https://api.login.yahoo.com",
7329
+ clientId,
7330
+ clientSecret,
7331
+ scopes: scopes ?? ["openid", "profile", "email"],
7332
+ authorizationUrl: "https://api.login.yahoo.com/oauth2/request_auth",
7333
+ tokenUrl: "https://api.login.yahoo.com/oauth2/get_token",
7334
+ userinfoUrl: "https://api.login.yahoo.com/openid/v1/userinfo"
7335
+ });
7336
+ }
7337
+ function lineProvider(clientId, clientSecret, scopes) {
7338
+ return genericOIDC({
7339
+ id: "line",
7340
+ name: "LINE",
7341
+ issuer: "https://access.line.me",
7342
+ clientId,
7343
+ clientSecret,
7344
+ scopes: scopes ?? ["openid", "profile", "email"],
7345
+ authorizationUrl: "https://access.line.me/oauth2/v2.1/authorize",
7346
+ tokenUrl: "https://api.line.me/oauth2/v2.1/token",
7347
+ userinfoUrl: "https://api.line.me/v2/profile"
7348
+ });
7349
+ }
7350
+ function coinbaseProvider(clientId, clientSecret, scopes) {
7351
+ return genericOIDC({
7352
+ id: "coinbase",
7353
+ name: "Coinbase",
7354
+ issuer: "https://login.coinbase.com",
7355
+ clientId,
7356
+ clientSecret,
7357
+ scopes: scopes ?? ["wallet:user:read", "wallet:user:email"],
7358
+ authorizationUrl: "https://login.coinbase.com/oauth2/auth",
7359
+ tokenUrl: "https://login.coinbase.com/oauth2/token",
7360
+ userinfoUrl: "https://api.coinbase.com/v2/user"
7361
+ });
7362
+ }
7363
+ function tiktokProvider(clientId, clientSecret, scopes) {
7364
+ return genericOIDC({
7365
+ id: "tiktok",
7366
+ name: "TikTok",
7367
+ issuer: "https://www.tiktok.com",
7368
+ clientId,
7369
+ clientSecret,
7370
+ scopes: scopes ?? ["user.info.basic"],
7371
+ authorizationUrl: "https://www.tiktok.com/v2/auth/authorize/",
7372
+ tokenUrl: "https://open.tiktokapis.com/v2/oauth/token/",
7373
+ userinfoUrl: "https://open.tiktokapis.com/v2/user/info/"
7374
+ });
7375
+ }
7376
+ function paypalProvider(clientId, clientSecret, scopes) {
7377
+ return genericOIDC({
7378
+ id: "paypal",
7379
+ name: "PayPal",
7380
+ issuer: "https://www.paypal.com",
7381
+ clientId,
7382
+ clientSecret,
7383
+ scopes: scopes ?? ["openid", "email"],
7384
+ authorizationUrl: "https://www.paypal.com/signin/authorize",
7385
+ tokenUrl: "https://api-m.paypal.com/v1/oauth2/token",
7386
+ userinfoUrl: "https://api-m.paypal.com/v1/identity/openidconnect/userinfo?schema=openid"
7387
+ });
7388
+ }
7389
+ function salesforceProvider(clientId, clientSecret, scopes) {
7390
+ return genericOIDC({
7391
+ id: "salesforce",
7392
+ name: "Salesforce",
7393
+ issuer: "https://login.salesforce.com",
7394
+ clientId,
7395
+ clientSecret,
7396
+ scopes: scopes ?? ["openid", "email", "profile"],
7397
+ authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
7398
+ tokenUrl: "https://login.salesforce.com/services/oauth2/token",
7399
+ userinfoUrl: "https://login.salesforce.com/services/oauth2/userinfo"
7400
+ });
7401
+ }
7402
+ function vkProvider(clientId, clientSecret, scopes) {
7403
+ return genericOIDC({
7404
+ id: "vk",
7405
+ name: "VK",
7406
+ issuer: "https://id.vk.com",
7407
+ clientId,
7408
+ clientSecret,
7409
+ scopes: scopes ?? ["email"],
7410
+ authorizationUrl: "https://id.vk.com/authorize",
7411
+ tokenUrl: "https://id.vk.com/oauth2/auth",
7412
+ userinfoUrl: "https://id.vk.com/oauth2/user_info"
7413
+ });
7414
+ }
7415
+ function kakaoProvider(clientId, clientSecret, scopes) {
7416
+ return genericOIDC({
7417
+ id: "kakao",
7418
+ name: "Kakao",
7419
+ issuer: "https://kauth.kakao.com",
7420
+ clientId,
7421
+ clientSecret,
7422
+ scopes: scopes ?? ["account_email", "profile_nickname"],
7423
+ authorizationUrl: "https://kauth.kakao.com/oauth/authorize",
7424
+ tokenUrl: "https://kauth.kakao.com/oauth/token",
7425
+ userinfoUrl: "https://kapi.kakao.com/v2/user/me"
7426
+ });
7427
+ }
7428
+ function naverProvider(clientId, clientSecret, scopes) {
7429
+ return genericOIDC({
7430
+ id: "naver",
7431
+ name: "Naver",
7432
+ issuer: "https://nid.naver.com",
7433
+ clientId,
7434
+ clientSecret,
7435
+ scopes: scopes ?? ["email", "profile"],
7436
+ authorizationUrl: "https://nid.naver.com/oauth2.0/authorize",
7437
+ tokenUrl: "https://nid.naver.com/oauth2.0/token",
7438
+ userinfoUrl: "https://openapi.naver.com/v1/nid/me"
7439
+ });
7440
+ }
7441
+ function huggingfaceProvider(clientId, clientSecret, scopes) {
7442
+ return genericOIDC({
7443
+ id: "huggingface",
7444
+ name: "Hugging Face",
7445
+ issuer: "https://huggingface.co",
7446
+ clientId,
7447
+ clientSecret,
7448
+ scopes: scopes ?? ["openid", "profile", "email"],
7449
+ authorizationUrl: "https://huggingface.co/oauth/authorize",
7450
+ tokenUrl: "https://huggingface.co/oauth/token",
7451
+ userinfoUrl: "https://huggingface.co/oauth/userinfo"
7452
+ });
7453
+ }
7454
+ function robloxProvider(clientId, clientSecret, scopes) {
7455
+ return genericOIDC({
7456
+ id: "roblox",
7457
+ name: "Roblox",
7458
+ issuer: "https://apis.roblox.com/oauth",
7459
+ clientId,
7460
+ clientSecret,
7461
+ scopes: scopes ?? ["openid", "profile"],
7462
+ authorizationUrl: "https://apis.roblox.com/oauth/v1/authorize",
7463
+ tokenUrl: "https://apis.roblox.com/oauth/v1/token",
7464
+ userinfoUrl: "https://apis.roblox.com/oauth/v1/userinfo"
7465
+ });
7466
+ }
7467
+ function vercelProvider(clientId, clientSecret, scopes) {
7468
+ return genericOIDC({
7469
+ id: "vercel",
7470
+ name: "Vercel",
7471
+ issuer: "https://vercel.com",
7472
+ clientId,
7473
+ clientSecret,
7474
+ scopes: scopes ?? ["openid", "email", "profile"],
7475
+ authorizationUrl: "https://vercel.com/integrations/oauth/authorize",
7476
+ tokenUrl: "https://api.vercel.com/v2/oauth/access_token",
7477
+ userinfoUrl: "https://api.vercel.com/v2/user"
7478
+ });
7479
+ }
7480
+ function linearProvider(clientId, clientSecret, scopes) {
7481
+ return genericOIDC({
7482
+ id: "linear",
7483
+ name: "Linear",
7484
+ issuer: "https://linear.app",
7485
+ clientId,
7486
+ clientSecret,
7487
+ scopes: scopes ?? ["read"],
7488
+ authorizationUrl: "https://linear.app/oauth/authorize",
7489
+ tokenUrl: "https://api.linear.app/oauth/token",
7490
+ userinfoUrl: "https://api.linear.app/graphql"
7491
+ });
7492
+ }
7493
+ function railwayProvider(clientId, clientSecret, scopes) {
7494
+ return genericOIDC({
7495
+ id: "railway",
7496
+ name: "Railway",
7497
+ issuer: "https://railway.com",
7498
+ clientId,
7499
+ clientSecret,
7500
+ scopes: scopes ?? ["read:user", "read:project"],
7501
+ authorizationUrl: "https://railway.com/oauth/authorize",
7502
+ tokenUrl: "https://railway.com/oauth/token",
7503
+ userinfoUrl: "https://backboard.railway.com/graphql/v2"
7504
+ });
7505
+ }
7506
+ function kickProvider(clientId, clientSecret, scopes) {
7507
+ return genericOIDC({
7508
+ id: "kick",
7509
+ name: "Kick",
7510
+ issuer: "https://id.kick.com",
7511
+ clientId,
7512
+ clientSecret,
7513
+ scopes: scopes ?? ["user:read"],
7514
+ authorizationUrl: "https://id.kick.com/oauth/authorize",
7515
+ tokenUrl: "https://id.kick.com/oauth/token",
7516
+ userinfoUrl: "https://id.kick.com/oauth/userinfo"
7517
+ });
7518
+ }
7519
+ function wechatProvider(clientId, clientSecret, scopes) {
7520
+ return genericOIDC({
7521
+ id: "wechat",
7522
+ name: "WeChat",
7523
+ issuer: "https://open.weixin.qq.com",
7524
+ clientId,
7525
+ clientSecret,
7526
+ scopes: scopes ?? ["snsapi_login"],
7527
+ authorizationUrl: "https://open.weixin.qq.com/connect/qrconnect",
7528
+ tokenUrl: "https://api.weixin.qq.com/sns/oauth2/access_token",
7529
+ userinfoUrl: "https://api.weixin.qq.com/sns/userinfo"
7530
+ });
7531
+ }
7532
+ function polarProvider(clientId, clientSecret, scopes) {
7533
+ return genericOIDC({
7534
+ id: "polar",
7535
+ name: "Polar",
7536
+ issuer: "https://polar.sh",
7537
+ clientId,
7538
+ clientSecret,
7539
+ scopes: scopes ?? ["openid", "email", "profile"],
7540
+ authorizationUrl: "https://polar.sh/oauth2/authorize",
7541
+ tokenUrl: "https://api.polar.sh/v1/oauth2/token",
7542
+ userinfoUrl: "https://api.polar.sh/v1/oauth2/userinfo"
7543
+ });
7544
+ }
7545
+ function auth0Provider(domain, clientId, clientSecret, scopes) {
7546
+ const issuer = `https://${domain.replace(/^https?:\/\//, "")}`;
7547
+ return genericOIDC({
7548
+ id: "auth0",
7549
+ name: "Auth0",
7550
+ issuer,
7551
+ clientId,
7552
+ clientSecret,
7553
+ scopes
7554
+ });
7555
+ }
7556
+ function oktaProvider(domain, clientId, clientSecret, scopes) {
7557
+ const issuer = `https://${domain.replace(/^https?:\/\//, "")}/oauth2/default`;
7558
+ return genericOIDC({
7559
+ id: "okta",
7560
+ name: "Okta",
7561
+ issuer,
7562
+ clientId,
7563
+ clientSecret,
7564
+ scopes
7565
+ });
7566
+ }
7567
+ function cognitoProvider(domain, clientId, clientSecret, scopes) {
7568
+ const host = domain.replace(/^https?:\/\//, "").replace(/\/$/, "");
7569
+ const issuer = `https://${host}`;
7570
+ return genericOIDC({
7571
+ id: "cognito",
7572
+ name: "AWS Cognito",
7573
+ issuer,
7574
+ clientId,
7575
+ clientSecret,
7576
+ scopes: scopes ?? ["openid", "email", "profile"],
7577
+ authorizationUrl: `${issuer}/oauth2/authorize`,
7578
+ tokenUrl: `${issuer}/oauth2/token`,
7579
+ userinfoUrl: `${issuer}/oauth2/userInfo`
7580
+ });
7581
+ }
7582
+
7583
+ // src/auth/oauth/providers/reddit.ts
7584
+ var AUTHORIZATION_URL12 = "https://www.reddit.com/api/v1/authorize";
7585
+ var TOKEN_URL12 = "https://www.reddit.com/api/v1/access_token";
7586
+ var USER_INFO_URL9 = "https://oauth.reddit.com/api/v1/me";
7587
+ var DEFAULT_USER_AGENT = "web:kavachos-oauth:v1 (by /u/kavachos)";
7588
+ var DEFAULT_REDDIT_SCOPES = ["identity"];
7589
+ var DEFAULT_SCOPES8 = DEFAULT_REDDIT_SCOPES;
7590
+ function createRedditProvider(config) {
7591
+ const scopes = mergeScopes12(DEFAULT_SCOPES8, config.scopes);
7592
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
7593
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
7594
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7595
+ const params = new URLSearchParams({
7596
+ client_id: config.clientId,
7597
+ redirect_uri: effectiveRedirectUri,
7598
+ response_type: "code",
7599
+ scope: scopes.join(" "),
7600
+ state,
7601
+ code_challenge: codeChallenge,
7602
+ code_challenge_method: "S256",
7603
+ // Reddit requires duration=permanent to receive a refresh token.
7604
+ duration: "permanent"
7605
+ });
7606
+ return `${AUTHORIZATION_URL12}?${params.toString()}`;
7607
+ }
7608
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
7609
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
7610
+ const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
7611
+ const body = new URLSearchParams({
7612
+ grant_type: "authorization_code",
7613
+ code: code2,
7614
+ redirect_uri: effectiveRedirectUri,
7615
+ code_verifier: codeVerifier
7616
+ });
7617
+ const response = await fetch(TOKEN_URL12, {
7618
+ method: "POST",
7619
+ headers: {
7620
+ "Content-Type": "application/x-www-form-urlencoded",
7621
+ Authorization: `Basic ${credentials}`,
7622
+ "User-Agent": DEFAULT_USER_AGENT
7623
+ },
7624
+ body: body.toString()
7625
+ });
7626
+ if (!response.ok) {
7627
+ const text3 = await response.text();
7628
+ throw new Error(`Reddit token exchange failed (${response.status}): ${text3}`);
7629
+ }
7630
+ const raw = await response.json();
7631
+ const data = raw;
7632
+ return {
7633
+ accessToken: data.access_token,
7634
+ refreshToken: data.refresh_token,
7635
+ expiresIn: data.expires_in,
7636
+ tokenType: data.token_type ?? "Bearer",
7637
+ raw
7638
+ };
7639
+ }
7640
+ async function getUserInfo(accessToken) {
7641
+ const response = await fetch(USER_INFO_URL9, {
7642
+ headers: {
7643
+ Authorization: `Bearer ${accessToken}`,
7644
+ "User-Agent": DEFAULT_USER_AGENT
7645
+ }
7646
+ });
7647
+ if (!response.ok) {
7648
+ const text3 = await response.text();
7649
+ throw new Error(`Reddit /api/v1/me fetch failed (${response.status}): ${text3}`);
6418
7650
  }
6419
7651
  const raw = await response.json();
6420
7652
  const data = raw;
6421
- if (!data.sub || !data.email) {
6422
- throw new Error("Google userinfo response missing required fields: sub, email");
7653
+ if (!data.id) {
7654
+ throw new Error("Reddit user response missing required field: id");
6423
7655
  }
7656
+ if (!data.name) {
7657
+ throw new Error("Reddit user response missing required field: name");
7658
+ }
7659
+ const avatar = data.icon_img ? stripQueryParams(data.icon_img) : void 0;
6424
7660
  return {
6425
- id: data.sub,
6426
- email: data.email,
7661
+ id: data.id,
7662
+ // No email — caller must handle the undefined case.
7663
+ email: void 0,
6427
7664
  name: data.name,
6428
- avatar: data.picture,
7665
+ avatar,
6429
7666
  raw
6430
7667
  };
6431
7668
  }
6432
7669
  return {
6433
- id: "google",
6434
- name: "Google",
6435
- authorizationUrl: AUTHORIZATION_URL5,
6436
- tokenUrl: TOKEN_URL5,
6437
- userInfoUrl: USER_INFO_URL3,
7670
+ id: "reddit",
7671
+ name: "Reddit",
7672
+ authorizationUrl: AUTHORIZATION_URL12,
7673
+ tokenUrl: TOKEN_URL12,
7674
+ userInfoUrl: USER_INFO_URL9,
6438
7675
  scopes,
6439
7676
  getAuthorizationUrl,
6440
7677
  exchangeCode,
6441
7678
  getUserInfo
6442
7679
  };
6443
7680
  }
6444
- function mergeScopes5(defaults, extras) {
7681
+ function normalizeProfile6(raw) {
7682
+ const data = raw;
7683
+ if (!data.id) {
7684
+ throw new Error("Reddit user response missing required field: id");
7685
+ }
7686
+ if (!data.name) {
7687
+ throw new Error("Reddit user response missing required field: name");
7688
+ }
7689
+ const avatar = data.icon_img ? stripQueryParams(data.icon_img) : void 0;
7690
+ return {
7691
+ id: data.id,
7692
+ // Reddit does not return email through OAuth; callers must handle this.
7693
+ email: void 0,
7694
+ name: data.name,
7695
+ avatar,
7696
+ raw
7697
+ };
7698
+ }
7699
+ function stripQueryParams(url) {
7700
+ try {
7701
+ const parsed = new URL(url);
7702
+ parsed.search = "";
7703
+ return parsed.toString();
7704
+ } catch {
7705
+ return url;
7706
+ }
7707
+ }
7708
+ function mergeScopes12(defaults, extras) {
6445
7709
  if (!extras || extras.length === 0) return defaults;
6446
7710
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6447
7711
  return [...merged];
6448
7712
  }
6449
7713
 
6450
- // src/auth/oauth/providers/linkedin.ts
6451
- var AUTHORIZATION_URL6 = "https://www.linkedin.com/oauth/v2/authorization";
6452
- var TOKEN_URL6 = "https://www.linkedin.com/oauth/v2/accessToken";
6453
- var USER_INFO_URL4 = "https://api.linkedin.com/v2/userinfo";
6454
- var DEFAULT_SCOPES6 = ["openid", "profile", "email"];
6455
- function createLinkedInProvider(config) {
6456
- const scopes = mergeScopes6(DEFAULT_SCOPES6, config.scopes);
7714
+ // src/auth/oauth/providers/slack.ts
7715
+ var AUTHORIZATION_URL13 = "https://slack.com/oauth/v2/authorize";
7716
+ var TOKEN_URL13 = "https://slack.com/api/oauth.v2.access";
7717
+ var USER_INFO_URL10 = "https://slack.com/api/openid.connect.userInfo";
7718
+ var DEFAULT_SLACK_SCOPES = ["openid", "profile", "email"];
7719
+ var DEFAULT_SCOPES9 = DEFAULT_SLACK_SCOPES;
7720
+ function createSlackProvider(config) {
7721
+ const scopes = mergeScopes13(DEFAULT_SCOPES9, config.scopes);
6457
7722
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6458
7723
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6459
7724
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
6460
7725
  const params = new URLSearchParams({
6461
- response_type: "code",
6462
7726
  client_id: config.clientId,
6463
7727
  redirect_uri: effectiveRedirectUri,
7728
+ response_type: "code",
6464
7729
  scope: scopes.join(" "),
6465
7730
  state,
6466
7731
  code_challenge: codeChallenge,
6467
7732
  code_challenge_method: "S256"
6468
7733
  });
6469
- return `${AUTHORIZATION_URL6}?${params.toString()}`;
7734
+ return `${AUTHORIZATION_URL13}?${params.toString()}`;
6470
7735
  }
6471
7736
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6472
7737
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6478,77 +7743,120 @@ function createLinkedInProvider(config) {
6478
7743
  redirect_uri: effectiveRedirectUri,
6479
7744
  code_verifier: codeVerifier
6480
7745
  });
6481
- const response = await fetch(TOKEN_URL6, {
7746
+ const response = await fetch(TOKEN_URL13, {
6482
7747
  method: "POST",
6483
7748
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6484
7749
  body: body.toString()
6485
7750
  });
6486
7751
  if (!response.ok) {
6487
7752
  const text3 = await response.text();
6488
- throw new Error(`LinkedIn token exchange failed (${response.status}): ${text3}`);
7753
+ throw new Error(`Slack token exchange failed (${response.status}): ${text3}`);
6489
7754
  }
6490
7755
  const raw = await response.json();
6491
7756
  const data = raw;
7757
+ if (!data.ok) {
7758
+ throw new Error(`Slack token exchange error: ${data.error ?? "unknown"}`);
7759
+ }
7760
+ const accessToken = data.authed_user?.access_token ?? data.access_token;
7761
+ if (!accessToken) {
7762
+ throw new Error("Slack token exchange returned no access_token");
7763
+ }
6492
7764
  return {
6493
- accessToken: data.access_token,
6494
- refreshToken: data.refresh_token,
6495
- expiresIn: data.expires_in,
6496
- tokenType: data.token_type ?? "Bearer",
7765
+ accessToken,
7766
+ refreshToken: void 0,
7767
+ expiresIn: void 0,
7768
+ tokenType: data.authed_user?.token_type ?? data.token_type ?? "Bearer",
6497
7769
  raw
6498
7770
  };
6499
7771
  }
6500
7772
  async function getUserInfo(accessToken) {
6501
- const response = await fetch(USER_INFO_URL4, {
7773
+ const response = await fetch(USER_INFO_URL10, {
6502
7774
  headers: { Authorization: `Bearer ${accessToken}` }
6503
7775
  });
6504
7776
  if (!response.ok) {
6505
7777
  const text3 = await response.text();
6506
- throw new Error(`LinkedIn /v2/userinfo fetch failed (${response.status}): ${text3}`);
7778
+ throw new Error(`Slack openid.connect.userInfo fetch failed (${response.status}): ${text3}`);
6507
7779
  }
6508
7780
  const raw = await response.json();
6509
7781
  const data = raw;
6510
- if (!data.sub) {
6511
- throw new Error("LinkedIn userinfo response missing required field: sub");
7782
+ if (!data.ok) {
7783
+ throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
7784
+ }
7785
+ const id = data.sub ?? data["https://slack.com/user_id"];
7786
+ if (!id) {
7787
+ throw new Error("Slack userInfo response missing required field: sub");
6512
7788
  }
6513
7789
  if (!data.email) {
6514
- throw new Error(
6515
- "LinkedIn userinfo response has no email. Ensure the `email` and `openid` scopes are granted."
6516
- );
7790
+ throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
6517
7791
  }
6518
- const name = data.name ?? ([data.given_name, data.family_name].filter(Boolean).join(" ") || void 0);
6519
7792
  return {
6520
- id: data.sub,
7793
+ id,
6521
7794
  email: data.email,
6522
- name,
7795
+ name: data.name,
6523
7796
  avatar: data.picture,
6524
7797
  raw
6525
7798
  };
6526
7799
  }
6527
7800
  return {
6528
- id: "linkedin",
6529
- name: "LinkedIn",
6530
- authorizationUrl: AUTHORIZATION_URL6,
6531
- tokenUrl: TOKEN_URL6,
6532
- userInfoUrl: USER_INFO_URL4,
7801
+ id: "slack",
7802
+ name: "Slack",
7803
+ authorizationUrl: AUTHORIZATION_URL13,
7804
+ tokenUrl: TOKEN_URL13,
7805
+ userInfoUrl: USER_INFO_URL10,
6533
7806
  scopes,
6534
7807
  getAuthorizationUrl,
6535
7808
  exchangeCode,
6536
7809
  getUserInfo
6537
7810
  };
6538
7811
  }
6539
- function mergeScopes6(defaults, extras) {
7812
+ function normalizeProfile7(raw) {
7813
+ const data = raw;
7814
+ if (!data.ok) {
7815
+ throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
7816
+ }
7817
+ const id = data.sub ?? data["https://slack.com/user_id"];
7818
+ if (!id) {
7819
+ throw new Error("Slack userInfo response missing required field: sub");
7820
+ }
7821
+ if (!data.email) {
7822
+ throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
7823
+ }
7824
+ return {
7825
+ id,
7826
+ email: data.email,
7827
+ name: data.name,
7828
+ avatar: data.picture,
7829
+ raw
7830
+ };
7831
+ }
7832
+ function mergeScopes13(defaults, extras) {
6540
7833
  if (!extras || extras.length === 0) return defaults;
6541
7834
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6542
7835
  return [...merged];
6543
7836
  }
6544
7837
 
6545
- // src/auth/oauth/providers/microsoft.ts
6546
- var AUTHORIZATION_URL7 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
6547
- var TOKEN_URL7 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
6548
- var USER_INFO_URL5 = "https://graph.microsoft.com/v1.0/me";
6549
- var DEFAULT_SCOPES7 = ["openid", "profile", "email", "User.Read"];
6550
- function createMicrosoftProvider(config) {
6551
- const scopes = mergeScopes7(DEFAULT_SCOPES7, config.scopes);
7838
+ // src/auth/oauth/providers/spotify.ts
7839
+ var AUTHORIZATION_URL14 = "https://accounts.spotify.com/authorize";
7840
+ var TOKEN_URL14 = "https://accounts.spotify.com/api/token";
7841
+ var USER_INFO_URL11 = "https://api.spotify.com/v1/me";
7842
+ var DEFAULT_SPOTIFY_SCOPES = ["user-read-email", "user-read-private"];
7843
+ function normalizeProfile8(raw) {
7844
+ const data = raw;
7845
+ if (!data.id) {
7846
+ throw new Error("Spotify user response missing required field: id");
7847
+ }
7848
+ const avatar = Array.isArray(data.images) && data.images.length > 0 ? data.images[0]?.url : void 0;
7849
+ return {
7850
+ id: data.id,
7851
+ email: data.email,
7852
+ // emailVerified is not provided by Spotify
7853
+ name: data.display_name ?? void 0,
7854
+ avatar,
7855
+ raw
7856
+ };
7857
+ }
7858
+ function createSpotifyProvider(config) {
7859
+ const scopes = mergeScopes14(DEFAULT_SPOTIFY_SCOPES, config.scopes);
6552
7860
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6553
7861
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6554
7862
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6561,7 +7869,7 @@ function createMicrosoftProvider(config) {
6561
7869
  code_challenge: codeChallenge,
6562
7870
  code_challenge_method: "S256"
6563
7871
  });
6564
- return `${AUTHORIZATION_URL7}?${params.toString()}`;
7872
+ return `${AUTHORIZATION_URL14}?${params.toString()}`;
6565
7873
  }
6566
7874
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6567
7875
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6573,14 +7881,14 @@ function createMicrosoftProvider(config) {
6573
7881
  code_verifier: codeVerifier,
6574
7882
  redirect_uri: effectiveRedirectUri
6575
7883
  });
6576
- const response = await fetch(TOKEN_URL7, {
7884
+ const response = await fetch(TOKEN_URL14, {
6577
7885
  method: "POST",
6578
7886
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6579
7887
  body: body.toString()
6580
7888
  });
6581
7889
  if (!response.ok) {
6582
7890
  const text3 = await response.text();
6583
- throw new Error(`Microsoft token exchange failed (${response.status}): ${text3}`);
7891
+ throw new Error(`Spotify token exchange failed (${response.status}): ${text3}`);
6584
7892
  }
6585
7893
  const raw = await response.json();
6586
7894
  const data = raw;
@@ -6593,60 +7901,42 @@ function createMicrosoftProvider(config) {
6593
7901
  };
6594
7902
  }
6595
7903
  async function getUserInfo(accessToken) {
6596
- const response = await fetch(USER_INFO_URL5, {
7904
+ const response = await fetch(USER_INFO_URL11, {
6597
7905
  headers: { Authorization: `Bearer ${accessToken}` }
6598
7906
  });
6599
7907
  if (!response.ok) {
6600
7908
  const text3 = await response.text();
6601
- throw new Error(`Microsoft Graph /me fetch failed (${response.status}): ${text3}`);
7909
+ throw new Error(`Spotify /v1/me fetch failed (${response.status}): ${text3}`);
6602
7910
  }
6603
7911
  const raw = await response.json();
6604
- const data = raw;
6605
- if (!data.id) {
6606
- throw new Error("Microsoft Graph /me response missing required field: id");
6607
- }
6608
- const email = data.mail ?? data.userPrincipalName;
6609
- if (!email) {
6610
- throw new Error(
6611
- "Microsoft Graph /me response has no email or userPrincipalName. Ensure the `email` and `User.Read` scopes are granted."
6612
- );
6613
- }
6614
- const name = data.displayName ?? ([data.givenName, data.surname].filter(Boolean).join(" ") || void 0);
6615
- return {
6616
- id: data.id,
6617
- email,
6618
- name,
6619
- avatar: void 0,
6620
- // Graph photo requires a separate /me/photo/$value call
6621
- raw
6622
- };
7912
+ return normalizeProfile8(raw);
6623
7913
  }
6624
7914
  return {
6625
- id: "microsoft",
6626
- name: "Microsoft",
6627
- authorizationUrl: AUTHORIZATION_URL7,
6628
- tokenUrl: TOKEN_URL7,
6629
- userInfoUrl: USER_INFO_URL5,
7915
+ id: "spotify",
7916
+ name: "Spotify",
7917
+ authorizationUrl: AUTHORIZATION_URL14,
7918
+ tokenUrl: TOKEN_URL14,
7919
+ userInfoUrl: USER_INFO_URL11,
6630
7920
  scopes,
6631
7921
  getAuthorizationUrl,
6632
7922
  exchangeCode,
6633
7923
  getUserInfo
6634
7924
  };
6635
7925
  }
6636
- function mergeScopes7(defaults, extras) {
7926
+ function mergeScopes14(defaults, extras) {
6637
7927
  if (!extras || extras.length === 0) return defaults;
6638
7928
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6639
7929
  return [...merged];
6640
7930
  }
6641
7931
 
6642
- // src/auth/oauth/providers/slack.ts
6643
- var AUTHORIZATION_URL8 = "https://slack.com/oauth/v2/authorize";
6644
- var TOKEN_URL8 = "https://slack.com/api/oauth.v2.access";
6645
- var USER_INFO_URL6 = "https://slack.com/api/openid.connect.userInfo";
6646
- var DEFAULT_SLACK_SCOPES = ["openid", "profile", "email"];
6647
- var DEFAULT_SCOPES8 = DEFAULT_SLACK_SCOPES;
6648
- function createSlackProvider(config) {
6649
- const scopes = mergeScopes8(DEFAULT_SCOPES8, config.scopes);
7932
+ // src/auth/oauth/providers/twitch.ts
7933
+ var AUTHORIZATION_URL15 = "https://id.twitch.tv/oauth2/authorize";
7934
+ var TOKEN_URL15 = "https://id.twitch.tv/oauth2/token";
7935
+ var USER_INFO_URL12 = "https://api.twitch.tv/helix/users";
7936
+ var DEFAULT_TWITCH_SCOPES = ["user:read:email"];
7937
+ var DEFAULT_SCOPES10 = DEFAULT_TWITCH_SCOPES;
7938
+ function createTwitchProvider(config) {
7939
+ const scopes = mergeScopes15(DEFAULT_SCOPES10, config.scopes);
6650
7940
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6651
7941
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6652
7942
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6659,7 +7949,7 @@ function createSlackProvider(config) {
6659
7949
  code_challenge: codeChallenge,
6660
7950
  code_challenge_method: "S256"
6661
7951
  });
6662
- return `${AUTHORIZATION_URL8}?${params.toString()}`;
7952
+ return `${AUTHORIZATION_URL15}?${params.toString()}`;
6663
7953
  }
6664
7954
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6665
7955
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6668,89 +7958,103 @@ function createSlackProvider(config) {
6668
7958
  client_id: config.clientId,
6669
7959
  client_secret: config.clientSecret,
6670
7960
  code: code2,
6671
- redirect_uri: effectiveRedirectUri,
6672
- code_verifier: codeVerifier
7961
+ code_verifier: codeVerifier,
7962
+ redirect_uri: effectiveRedirectUri
6673
7963
  });
6674
- const response = await fetch(TOKEN_URL8, {
7964
+ const response = await fetch(TOKEN_URL15, {
6675
7965
  method: "POST",
6676
7966
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
6677
7967
  body: body.toString()
6678
7968
  });
6679
7969
  if (!response.ok) {
6680
7970
  const text3 = await response.text();
6681
- throw new Error(`Slack token exchange failed (${response.status}): ${text3}`);
7971
+ throw new Error(`Twitch token exchange failed (${response.status}): ${text3}`);
6682
7972
  }
6683
7973
  const raw = await response.json();
6684
7974
  const data = raw;
6685
- if (!data.ok) {
6686
- throw new Error(`Slack token exchange error: ${data.error ?? "unknown"}`);
6687
- }
6688
- const accessToken = data.authed_user?.access_token ?? data.access_token;
6689
- if (!accessToken) {
6690
- throw new Error("Slack token exchange returned no access_token");
6691
- }
6692
7975
  return {
6693
- accessToken,
6694
- refreshToken: void 0,
6695
- expiresIn: void 0,
6696
- tokenType: data.authed_user?.token_type ?? data.token_type ?? "Bearer",
7976
+ accessToken: data.access_token,
7977
+ refreshToken: data.refresh_token,
7978
+ expiresIn: data.expires_in,
7979
+ tokenType: data.token_type ?? "Bearer",
6697
7980
  raw
6698
7981
  };
6699
7982
  }
6700
7983
  async function getUserInfo(accessToken) {
6701
- const response = await fetch(USER_INFO_URL6, {
6702
- headers: { Authorization: `Bearer ${accessToken}` }
7984
+ const response = await fetch(USER_INFO_URL12, {
7985
+ headers: {
7986
+ Authorization: `Bearer ${accessToken}`,
7987
+ "Client-ID": config.clientId
7988
+ }
6703
7989
  });
6704
7990
  if (!response.ok) {
6705
7991
  const text3 = await response.text();
6706
- throw new Error(`Slack openid.connect.userInfo fetch failed (${response.status}): ${text3}`);
7992
+ throw new Error(`Twitch /helix/users fetch failed (${response.status}): ${text3}`);
6707
7993
  }
6708
7994
  const raw = await response.json();
6709
- const data = raw;
6710
- if (!data.ok) {
6711
- throw new Error(`Slack userInfo error: ${data.error ?? "unknown"}`);
6712
- }
6713
- const id = data.sub ?? data["https://slack.com/user_id"];
6714
- if (!id) {
6715
- throw new Error("Slack userInfo response missing required field: sub");
7995
+ const body = raw;
7996
+ const user = body.data?.[0];
7997
+ if (!user?.id) {
7998
+ throw new Error("Twitch user response missing required field: id");
6716
7999
  }
6717
- if (!data.email) {
6718
- throw new Error("Slack userInfo response has no email. Ensure the `email` scope is granted.");
8000
+ if (!user.email) {
8001
+ throw new Error(
8002
+ "Twitch user response has no email. Ensure the `user:read:email` scope is granted."
8003
+ );
6719
8004
  }
6720
8005
  return {
6721
- id,
6722
- email: data.email,
6723
- name: data.name,
6724
- avatar: data.picture,
8006
+ id: user.id,
8007
+ email: user.email,
8008
+ name: user.display_name,
8009
+ avatar: user.profile_image_url,
6725
8010
  raw
6726
8011
  };
6727
8012
  }
6728
8013
  return {
6729
- id: "slack",
6730
- name: "Slack",
6731
- authorizationUrl: AUTHORIZATION_URL8,
6732
- tokenUrl: TOKEN_URL8,
6733
- userInfoUrl: USER_INFO_URL6,
8014
+ id: "twitch",
8015
+ name: "Twitch",
8016
+ authorizationUrl: AUTHORIZATION_URL15,
8017
+ tokenUrl: TOKEN_URL15,
8018
+ userInfoUrl: USER_INFO_URL12,
6734
8019
  scopes,
6735
8020
  getAuthorizationUrl,
6736
8021
  exchangeCode,
6737
8022
  getUserInfo
6738
8023
  };
6739
8024
  }
6740
- function mergeScopes8(defaults, extras) {
8025
+ function normalizeProfile9(raw) {
8026
+ const body = raw;
8027
+ const user = body.data?.[0];
8028
+ if (!user?.id) {
8029
+ throw new Error("Twitch user response missing required field: id");
8030
+ }
8031
+ if (!user.email) {
8032
+ throw new Error(
8033
+ "Twitch user response has no email. Ensure the `user:read:email` scope is granted."
8034
+ );
8035
+ }
8036
+ return {
8037
+ id: user.id,
8038
+ email: user.email,
8039
+ name: user.display_name,
8040
+ avatar: user.profile_image_url,
8041
+ raw
8042
+ };
8043
+ }
8044
+ function mergeScopes15(defaults, extras) {
6741
8045
  if (!extras || extras.length === 0) return defaults;
6742
8046
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6743
8047
  return [...merged];
6744
8048
  }
6745
8049
 
6746
8050
  // src/auth/oauth/providers/twitter.ts
6747
- var AUTHORIZATION_URL9 = "https://twitter.com/i/oauth2/authorize";
6748
- var TOKEN_URL9 = "https://api.twitter.com/2/oauth2/token";
6749
- var USER_INFO_URL7 = "https://api.twitter.com/2/users/me";
8051
+ var AUTHORIZATION_URL16 = "https://twitter.com/i/oauth2/authorize";
8052
+ var TOKEN_URL16 = "https://api.twitter.com/2/oauth2/token";
8053
+ var USER_INFO_URL13 = "https://api.twitter.com/2/users/me";
6750
8054
  var USER_FIELDS = "id,name,username,profile_image_url";
6751
- var DEFAULT_SCOPES9 = ["users.read", "tweet.read"];
8055
+ var DEFAULT_SCOPES11 = ["users.read", "tweet.read"];
6752
8056
  function createTwitterProvider(config) {
6753
- const scopes = mergeScopes9(DEFAULT_SCOPES9, config.scopes);
8057
+ const scopes = mergeScopes16(DEFAULT_SCOPES11, config.scopes);
6754
8058
  async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
6755
8059
  const codeChallenge = await deriveCodeChallenge(codeVerifier);
6756
8060
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6763,7 +8067,7 @@ function createTwitterProvider(config) {
6763
8067
  code_challenge: codeChallenge,
6764
8068
  code_challenge_method: "S256"
6765
8069
  });
6766
- return `${AUTHORIZATION_URL9}?${params.toString()}`;
8070
+ return `${AUTHORIZATION_URL16}?${params.toString()}`;
6767
8071
  }
6768
8072
  async function exchangeCode(code2, codeVerifier, redirectUri) {
6769
8073
  const effectiveRedirectUri = config.redirectUri ?? redirectUri;
@@ -6774,7 +8078,7 @@ function createTwitterProvider(config) {
6774
8078
  redirect_uri: effectiveRedirectUri
6775
8079
  });
6776
8080
  const credentials = btoa(`${config.clientId}:${config.clientSecret}`);
6777
- const response = await fetch(TOKEN_URL9, {
8081
+ const response = await fetch(TOKEN_URL16, {
6778
8082
  method: "POST",
6779
8083
  headers: {
6780
8084
  "Content-Type": "application/x-www-form-urlencoded",
@@ -6797,7 +8101,7 @@ function createTwitterProvider(config) {
6797
8101
  };
6798
8102
  }
6799
8103
  async function getUserInfo(accessToken) {
6800
- const url = `${USER_INFO_URL7}?user.fields=${USER_FIELDS}`;
8104
+ const url = `${USER_INFO_URL13}?user.fields=${USER_FIELDS}`;
6801
8105
  const response = await fetch(url, {
6802
8106
  headers: { Authorization: `Bearer ${accessToken}` }
6803
8107
  });
@@ -6829,16 +8133,113 @@ function createTwitterProvider(config) {
6829
8133
  return {
6830
8134
  id: "twitter",
6831
8135
  name: "Twitter",
6832
- authorizationUrl: AUTHORIZATION_URL9,
6833
- tokenUrl: TOKEN_URL9,
6834
- userInfoUrl: USER_INFO_URL7,
8136
+ authorizationUrl: AUTHORIZATION_URL16,
8137
+ tokenUrl: TOKEN_URL16,
8138
+ userInfoUrl: USER_INFO_URL13,
6835
8139
  scopes,
6836
8140
  getAuthorizationUrl,
6837
8141
  exchangeCode,
6838
8142
  getUserInfo
6839
8143
  };
6840
8144
  }
6841
- function mergeScopes9(defaults, extras) {
8145
+ function mergeScopes16(defaults, extras) {
8146
+ if (!extras || extras.length === 0) return defaults;
8147
+ const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
8148
+ return [...merged];
8149
+ }
8150
+
8151
+ // src/auth/oauth/providers/zoom.ts
8152
+ var AUTHORIZATION_URL17 = "https://zoom.us/oauth/authorize";
8153
+ var TOKEN_URL17 = "https://zoom.us/oauth/token";
8154
+ var USER_INFO_URL14 = "https://api.zoom.us/v2/users/me";
8155
+ var DEFAULT_ZOOM_SCOPES = ["user:read"];
8156
+ function normalizeProfile10(raw) {
8157
+ const data = raw;
8158
+ if (!data.id) {
8159
+ throw new Error("Zoom user response missing required field: id");
8160
+ }
8161
+ if (!data.email) {
8162
+ throw new Error("Zoom user response missing required field: email");
8163
+ }
8164
+ const joinedName = [data.first_name, data.last_name].filter(Boolean).join(" ");
8165
+ const name = data.display_name ?? (joinedName.length > 0 ? joinedName : void 0);
8166
+ return {
8167
+ id: data.id,
8168
+ email: data.email,
8169
+ name,
8170
+ avatar: data.pic_url,
8171
+ raw
8172
+ };
8173
+ }
8174
+ function createZoomProvider(config) {
8175
+ const scopes = mergeScopes17(DEFAULT_ZOOM_SCOPES, config.scopes);
8176
+ async function getAuthorizationUrl(state, codeVerifier, redirectUri) {
8177
+ const codeChallenge = await deriveCodeChallenge(codeVerifier);
8178
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
8179
+ const params = new URLSearchParams({
8180
+ client_id: config.clientId,
8181
+ redirect_uri: effectiveRedirectUri,
8182
+ response_type: "code",
8183
+ scope: scopes.join(" "),
8184
+ state,
8185
+ code_challenge: codeChallenge,
8186
+ code_challenge_method: "S256"
8187
+ });
8188
+ return `${AUTHORIZATION_URL17}?${params.toString()}`;
8189
+ }
8190
+ async function exchangeCode(code2, codeVerifier, redirectUri) {
8191
+ const effectiveRedirectUri = config.redirectUri ?? redirectUri;
8192
+ const body = new URLSearchParams({
8193
+ grant_type: "authorization_code",
8194
+ client_id: config.clientId,
8195
+ client_secret: config.clientSecret,
8196
+ code: code2,
8197
+ code_verifier: codeVerifier,
8198
+ redirect_uri: effectiveRedirectUri
8199
+ });
8200
+ const response = await fetch(TOKEN_URL17, {
8201
+ method: "POST",
8202
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
8203
+ body: body.toString()
8204
+ });
8205
+ if (!response.ok) {
8206
+ const text3 = await response.text();
8207
+ throw new Error(`Zoom token exchange failed (${response.status}): ${text3}`);
8208
+ }
8209
+ const raw = await response.json();
8210
+ const data = raw;
8211
+ return {
8212
+ accessToken: data.access_token,
8213
+ refreshToken: data.refresh_token,
8214
+ expiresIn: data.expires_in,
8215
+ tokenType: data.token_type ?? "Bearer",
8216
+ raw
8217
+ };
8218
+ }
8219
+ async function getUserInfo(accessToken) {
8220
+ const response = await fetch(USER_INFO_URL14, {
8221
+ headers: { Authorization: `Bearer ${accessToken}` }
8222
+ });
8223
+ if (!response.ok) {
8224
+ const text3 = await response.text();
8225
+ throw new Error(`Zoom /v2/users/me fetch failed (${response.status}): ${text3}`);
8226
+ }
8227
+ const raw = await response.json();
8228
+ return normalizeProfile10(raw);
8229
+ }
8230
+ return {
8231
+ id: "zoom",
8232
+ name: "Zoom",
8233
+ authorizationUrl: AUTHORIZATION_URL17,
8234
+ tokenUrl: TOKEN_URL17,
8235
+ userInfoUrl: USER_INFO_URL14,
8236
+ scopes,
8237
+ getAuthorizationUrl,
8238
+ exchangeCode,
8239
+ getUserInfo
8240
+ };
8241
+ }
8242
+ function mergeScopes17(defaults, extras) {
6842
8243
  if (!extras || extras.length === 0) return defaults;
6843
8244
  const merged = /* @__PURE__ */ new Set([...defaults, ...extras]);
6844
8245
  return [...merged];
@@ -7113,7 +8514,7 @@ var DEFAULT_REFRESH_TOKEN_TTL = 86400 * 30;
7113
8514
  var DEFAULT_AUTH_CODE_TTL = 600;
7114
8515
  var DEFAULT_ID_TOKEN_TTL = 3600;
7115
8516
  var DEFAULT_SIGNING_ALG = "RS256";
7116
- var DEFAULT_SCOPES10 = ["openid", "profile", "email"];
8517
+ var DEFAULT_SCOPES12 = ["openid", "profile", "email"];
7117
8518
  var CLIENT_ID_BYTE_LENGTH = 16;
7118
8519
  var CLIENT_SECRET_BYTE_LENGTH = 32;
7119
8520
  var AUTH_CODE_BYTE_LENGTH = 32;
@@ -7140,7 +8541,7 @@ function createOidcProviderModule(config, db, getUserClaims) {
7140
8541
  const refreshTokenTtl = config.refreshTokenTtl ?? DEFAULT_REFRESH_TOKEN_TTL;
7141
8542
  const authCodeTtl = config.authCodeTtl ?? DEFAULT_AUTH_CODE_TTL;
7142
8543
  const idTokenTtl = config.idTokenTtl ?? DEFAULT_ID_TOKEN_TTL;
7143
- const supportedScopes = config.supportedScopes ?? DEFAULT_SCOPES10;
8544
+ const supportedScopes = config.supportedScopes ?? DEFAULT_SCOPES12;
7144
8545
  let signingKeyPromise;
7145
8546
  let publicJwkPromise;
7146
8547
  async function getSigningKey() {
@@ -21754,6 +23155,6 @@ async function verifyWebhookSignature3(secret, rawBody, signature) {
21754
23155
  return diff === 0;
21755
23156
  }
21756
23157
 
21757
- export { CredentialStatusSchema, CredentialSubjectSchema, EVENT_TYPES, HibpApiError, HibpBreachedError, KAVACHOS_AUDIT_CONTEXT, KAVACHOS_AUDIT_CREDENTIAL, KAVACH_AGENT_CREDENTIAL, KAVACH_DELEGATION_CREDENTIAL, KAVACH_PERMISSION_CREDENTIAL, KVStore, MemoryStore, MultiSessionLimitError, OAuthProxyError, OneTapVerifyError, ProofSchema, RefreshTokenError, SSO_ERROR, SsoError, VC_CONTEXT_V1, VC_CONTEXT_V2, VC_TYPE_CREDENTIAL, VC_TYPE_PRESENTATION, VerifiableCredentialSchema, VerifiablePresentationSchema, additionalFields, admin, agentCards, agentDids, agents, anonymousAuth, apiKeys2 as apiKeys, apiKeys as apiKeysTable, approvalRequests, auditLogs, bearerAuth, budgetPolicies, buildDidDocument, buildSessionMetadata, classifyViolation, constantTimeEqual, createAdditionalFieldsModule, createAdminModule, createAgentModule, createAnonymousAuthModule, createApiKeyManagerModule, createAppleProvider, createApprovalModule, createAuditModule, createCaptchaModule, createCookieSessionManager, createCostAttributionModule, createCustomSessionModule, createDatabase, createDatabaseSync, createDelegationModule, createDeviceAuthModule, createDidModule, createDiscordProvider, createEmailOtpModule, createEmailTemplates, createEmailVerificationModule, createEphemeralSessionModule, createEventStreamModule, createFederationModule, createGdprModule, createGithubProvider, createGitlabProvider, createGoogleProvider, createHibpModule, createI18n, createJwtSessionModule, createKavach, createLastLoginModule, createLinkedInProvider, createMagicLinkModule, createMicrosoftProvider, createMultiSessionModule, createOAuthModule, createOAuthProxyModule, createOidcProviderModule, createOneTapModule, createOneTimeTokenModule, createOpenApiModule, createOrgModule, createPasskeyModule, createPasswordResetModule, createPermissionEngine, createPhoneAuthModule, createPluginRouter, createPolarModule, createPolicyModule, createPresentation, createPrivilegeAnalyzer, createRateLimiter, createReBACModule, createRedirectChain, createScimModule, createSessionFreshnessModule, createSessionManager, createSessionRefresher, createSiweModule, createSlackProvider, createSsoModule, createStripeModule, createTables, createTenantModule, createTokenFamilyStore, createTotpModule, createTrustModule, createTrustedDeviceModule, createTwitterProvider, createUsernameAuthModule, createVCIssuer, createVCVerifier, createWebhookModule2 as createWebhookModule, customAuth, customSession, de, delegationChains, deviceAuth, deviceLabelFromRequest, emailOtp, emailOtps, en, es, exportAuditAsVC, fr, fromBase64Url, fromHex, gdpr, generateCsrfToken, generateDidKey, generateDidWeb, generateId, generateOpenAPISpec, getCookie2 as getCookie, getDidWebUrl, getPermissionTemplate, headerAuth, hmacSha1Raw, hmacSha256, hmacSha256Raw, importHmacKey, initializePlugins, ja, kvStore, listAuditRecords, magicLink, magicLinks, mcpServers, oauth, oauthAccessTokens, oauthAuthorizationCodes, oauthClients, oauthProxy, oneTap, orgInvitations, orgMembers, orgRoles, organization, organizations, parseCookies2 as parseCookies, parseCookiesFromRequest, passkey, passkeyChallenges, passkeyCredentials, pbkdf2Hash, pbkdf2Verify, permissionTemplates, permissions, polar, randomBytes, randomBytesHex, rateLimit, rateLimits, resolveDidKey, resolveDidWeb, scim, serializeCookie, serializeCookieDeletion, sessions, sha1, sha256, sha256Raw, signPayload2 as signPayload, siwe, ssoConnections, stripe, tenants, toBase64Url, toHex, totpRecords, trustScores, twoFactor, users, validateCsrfToken, validateOrigin, verifyPayload, verifyPresentation, verifyWebhookSignature3 as verifyWebhookSignature, withRateLimit, zh };
23158
+ export { CredentialStatusSchema, CredentialSubjectSchema, 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, KAVACHOS_AUDIT_CONTEXT, KAVACHOS_AUDIT_CREDENTIAL, KAVACH_AGENT_CREDENTIAL, KAVACH_DELEGATION_CREDENTIAL, KAVACH_PERMISSION_CREDENTIAL, KVStore, MemoryStore, MultiSessionLimitError, OAuthProxyError, OneTapVerifyError, ProofSchema, RefreshTokenError, SSO_ERROR, SsoError, VC_CONTEXT_V1, VC_CONTEXT_V2, VC_TYPE_CREDENTIAL, VC_TYPE_PRESENTATION, VerifiableCredentialSchema, VerifiablePresentationSchema, additionalFields, admin, agentCards, agentDids, agents, anonymousAuth, apiKeys2 as apiKeys, apiKeys as apiKeysTable, approvalRequests, atlassianProvider, auditLogs, auth0Provider, bearerAuth, bitbucketProvider, budgetPolicies, buildDidDocument, buildSessionMetadata, classifyViolation, cognitoProvider, coinbaseProvider, constantTimeEqual, createAdditionalFieldsModule, createAdminModule, createAgentModule, createAnonymousAuthModule, createApiKeyManagerModule, createAppleProvider, createApprovalModule, createAtlassianProvider, createAuditModule, createCaptchaModule, createCookieSessionManager, createCostAttributionModule, createCustomSessionModule, createDatabase, createDatabaseSync, createDelegationModule, createDeviceAuthModule, createDidModule, createDiscordProvider, createDropboxProvider, createEmailOtpModule, createEmailTemplates, createEmailVerificationModule, createEphemeralSessionModule, createEventStreamModule, createFederationModule, createFigmaProvider, createGdprModule, createGithubProvider, createGitlabProvider, createGoogleProvider, createHibpModule, createI18n, createJwtSessionModule, createKavach, createLastLoginModule, createLinkedInProvider, createMagicLinkModule, createMicrosoftProvider, createMultiSessionModule, createNotionProvider, createOAuthModule, createOAuthProxyModule, createOidcProviderModule, createOneTapModule, createOneTimeTokenModule, createOpenApiModule, createOrgModule, createPasskeyModule, createPasswordResetModule, createPermissionEngine, createPhoneAuthModule, createPluginRouter, createPolarModule, createPolicyModule, createPresentation, createPrivilegeAnalyzer, createRateLimiter, createReBACModule, createRedditProvider, createRedirectChain, createScimModule, createSessionFreshnessModule, createSessionManager, createSessionRefresher, createSiweModule, createSlackProvider, createSpotifyProvider, createSsoModule, createStripeModule, createTables, createTenantModule, createTokenFamilyStore, createTotpModule, createTrustModule, createTrustedDeviceModule, createTwitchProvider, createTwitterProvider, createUsernameAuthModule, createVCIssuer, createVCVerifier, createWebhookModule2 as createWebhookModule, createZoomProvider, customAuth, customSession, de, delegationChains, deviceAuth, deviceLabelFromRequest, dropboxProvider, emailOtp, emailOtps, en, es, exportAuditAsVC, facebookProvider, figmaProvider, fr, fromBase64Url, fromHex, gdpr, generateCsrfToken, generateDidKey, generateDidWeb, generateId, generateOpenAPISpec, genericOIDC, getCookie2 as getCookie, getDidWebUrl, getPermissionTemplate, headerAuth, hmacSha1Raw, hmacSha256, hmacSha256Raw, huggingfaceProvider, importHmacKey, initializePlugins, ja, kakaoProvider, kickProvider, kvStore, lineProvider, linearProvider, listAuditRecords, magicLink, magicLinks, mcpServers, 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, oauthAccessTokens, oauthAuthorizationCodes, oauthClients, oauthProxy, oktaProvider, oneTap, orgInvitations, orgMembers, orgRoles, organization, organizations, parseCookies2 as parseCookies, parseCookiesFromRequest, passkey, passkeyChallenges, passkeyCredentials, paypalProvider, pbkdf2Hash, pbkdf2Verify, permissionTemplates, permissions, polar, polarProvider, railwayProvider, randomBytes, randomBytesHex, rateLimit, rateLimits, redditProvider, resolveDidKey, resolveDidWeb, robloxProvider, salesforceProvider, scim, serializeCookie, serializeCookieDeletion, sessions, sha1, sha256, sha256Raw, signPayload2 as signPayload, siwe, spotifyProvider, ssoConnections, stripe, tenants, tiktokProvider, toBase64Url, toHex, totpRecords, trustScores, twitchProvider, twoFactor, users, validateCsrfToken, validateOrigin, vercelProvider, verifyPayload, verifyPresentation, verifyWebhookSignature3 as verifyWebhookSignature, vkProvider, wechatProvider, withRateLimit, yahooProvider, zh, zoomProvider };
21758
23159
  //# sourceMappingURL=index.js.map
21759
23160
  //# sourceMappingURL=index.js.map