-
Notifications
You must be signed in to change notification settings - Fork 138
fix: stop exposing client-side tokens #1329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a2b6a8c
8f47c28
fb43872
0190eea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ export interface GitHubOrgStats { | |
| totalRepositories: number; | ||
| totalContributors: number; | ||
| publicRepositories: number; | ||
| discussionsCount: number; | ||
| discussionsCount: number | null; | ||
| lastUpdated: number; | ||
| } | ||
|
|
||
|
|
@@ -64,6 +64,8 @@ class GitHubService { | |
| private readonly CACHE_KEY = "github_org_stats"; | ||
| private readonly CACHE_DURATION = 30 * 60 * 1000; // 30 minutes in milliseconds | ||
| private readonly BASE_URL = "https://api.github.com"; | ||
| private readonly DISCUSSIONS_UNAVAILABLE_MESSAGE = | ||
| "GitHub Discussions are disabled until a server-side GitHub proxy is configured."; | ||
|
|
||
| // === ADDED: include anonymous contributors configurable (default false) | ||
| private includeAnonymousContributors = false; | ||
|
|
@@ -96,6 +98,31 @@ class GitHubService { | |
| return headers; | ||
| } | ||
|
|
||
| private canUseGitHubGraphQL(): boolean { | ||
| return typeof window === "undefined"; | ||
| } | ||
|
|
||
| private getGitHubToken(): string | null { | ||
| if (typeof window !== "undefined") { | ||
| return null; | ||
| } | ||
|
|
||
| return process.env.GITHUB_TOKEN?.trim() || this.token || null; | ||
| } | ||
|
|
||
| private getGraphQLHeaders(): Record<string, string> { | ||
| const token = this.getGitHubToken(); | ||
|
|
||
| if (!token) { | ||
| throw new Error(this.DISCUSSIONS_UNAVAILABLE_MESSAGE); | ||
| } | ||
|
|
||
| return { | ||
| ...this.getHeaders(), | ||
| Authorization: `Bearer ${token}`, | ||
| }; | ||
| } | ||
|
|
||
| // === ADDED: setter to toggle anonymous contributors inclusion | ||
| setIncludeAnonymousContributors(value: boolean) { | ||
| this.includeAnonymousContributors = value; | ||
|
|
@@ -287,16 +314,16 @@ class GitHubService { | |
| return totalContributors; | ||
| } | ||
|
|
||
| // === UPDATED: Get discussions count for a specific repository (default: "Support") | ||
| // Reason: previous code used an org-wide issues search which returned issues, not discussions. | ||
| // This function uses GraphQL to read repository.discussions.totalCount (repo-specific). | ||
| // If you need org-wide discussions count, we should iterate all repos and sum totalCount (heavier). | ||
| // GitHub GraphQL requires authentication, so the browser should not call it directly. | ||
| private async getDiscussionsCount( | ||
| signal?: AbortSignal, | ||
| repoName: string = "Support", | ||
| ): Promise<number> { | ||
| ): Promise<number | null> { | ||
| if (!this.canUseGitHubGraphQL()) { | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| // GraphQL query to get discussions totalCount for a repository | ||
| const query = ` | ||
| query ($owner: String!, $name: String!) { | ||
| repository(owner: $owner, name: $name) { | ||
|
|
@@ -309,7 +336,7 @@ class GitHubService { | |
| const resp = await fetch("https://api.github.com/graphql", { | ||
| method: "POST", | ||
| headers: { | ||
| ...this.getHeaders(), | ||
| ...this.getGraphQLHeaders(), | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({ query, variables }), | ||
|
|
@@ -318,20 +345,20 @@ class GitHubService { | |
|
|
||
| if (!resp.ok) { | ||
| console.warn(`GraphQL request for discussions failed: ${resp.status}`); | ||
| return 0; | ||
| return null; | ||
| } | ||
|
|
||
| const data = await resp.json(); | ||
| if (data.errors) { | ||
| console.warn("GraphQL errors while fetching discussions:", data.errors); | ||
| return 0; | ||
| return null; | ||
| } | ||
|
|
||
| const count = data?.data?.repository?.discussions?.totalCount || 0; | ||
| return Number(count); | ||
| } catch (error) { | ||
| console.warn("Error fetching discussions count via GraphQL:", error); | ||
| return 0; | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -363,11 +390,10 @@ class GitHubService { | |
| 0, | ||
| ); | ||
|
|
||
| // Estimate contributors and get discussions count | ||
| // === UPDATED: getDiscussionsCount now uses GraphQL for a specific repo (default 'Support') | ||
| // Estimate contributors and fetch discussion stats when a server-side context is available. | ||
| const [totalContributors, discussionsCount] = await Promise.all([ | ||
| this.estimateContributors(activeRepos, signal), | ||
| this.getDiscussionsCount(signal), // default repoName: "Support" (change if you prefer another repo) | ||
| this.getDiscussionsCount(signal), | ||
| ]); | ||
|
|
||
| const stats: GitHubOrgStats = { | ||
|
|
@@ -394,7 +420,7 @@ class GitHubService { | |
| totalRepositories: 0, | ||
| publicRepositories: 0, | ||
| totalContributors: 0, | ||
| discussionsCount: 0, | ||
| discussionsCount: null, | ||
| lastUpdated: Date.now(), | ||
| }; | ||
|
|
||
|
|
@@ -422,11 +448,16 @@ class GitHubService { | |
| return { cached: true, age, expiresIn }; | ||
| } | ||
|
|
||
| // Fetch GitHub Discussions using GraphQL API (existing method kept intact) | ||
| async fetchDiscussions( | ||
| limit: number = 20, | ||
| signal?: AbortSignal, | ||
| ): Promise<GitHubDiscussion[]> { | ||
| if (!this.canUseGitHubGraphQL()) { | ||
| throw new Error(this.DISCUSSIONS_UNAVAILABLE_MESSAGE); | ||
| } | ||
|
|
||
|
Comment on lines
+455
to
+458
|
||
| const graphqlHeaders = this.getGraphQLHeaders(); | ||
|
|
||
| const query = ` | ||
| query GetDiscussions($owner: String!, $name: String!, $first: Int!) { | ||
| repository(owner: $owner, name: $name) { | ||
|
|
@@ -475,7 +506,7 @@ class GitHubService { | |
| const response = await fetch("https://api.github.com/graphql", { | ||
| method: "POST", | ||
| headers: { | ||
| ...this.getHeaders(), | ||
| ...graphqlHeaders, | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({ query, variables }), | ||
|
|
@@ -525,9 +556,7 @@ class GitHubService { | |
| ); | ||
| } catch (error) { | ||
| console.error("Error fetching discussions:", error); | ||
|
|
||
| // Return mock data for development/fallback | ||
| return this.getMockDiscussions(); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getDiscussionsCount()returns0when GraphQL is unavailable in the browser. That makes downstream UI/reporting treat the value as a real count ("0 discussions") rather than "unavailable", which is misleading. Consider representing this asnull/undefined(and updatingGitHubOrgStats+ UI), or surfacing an explicit "unavailable" flag/message instead of overloading0.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Abhash-Chakraborty this can be doable