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