Skip to content

Commit a730b6b

Browse files
committed
fixup! feat: Add api-login domain to connect-src directive
1 parent bb6d3e6 commit a730b6b

File tree

4 files changed

+80
-113
lines changed

4 files changed

+80
-113
lines changed

web/middlewares/domain_utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package middlewares
2+
3+
import (
4+
"strings"
5+
)
6+
7+
func parseDomainForApiLogin(domain string) (string, string, bool) {
8+
return strings.Cut(domain, ".")
9+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package middlewares
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestParseDomainForApiLogin tests the parseDomainForApiLogin function
8+
// which is a simple wrapper around strings.Cut.
9+
func TestParseDomainForApiLogin(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
expectedBefore string
14+
expectedAfter string
15+
expectedFound bool
16+
}{
17+
{
18+
name: "domain with multiple parts",
19+
input: "alice.cozy.example.com",
20+
expectedBefore: "alice",
21+
expectedAfter: "cozy.example.com",
22+
expectedFound: true,
23+
},
24+
{
25+
name: "domain with exactly 2 parts",
26+
input: "cozy.example.com",
27+
expectedBefore: "cozy",
28+
expectedAfter: "example.com",
29+
expectedFound: true,
30+
},
31+
{
32+
name: "domain with exactly 1 part",
33+
input: "localhost",
34+
expectedBefore: "localhost",
35+
expectedAfter: "",
36+
expectedFound: false,
37+
},
38+
{
39+
name: "empty domain",
40+
input: "",
41+
expectedBefore: "",
42+
expectedAfter: "",
43+
expectedFound: false,
44+
},
45+
{
46+
name: "domain with special characters",
47+
input: "user-123.cozy.example.com",
48+
expectedBefore: "user-123",
49+
expectedAfter: "cozy.example.com",
50+
expectedFound: true,
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
before, after, found := parseDomainForApiLogin(tt.input)
57+
if before != tt.expectedBefore {
58+
t.Errorf("parseDomainForApiLogin(%q) before = %q, want %q", tt.input, before, tt.expectedBefore)
59+
}
60+
if after != tt.expectedAfter {
61+
t.Errorf("parseDomainForApiLogin(%q) after = %q, want %q", tt.input, after, tt.expectedAfter)
62+
}
63+
if found != tt.expectedFound {
64+
t.Errorf("parseDomainForApiLogin(%q) found = %t, want %t", tt.input, found, tt.expectedFound)
65+
}
66+
})
67+
}
68+
}

