@sentry/junior-github 0.75.0 → 0.76.1
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/index.js +109 -37
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const GITHUB_AUTH_TOKEN_ENV = "GITHUB_TOKEN";
|
|
|
13
13
|
const GITHUB_AUTH_TOKEN_PLACEHOLDER = "ghp_host_managed_credential";
|
|
14
14
|
const MAX_LEASE_MS = 60 * 60 * 1000;
|
|
15
15
|
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
16
|
+
const USER_REFRESH_TIMEOUT_MS = 20_000;
|
|
16
17
|
const GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
|
|
17
18
|
const HTTP_READ_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
18
19
|
const USER_TOKEN_GRANTS = new Set(["user-read", "user-write"]);
|
|
@@ -333,20 +334,30 @@ function parseOAuthTokenResponse(data, requestedScope) {
|
|
|
333
334
|
refreshToken: data.refresh_token,
|
|
334
335
|
...(scope ? { scope } : {}),
|
|
335
336
|
};
|
|
336
|
-
if (data.expires_in
|
|
337
|
-
|
|
337
|
+
if (data.expires_in !== undefined) {
|
|
338
|
+
if (
|
|
339
|
+
typeof data.expires_in !== "number" ||
|
|
340
|
+
!Number.isFinite(data.expires_in) ||
|
|
341
|
+
data.expires_in <= 0
|
|
342
|
+
) {
|
|
343
|
+
throw new Error("OAuth token response returned invalid expires_in");
|
|
344
|
+
}
|
|
345
|
+
result.expiresAt = Date.now() + data.expires_in * 1000;
|
|
338
346
|
}
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
347
|
+
if (data.refresh_token_expires_in !== undefined) {
|
|
348
|
+
if (
|
|
349
|
+
typeof data.refresh_token_expires_in !== "number" ||
|
|
350
|
+
!Number.isFinite(data.refresh_token_expires_in) ||
|
|
351
|
+
data.refresh_token_expires_in <= 0
|
|
352
|
+
) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
"OAuth token response returned invalid refresh_token_expires_in",
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
result.refreshTokenExpiresAt =
|
|
358
|
+
Date.now() + data.refresh_token_expires_in * 1000;
|
|
345
359
|
}
|
|
346
|
-
return
|
|
347
|
-
...result,
|
|
348
|
-
expiresAt: Date.now() + data.expires_in * 1000,
|
|
349
|
-
};
|
|
360
|
+
return result;
|
|
350
361
|
}
|
|
351
362
|
|
|
352
363
|
async function refreshUserAccessToken(input) {
|
|
@@ -364,6 +375,7 @@ async function refreshUserAccessToken(input) {
|
|
|
364
375
|
method: "POST",
|
|
365
376
|
headers: request.headers,
|
|
366
377
|
body: request.body,
|
|
378
|
+
signal: AbortSignal.timeout(USER_REFRESH_TIMEOUT_MS),
|
|
367
379
|
});
|
|
368
380
|
const responseText = await response.text();
|
|
369
381
|
const responseData = parseOAuthResponseJson(responseText);
|
|
@@ -528,6 +540,83 @@ async function tokensWithAccount(tokenSlot, stored, scope) {
|
|
|
528
540
|
return { ok: true, tokens: updated };
|
|
529
541
|
}
|
|
530
542
|
|
|
543
|
+
function shouldRefreshUserToken(stored, now = Date.now()) {
|
|
544
|
+
return (
|
|
545
|
+
stored.expiresAt !== undefined && stored.expiresAt - now < REFRESH_BUFFER_MS
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function canUseStoredUserToken(stored) {
|
|
550
|
+
return (
|
|
551
|
+
stored.expiresAt === undefined ||
|
|
552
|
+
(stored.expiresAt > Date.now() && !shouldRefreshUserToken(stored))
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Re-read under the token-slot refresh gate so concurrent callers reuse the winner's rotated tokens. */
|
|
557
|
+
async function refreshUserTokensWithLock(tokenSlot, scope, options) {
|
|
558
|
+
return await tokenSlot.withRefresh(async () => {
|
|
559
|
+
const latest = await tokenSlot.get();
|
|
560
|
+
if (!latest) {
|
|
561
|
+
return {
|
|
562
|
+
ok: false,
|
|
563
|
+
result: credentialNeeded("Connect your GitHub account.", scope),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (!hasRequiredOAuthScope(latest.scope, scope)) {
|
|
567
|
+
return {
|
|
568
|
+
ok: false,
|
|
569
|
+
result: credentialNeeded(
|
|
570
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
571
|
+
scope,
|
|
572
|
+
),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
if (canUseStoredUserToken(latest)) {
|
|
576
|
+
return { ok: true, tokens: latest };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
let refreshed;
|
|
580
|
+
try {
|
|
581
|
+
refreshed = await refreshUserAccessToken({
|
|
582
|
+
clientIdEnv: options.clientIdEnv,
|
|
583
|
+
clientSecretEnv: options.clientSecretEnv,
|
|
584
|
+
refreshToken: latest.refreshToken,
|
|
585
|
+
requestedScope: latest.scope ?? scope,
|
|
586
|
+
});
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (!(error instanceof GitHubUserRefreshRejectedError)) {
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
ok: false,
|
|
593
|
+
result: credentialNeeded(
|
|
594
|
+
"Your GitHub authorization has expired.",
|
|
595
|
+
scope,
|
|
596
|
+
),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
if (!hasRequiredOAuthScope(refreshed.scope, scope)) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
result: credentialNeeded(
|
|
603
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
604
|
+
scope,
|
|
605
|
+
),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const refreshedTokens = {
|
|
609
|
+
...(latest.refreshTokenExpiresAt
|
|
610
|
+
? { refreshTokenExpiresAt: latest.refreshTokenExpiresAt }
|
|
611
|
+
: {}),
|
|
612
|
+
...refreshed,
|
|
613
|
+
...(latest.account ? { account: latest.account } : {}),
|
|
614
|
+
};
|
|
615
|
+
await tokenSlot.set(refreshedTokens);
|
|
616
|
+
return { ok: true, tokens: refreshedTokens };
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
531
620
|
async function issueUserCredential(ctx, options) {
|
|
532
621
|
const scope = options.userScope;
|
|
533
622
|
const tokenSlot = ctx.tokens.currentUser ?? ctx.tokens.credentialSubject;
|
|
@@ -558,34 +647,17 @@ async function issueUserCredential(ctx, options) {
|
|
|
558
647
|
stored.expiresAt !== undefined &&
|
|
559
648
|
stored.expiresAt - now < REFRESH_BUFFER_MS
|
|
560
649
|
) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
});
|
|
569
|
-
} catch (error) {
|
|
570
|
-
if (!(error instanceof GitHubUserRefreshRejectedError)) {
|
|
571
|
-
throw error;
|
|
572
|
-
}
|
|
573
|
-
return credentialNeeded("Your GitHub authorization has expired.", scope);
|
|
574
|
-
}
|
|
575
|
-
if (!hasRequiredOAuthScope(refreshed.scope, scope)) {
|
|
576
|
-
return credentialNeeded(
|
|
577
|
-
"Your GitHub authorization needs to be refreshed.",
|
|
578
|
-
scope,
|
|
579
|
-
);
|
|
650
|
+
const refreshResult = await refreshUserTokensWithLock(
|
|
651
|
+
tokenSlot,
|
|
652
|
+
scope,
|
|
653
|
+
options,
|
|
654
|
+
);
|
|
655
|
+
if (!refreshResult.ok) {
|
|
656
|
+
return refreshResult.result;
|
|
580
657
|
}
|
|
581
|
-
const refreshedTokens = {
|
|
582
|
-
...refreshed,
|
|
583
|
-
...(stored.account ? { account: stored.account } : {}),
|
|
584
|
-
};
|
|
585
|
-
await tokenSlot.set(refreshedTokens);
|
|
586
658
|
const withAccount = await tokensWithAccount(
|
|
587
659
|
tokenSlot,
|
|
588
|
-
|
|
660
|
+
refreshResult.tokens,
|
|
589
661
|
scope,
|
|
590
662
|
);
|
|
591
663
|
if (!withAccount.ok) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"SETUP.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@sentry/junior-plugin-api": "0.
|
|
28
|
+
"@sentry/junior-plugin-api": "0.76.1"
|
|
29
29
|
}
|
|
30
30
|
}
|