@@ -12,6 +12,43 @@ import { fetchWithRetry, measure } from "./utils.js";
1212
1313export const GITHUB_CLOUD_HOSTNAME = "github.com" ;
1414
15+ /**
16+ * GitHub token types and their prefixes.
17+ * @see https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
18+ */
19+ export type GitHubTokenType =
20+ | 'classic_pat' // ghp_ - Personal Access Token (classic)
21+ | 'oauth_user' // gho_ - OAuth App user token
22+ | 'app_user' // ghu_ - GitHub App user token
23+ | 'app_installation' // ghs_ - GitHub App installation token
24+ | 'fine_grained_pat' // github_pat_ - Fine-grained PAT
25+ | 'unknown' ;
26+
27+ /**
28+ * Token types that support scope introspection via x-oauth-scopes header.
29+ */
30+ export const SCOPE_INTROSPECTABLE_TOKEN_TYPES : GitHubTokenType [ ] = [ 'classic_pat' , 'oauth_user' ] ;
31+
32+ /**
33+ * Detects the GitHub token type based on its prefix.
34+ * @see https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
35+ */
36+ export const detectGitHubTokenType = ( token : string ) : GitHubTokenType => {
37+ if ( token . startsWith ( 'ghp_' ) ) return 'classic_pat' ;
38+ if ( token . startsWith ( 'gho_' ) ) return 'oauth_user' ;
39+ if ( token . startsWith ( 'ghu_' ) ) return 'app_user' ;
40+ if ( token . startsWith ( 'ghs_' ) ) return 'app_installation' ;
41+ if ( token . startsWith ( 'github_pat_' ) ) return 'fine_grained_pat' ;
42+ return 'unknown' ;
43+ } ;
44+
45+ /**
46+ * Checks if a token type supports OAuth scope introspection via x-oauth-scopes header.
47+ */
48+ export const supportsOAuthScopeIntrospection = ( tokenType : GitHubTokenType ) : boolean => {
49+ return SCOPE_INTROSPECTABLE_TOKEN_TYPES . includes ( tokenType ) ;
50+ } ;
51+
1552// Limit concurrent GitHub requests to avoid hitting rate limits and overwhelming installations.
1653const MAX_CONCURRENT_GITHUB_QUERIES = 5 ;
1754const githubQueryLimit = pLimit ( MAX_CONCURRENT_GITHUB_QUERIES ) ;
@@ -182,6 +219,10 @@ export const getRepoCollaborators = async (owner: string, repo: string, octokit:
182219 }
183220}
184221
222+ /**
223+ * Lists repositories that the authenticated user has explicit permission (:read, :write, or :admin) to access.
224+ * @see : https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user
225+ */
185226export const getReposForAuthenticatedUser = async ( visibility : 'all' | 'private' | 'public' = 'all' , octokit : Octokit ) => {
186227 try {
187228 const fetchFn = ( ) => octokit . paginate ( octokit . repos . listForAuthenticatedUser , {
@@ -198,9 +239,30 @@ export const getReposForAuthenticatedUser = async (visibility: 'all' | 'private'
198239 }
199240}
200241
201- // Gets oauth scopes
202- // @see : https://github.com/octokit/auth-token.js/?tab=readme-ov-file#find-out-what-scopes-are-enabled-for-oauth-tokens
203- export const getOAuthScopesForAuthenticatedUser = async ( octokit : Octokit ) => {
242+ /**
243+ * Gets OAuth scopes for a GitHub token.
244+ *
245+ * Returns `null` for token types that don't support scope introspection:
246+ * - GitHub App user tokens (ghu_)
247+ * - GitHub App installation tokens (ghs_)
248+ * - Fine-grained PATs (github_pat_)
249+ *
250+ * Returns scope array for tokens that support introspection:
251+ * - Classic PATs (ghp_)
252+ * - OAuth App user tokens (gho_)
253+ *
254+ * @see https://github.com/octokit/auth-token.js/?tab=readme-ov-file#find-out-what-scopes-are-enabled-for-oauth-tokens
255+ * @see https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
256+ */
257+ export const getOAuthScopesForAuthenticatedUser = async ( octokit : Octokit , token ?: string ) : Promise < string [ ] | null > => {
258+ // If token is provided, check if it supports scope introspection
259+ if ( token ) {
260+ const tokenType = detectGitHubTokenType ( token ) ;
261+ if ( ! supportsOAuthScopeIntrospection ( tokenType ) ) {
262+ return null ;
263+ }
264+ }
265+
204266 try {
205267 const response = await octokit . request ( "HEAD /" ) ;
206268 const scopes = response . headers [ "x-oauth-scopes" ] ?. split ( / , \s + / ) || [ ] ;
0 commit comments