Skip to content

Commit 7883705

Browse files
authored
fix(server): add a github bearer token to the request if the site of interest is from GitHub (#5)
* fix(server): add a github bearer token to the request if the site of interest is from GitHub * test(server): add test to make sure the github sites handling works as expected
1 parent 0884dd6 commit 7883705

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

pkg/server/handlers.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import (
44
"fmt"
55
"io"
66
"net/http"
7+
"os"
78
"slices"
9+
"strings"
810
)
911

1012
// proxyHandler adds headers to overcome the CORS errors for the Jumble Nostr client.
1113
func proxyHandler() func(w http.ResponseWriter, r *http.Request) {
14+
// Get token from environment variable
15+
githubToken := os.Getenv("JUMBLE_PROXY_GITHUB_TOKEN")
1216
return func(w http.ResponseWriter, r *http.Request) {
1317
// add the paraters to fix CORS issues.
1418
w.Header().Set("Access-Control-Allow-Origin", "*")
@@ -25,6 +29,17 @@ func proxyHandler() func(w http.ResponseWriter, r *http.Request) {
2529
return
2630
}
2731

32+
// Check if the target URL is GitHub.
33+
if isGitHubURL(site) {
34+
// Set GitHub-specific headers in the proxy.
35+
req.Header.Set("Authorization", "Bearer "+githubToken)
36+
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; JumbleProxy/0.1)")
37+
req.Header.Set(
38+
"Accept",
39+
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
40+
)
41+
}
42+
2843
// Copy headers from original request.
2944
for header, values := range r.Header {
3045
for _, value := range values {
@@ -60,3 +75,9 @@ func proxyHandler() func(w http.ResponseWriter, r *http.Request) {
6075
w.Write(body)
6176
}
6277
}
78+
79+
func isGitHubURL(url string) bool {
80+
return strings.Contains(strings.ToLower(url), "github.com") ||
81+
strings.Contains(strings.ToLower(url), "api.github.com") ||
82+
strings.Contains(strings.ToLower(url), "gist.github.com")
83+
}

pkg/server/server_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"net/url"
10+
"os"
1011
"testing"
1112

1213
"github.com/danvergara/jumble-proxy-server/pkg/config"
@@ -72,3 +73,128 @@ func TestServer(t *testing.T) {
7273
htmlContent, string(body))
7374
}
7475
}
76+
77+
func TestProxyHandlerGitHub(t *testing.T) {
78+
// Set up GitHub token for testing.
79+
originalToken := os.Getenv("JUMBLE_PROXY_GITHUB_TOKEN")
80+
os.Setenv("JUMBLE_PROXY_GITHUB_TOKEN", "test-github-token")
81+
defer func() {
82+
if originalToken == "" {
83+
os.Unsetenv("JUMBLE_PROXY_GITHUB_TOKEN")
84+
} else {
85+
os.Setenv("JUMBLE_PROXY_GITHUB_TOKEN", originalToken)
86+
}
87+
}()
88+
89+
// Create a mock GitHub server.
90+
mockGitHubServer := httptest.NewServer(
91+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
92+
// Verify GitHub-specific headers were added by the proxy.
93+
if auth := r.Header.Get("Authorization"); auth != "Bearer test-github-token" {
94+
t.Errorf("Expected Authorization header 'Bearer test-github-token', got '%s'", auth)
95+
}
96+
if userAgent := r.Header.Get("User-Agent"); userAgent != "Mozilla/5.0 (compatible; JumbleProxy/0.1)" {
97+
t.Errorf(
98+
"Expected User-Agent 'Mozilla/5.0 (compatible; JumbleProxy/0.1)', got '%s'",
99+
userAgent,
100+
)
101+
}
102+
if accept := r.Header.Get("Accept"); accept != "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" {
103+
t.Errorf("Expected specific Accept header, got '%s'", accept)
104+
}
105+
106+
// Send back a mock GitHub response.
107+
w.Header().Set("Content-Type", "application/json")
108+
w.Header().Set("X-GitHub-Media-Type", "github.v3")
109+
w.WriteHeader(http.StatusOK)
110+
w.Write([]byte(`{"message": "Hello from GitHub!"}`))
111+
}),
112+
)
113+
defer mockGitHubServer.Close()
114+
115+
// Test cases for different GitHub URL formats.
116+
testCases := []struct {
117+
name string
118+
url string
119+
}{
120+
{"github.com", mockGitHubServer.URL + "/github.com/user/repo"},
121+
{"api.github.com", mockGitHubServer.URL + "/api.github.com/repos/user/repo"},
122+
{"gist.github.com", mockGitHubServer.URL + "/gist.github.com/user/gist-id"},
123+
}
124+
125+
for _, tc := range testCases {
126+
t.Run(tc.name, func(t *testing.T) {
127+
// Create a request to the proxy handler.
128+
req := httptest.NewRequest("GET", "/sites/"+url.QueryEscape(tc.url), nil)
129+
req.SetPathValue("site", tc.url)
130+
131+
// Create a response recorder.
132+
w := httptest.NewRecorder()
133+
134+
// Call the proxy handler.
135+
handler := proxyHandler()
136+
handler(w, req)
137+
138+
// Check response status.
139+
if w.Code != http.StatusOK {
140+
t.Errorf("Expected status 200, got %d", w.Code)
141+
}
142+
143+
// Validate CORS headers.
144+
expectedHeaders := map[string]string{
145+
"Access-Control-Allow-Origin": "*",
146+
"Access-Control-Allow-Methods": "GET",
147+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
148+
}
149+
150+
for header, expectedValue := range expectedHeaders {
151+
if actualValue := w.Header().Get(header); actualValue != expectedValue {
152+
t.Errorf(
153+
"Expected %s header '%s', got '%s'",
154+
header,
155+
expectedValue,
156+
actualValue,
157+
)
158+
}
159+
}
160+
161+
// Validate that GitHub response headers are proxied.
162+
if contentType := w.Header().Get("Content-Type"); contentType != "application/json" {
163+
t.Errorf("Expected Content-Type 'application/json', got '%s'", contentType)
164+
}
165+
166+
if githubHeader := w.Header().Get("X-GitHub-Media-Type"); githubHeader != "github.v3" {
167+
t.Errorf("Expected X-GitHub-Media-Type 'github.v3', got '%s'", githubHeader)
168+
}
169+
170+
// Validate response body.
171+
expectedBody := `{"message": "Hello from GitHub!"}`
172+
if actualBody := w.Body.String(); actualBody != expectedBody {
173+
t.Errorf("Expected body '%s', got '%s'", expectedBody, actualBody)
174+
}
175+
})
176+
}
177+
}
178+
179+
func TestIsGitHubURL(t *testing.T) {
180+
testCases := []struct {
181+
url string
182+
expected bool
183+
}{
184+
{"https://github.com/user/repo", true},
185+
{"https://api.github.com/repos/user/repo", true},
186+
{"https://gist.github.com/user/gist-id", true},
187+
{"https://GITHUB.COM/user/repo", true}, // case insensitive
188+
{"https://example.com", false},
189+
{"https://gitlab.com/user/repo", false},
190+
}
191+
192+
for _, tc := range testCases {
193+
t.Run(tc.url, func(t *testing.T) {
194+
result := isGitHubURL(tc.url)
195+
if result != tc.expected {
196+
t.Errorf("isGitHubURL(%s) = %v, expected %v", tc.url, result, tc.expected)
197+
}
198+
})
199+
}
200+
}

0 commit comments

Comments
 (0)