web/middlewares/secure.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,11 +307,9 @@ func (b cspBuilder) makeCSPHeader(header, cspAllowList string, sources []CSPSour
307307
}
308308
// Add api-login.{org_id}.{domain without prefix} to connect-src directive if present
309309
if header == "connect-src" && b.instance != nil && b.instance.OrgID != "" {
310-
if parts := strings.Split(b.instance.Domain, "."); len(parts) >= 3 {
311-
domainWithoutPrefix := strings.Join(parts[1:], ".")
312-
headers = append(headers, "api-login-"+b.instance.OrgID+"."+domainWithoutPrefix)
313-
} else {
314-
headers = append(headers, "api-login-"+b.instance.OrgID+"."+b.instance.Domain)
310+
_, domain, found := parseDomainForApiLogin(b.instance.Domain)
311+
if found {
312+
headers = append(headers, "api-login-"+b.instance.OrgID+"."+domain)
315313
}
316314
}
317315
if len(headers) == 0 {

web/middlewares/secure_test.go

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -202,112 +202,4 @@ func TestSecure(t *testing.T) {
202202
}
203203
}
204204
})
205-
206-
t.Run("SecureMiddlewareCSPWithOrgDomainAPILogin", func(t *testing.T) {
207-
// Test case 1: Domain with 3+ parts (alice.twake.app)
208-
// Should strip the first part and use "twake.app"
209-
e1 := echo.New()
210-
req1, _ := http.NewRequest(echo.GET, "http://alice.twake.app/", nil)
211-
rec1 := httptest.NewRecorder()
212-
c1 := e1.NewContext(req1, rec1)
213-
inst1 := &instance.Instance{
214-
Domain: "alice.twake.app",
215-
OrgDomain: "example.com",
216-
OrgID: "org123",
217-
}
218-
c1.Set("instance", inst1)
219-
h1 := Secure(&SecureConfig{
220-
CSPConnectSrc: []CSPSource{CSPSrcSelf},
221-
})(echo.NotFoundHandler)
222-
_ = h1(c1)
223-
224-
csp1 := rec1.Header().Get(echo.HeaderContentSecurityPolicy)
225-
226-
// Should contain api-login-org123.twake.app (domain without alice prefix)
227-
assert.Contains(t, csp1, "api-login-org123.twake.app",
228-
"connect-src should contain api-login-org123.twake.app for domain with 3+ parts. CSP: %s", csp1)
229-
230-
connectSrcIndex := strings.Index(csp1, "connect-src ")
231-
assert.NotEqual(t, -1, connectSrcIndex,
232-
"connect-src should be present in CSP. Full CSP: %s", csp1)
233-
234-
connectSrcEnd := strings.Index(csp1[connectSrcIndex:], ";")
235-
assert.NotEqual(t, -1, connectSrcEnd,
236-
"connect-src should end with semicolon")
237-
238-
connectSrcContent := csp1[connectSrcIndex : connectSrcIndex+connectSrcEnd]
239-
assert.Contains(t, connectSrcContent, "api-login-org123.twake.app",
240-
"connect-src should contain api-login-org123.twake.app. Found: %s", connectSrcContent)
241-
242-
// Test case 2: Domain with fewer than 3 parts (cozy.local)
243-
// Should use the domain as-is
244-
e2 := echo.New()
245-
req2, _ := http.NewRequest(echo.GET, "http://cozy.local/", nil)
246-
rec2 := httptest.NewRecorder()
247-
c2 := e2.NewContext(req2, rec2)
248-
inst2 := &instance.Instance{
249-
Domain: "cozy.local",
250-
OrgDomain: "example.com",
251-
OrgID: "org456",
252-
}
253-
c2.Set("instance", inst2)
254-
h2 := Secure(&SecureConfig{
255-
CSPConnectSrc: []CSPSource{CSPSrcSelf},
256-
})(echo.NotFoundHandler)
257-
_ = h2(c2)
258-
259-
csp2 := rec2.Header().Get(echo.HeaderContentSecurityPolicy)
260-
261-
// Should contain api-login-org456.cozy.local (full domain used)
262-
assert.Contains(t, csp2, "api-login-org456.cozy.local",
263-
"connect-src should contain api-login-org456.cozy.local for domain with <3 parts. CSP: %s", csp2)
264-
265-
connectSrcIndex2 := strings.Index(csp2, "connect-src ")
266-
assert.NotEqual(t, -1, connectSrcIndex2,
267-
"connect-src should be present in CSP. Full CSP: %s", csp2)
268-
269-
connectSrcEnd2 := strings.Index(csp2[connectSrcIndex2:], ";")
270-
assert.NotEqual(t, -1, connectSrcEnd2,
271-
"connect-src should end with semicolon")
272-
273-
connectSrcContent2 := csp2[connectSrcIndex2 : connectSrcIndex2+connectSrcEnd2]
274-
assert.Contains(t, connectSrcContent2, "api-login-org456.cozy.local",
275-
"connect-src should contain api-login-org456.cozy.local. Found: %s", connectSrcContent2)
276-
277-
// Test case 3: Domain with 4 parts (bob.acme.twake.app)
278-
// Should strip only the first part and use "acme.twake.app"
279-
e3 := echo.New()
280-
req3, _ := http.NewRequest(echo.GET, "http://bob.acme.twake.app/", nil)
281-
rec3 := httptest.NewRecorder()
282-
c3 := e3.NewContext(req3, rec3)
283-
inst3 := &instance.Instance{
284-
Domain: "bob.acme.twake.app",
285-
OrgDomain: "example.org",
286-
OrgID: "org789",
287-
}
288-
c3.Set("instance", inst3)
289-
h3 := Secure(&SecureConfig{
290-
CSPConnectSrc: []CSPSource{CSPSrcSelf},
291-
})(echo.NotFoundHandler)
292-
_ = h3(c3)
293-
294-
csp3 := rec3.Header().Get(echo.HeaderContentSecurityPolicy)
295-
296-
// Should contain api-login-org789.acme.twake.app (domain without bob prefix)
297-
assert.Contains(t, csp3, "api-login-org789.acme.twake.app",
298-
"connect-src should contain api-login-org789.acme.twake.app for domain with 4 parts. CSP: %s", csp3)
299-
300-
// Verify it's in connect-src directive
301-
connectSrcIndex3 := strings.Index(csp3, "connect-src ")
302-
assert.NotEqual(t, -1, connectSrcIndex3,
303-
"connect-src should be present in CSP. Full CSP: %s", csp3)
304-
305-
connectSrcEnd3 := strings.Index(csp3[connectSrcIndex3:], ";")
306-
assert.NotEqual(t, -1, connectSrcEnd3,
307-
"connect-src should end with semicolon")
308-
309-
connectSrcContent3 := csp3[connectSrcIndex3 : connectSrcIndex3+connectSrcEnd3]
310-
assert.Contains(t, connectSrcContent3, "api-login-org789.acme.twake.app",
311-
"connect-src should contain api-login-org789.acme.twake.app. Found: %s", connectSrcContent3)
312-
})
313205
}

0 commit comments

Comments
 (0)