Skip to content

Commit 47bf39e

Browse files
Merge pull request #9 from form3tech-oss/access-control-allow-origin
feat: add config option to set Access-Control-Allow-Origin header on responses
2 parents 9554e21 + 6845c14 commit 47bf39e

File tree

8 files changed

+93
-8
lines changed

8 files changed

+93
-8
lines changed

config/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ type Config struct {
77
}
88

99
type ServerConfig struct {
10-
Port int `mapstructure:"port"`
11-
SSL SSLConfig `mapstructure:"ssl"`
10+
Port int `mapstructure:"port"`
11+
SSL SSLConfig `mapstructure:"ssl"`
12+
AccessControlAllowOrigin string `mapstructure:"accessControlAllowOrigin"`
1213
}
1314

1415
type ProxyConfig struct {

config/config_test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ server:
44
enable: false
55
certFilePath: "/etc/ssl/certs/cert.crt"
66
keyFilePath: "/etc/ssl/private/private.key"
7+
accessControlAllowOrigin: "*"
78

89
proxy:
910
upstreamTarget: "https://api.form3.tech/v1"

config/loader_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func TestLoadConfig(t *testing.T) {
129129
CertFilePath: "/etc/ssl/certs/cert.crt",
130130
KeyFilePath: "/etc/ssl/private/private.key",
131131
},
132+
AccessControlAllowOrigin: "*",
132133
},
133134
Log: LogConfig{
134135
Level: "debug",

example/config_example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ server:
1111
certFilePath: "/etc/ssl/certs/cert.crt"
1212
# Location of the proxy's private key, if SSL is enabled
1313
keyFilePath: "/etc/ssl/private/private.key"
14+
# Value to be used in the Access-Control-Allow-Origin response header
15+
accessControlAllowOrigin: "*"
1416

1517
# Request forward proxy config
1618
proxy:

proxy/handler_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,69 @@ func TestHandler(t *testing.T) {
8484
}
8585
}
8686

87+
func TestHandlerCORS(t *testing.T) {
88+
tests := []struct {
89+
name string
90+
accessControlAllowOrigin string
91+
}{
92+
{
93+
"no value",
94+
"",
95+
},
96+
{
97+
"*",
98+
"*",
99+
},
100+
{
101+
"single domain",
102+
"https://test",
103+
},
104+
}
105+
106+
expectedRespBody := "OK"
107+
mockURL := "mock"
108+
mockCtrl := gomock.NewController(t)
109+
defer mockCtrl.Finish()
110+
111+
// Mock dependencies
112+
mockReqSigner := mockReqSigner(mockCtrl)
113+
mockMetricPublisher := mockMetricPublisher(mockCtrl, mockURL)
114+
115+
// Test upstream target that returns 200 OK
116+
targetSrv := testTargetServer(expectedRespBody)
117+
118+
// Reverse proxy pointing to test target
119+
rs, err := NewReverseProxy(targetSrv.URL)
120+
require.NoError(t, err)
121+
122+
// Test handler
123+
var w *test.TestResponseRecorder
124+
125+
for _, tt := range tests {
126+
h := NewHandler(rs, mockReqSigner, mockMetricPublisher)
127+
_, e := gin.CreateTestContext(w)
128+
e.NoRoute(
129+
RecoverMiddleware(mockMetricPublisher),
130+
LogAndMetricsMiddleware(mockMetricPublisher),
131+
CORSMiddleware(tt.accessControlAllowOrigin),
132+
h.ForwardRequest,
133+
)
134+
135+
t.Run(tt.name, func(t *testing.T) {
136+
w = test.NewTestResponseRecorder()
137+
138+
// Test request
139+
req, err := http.NewRequest(http.MethodGet, mockURL, nil)
140+
require.NoError(t, err)
141+
142+
e.ServeHTTP(w, req)
143+
144+
require.Equal(t, http.StatusOK, w.Code)
145+
require.Equal(t, w.Header().Get(AccessControlAllowOriginHeader), tt.accessControlAllowOrigin)
146+
})
147+
}
148+
}
149+
87150
func mockReqSigner(mockCtrl *gomock.Controller) *MockRequestSigner {
88151
mockReqSigner := NewMockRequestSigner(mockCtrl)
89152
mockReqSigner.EXPECT().SignRequest(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Request, error) {

proxy/middleware.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
log "github.com/sirupsen/logrus"
1414
)
1515

16+
const (
17+
AccessControlAllowOriginHeader string = "Access-Control-Allow-Origin"
18+
)
19+
1620
func RecoverMiddleware(metricPublisher MetricPublisher) gin.HandlerFunc {
1721
return func(c *gin.Context) {
1822
defer func() {
@@ -71,3 +75,12 @@ func LogAndMetricsMiddleware(metricPublisher MetricPublisher) gin.HandlerFunc {
7175
}).Info("request summary")
7276
}
7377
}
78+
79+
func CORSMiddleware(accessControlAllowOrigin string) gin.HandlerFunc {
80+
if accessControlAllowOrigin != "" {
81+
return func(c *gin.Context) {
82+
c.Writer.Header().Set(AccessControlAllowOriginHeader, accessControlAllowOrigin)
83+
}
84+
}
85+
return func(_ *gin.Context) {}
86+
}

proxy/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func NewServer(cfg config.ServerConfig, handler Handler, metric MetricPublisher)
3333
router.NoRoute(
3434
RecoverMiddleware(metric),
3535
LogAndMetricsMiddleware(metric),
36+
CORSMiddleware(cfg.AccessControlAllowOrigin),
3637
handler.ForwardRequest,
3738
)
3839

test/e2e_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"testing"
1616
"time"
1717

18-
"github.com/form3tech-oss/go-http-message-signatures"
18+
httpsignatures "github.com/form3tech-oss/go-http-message-signatures"
1919
"github.com/form3tech-oss/http-message-signing-proxy/cmd"
2020
"github.com/stretchr/testify/suite"
2121
)
@@ -37,6 +37,7 @@ const (
3737

3838
type e2eTestSuite struct {
3939
suite.Suite
40+
accessControlAllowOrigin string
4041
}
4142

4243
func (s *e2eTestSuite) msgVerifier() *httpsignatures.MessageVerifier {
@@ -86,10 +87,11 @@ func (s *e2eTestSuite) runProxy(upstreamTarget string) {
8687
rootCmd.SetArgs(append(
8788
[]string{"--config", cfgFile},
8889
genSetFlags(map[string]string{
89-
"server.ssl.certFilePath": sslCertFile,
90-
"server.ssl.keyFilePath": sslKeyFile,
91-
"proxy.signer.keyFilePath": privateKeyFile,
92-
"proxy.upstreamTarget": upstreamTarget,
90+
"server.ssl.certFilePath": sslCertFile,
91+
"server.ssl.keyFilePath": sslKeyFile,
92+
"proxy.signer.keyFilePath": privateKeyFile,
93+
"proxy.upstreamTarget": upstreamTarget,
94+
"server.accessControlAllowOrigin": s.accessControlAllowOrigin,
9395
})...,
9496
))
9597
go func() {
@@ -167,6 +169,7 @@ func (s *e2eTestSuite) TestProxy() {
167169
r, err := http.DefaultClient.Do(req)
168170
s.NoError(err)
169171
s.Equal(test.expectedStatus, r.StatusCode)
172+
s.Equal(s.accessControlAllowOrigin, r.Header.Get("Access-Control-Allow-Origin"))
170173

171174
if test.expectedStatus == http.StatusOK {
172175
resp, err := readHttpResp[successResp](r)
@@ -186,7 +189,7 @@ func (s *e2eTestSuite) TestProxy() {
186189
}
187190

188191
func TestE2ETestSuite(t *testing.T) {
189-
suite.Run(t, new(e2eTestSuite))
192+
suite.Run(t, &e2eTestSuite{accessControlAllowOrigin: "*"})
190193
}
191194

192195
func genSetFlags(m map[string]string) []string {

0 commit comments

Comments
 (0)