mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-19 15:14:54 +08:00
Allow direct IP webui sessions
This commit is contained in:
1
cmd/workspace/embedkeep.txt
Normal file
1
cmd/workspace/embedkeep.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
embedded workspace placeholder
|
||||||
@@ -343,6 +343,25 @@ func requestOrigin(r *http.Request) string {
|
|||||||
return scheme + "://" + canonicalOriginHost(r.Host, scheme == "https")
|
return scheme + "://" + canonicalOriginHost(r.Host, scheme == "https")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameSiteOrigin(originA, originB string) bool {
|
||||||
|
parsedA, err := url.Parse(strings.TrimSpace(originA))
|
||||||
|
if err != nil || parsedA == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
parsedB, err := url.Parse(strings.TrimSpace(originB))
|
||||||
|
if err != nil || parsedB == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
schemeA := strings.ToLower(strings.TrimSpace(parsedA.Scheme))
|
||||||
|
schemeB := strings.ToLower(strings.TrimSpace(parsedB.Scheme))
|
||||||
|
if schemeA == "" || schemeB == "" || schemeA != schemeB {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hostA := strings.ToLower(strings.TrimSpace(parsedA.Hostname()))
|
||||||
|
hostB := strings.ToLower(strings.TrimSpace(parsedB.Hostname()))
|
||||||
|
return hostA != "" && hostA == hostB
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) isTrustedOrigin(r *http.Request) bool {
|
func (s *Server) isTrustedOrigin(r *http.Request) bool {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return false
|
return false
|
||||||
@@ -360,7 +379,17 @@ func (s *Server) isTrustedOrigin(r *http.Request) bool {
|
|||||||
|
|
||||||
func (s *Server) shouldUseCrossSiteCookie(r *http.Request) bool {
|
func (s *Server) shouldUseCrossSiteCookie(r *http.Request) bool {
|
||||||
origin := normalizeOrigin(r.Header.Get("Origin"))
|
origin := normalizeOrigin(r.Header.Get("Origin"))
|
||||||
return origin != "" && origin != requestOrigin(r) && s.isTrustedOrigin(r)
|
if origin == "" || !s.isTrustedOrigin(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reqOrigin := requestOrigin(r)
|
||||||
|
if origin == reqOrigin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if sameSiteOrigin(origin, reqOrigin) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) websocketUpgrader() *websocket.Upgrader {
|
func (s *Server) websocketUpgrader() *websocket.Upgrader {
|
||||||
|
|||||||
@@ -209,9 +209,10 @@ func TestHandleWebUINodeArtifactsAppliesRetentionConfig(t *testing.T) {
|
|||||||
t.Fatalf("save config: %v", err)
|
t.Fatalf("save config: %v", err)
|
||||||
}
|
}
|
||||||
srv.SetConfigPath(cfgPath)
|
srv.SetConfigPath(cfgPath)
|
||||||
|
base := time.Now().UTC().Add(-2 * time.Hour)
|
||||||
auditLines := strings.Join([]string{
|
auditLines := strings.Join([]string{
|
||||||
"{\"time\":\"2026-03-09T00:00:00Z\",\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"one.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"b25l\"}]}",
|
fmt.Sprintf("{\"time\":%q,\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"one.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"b25l\"}]}", base.Format(time.RFC3339)),
|
||||||
"{\"time\":\"2026-03-09T00:01:00Z\",\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"two.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"dHdv\"}]}",
|
fmt.Sprintf("{\"time\":%q,\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"two.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"dHdv\"}]}", base.Add(time.Minute).Format(time.RFC3339)),
|
||||||
}, "\n") + "\n"
|
}, "\n") + "\n"
|
||||||
if err := os.WriteFile(filepath.Join(workspace, "memory", "nodes-dispatch-audit.jsonl"), []byte(auditLines), 0o644); err != nil {
|
if err := os.WriteFile(filepath.Join(workspace, "memory", "nodes-dispatch-audit.jsonl"), []byte(auditLines), 0o644); err != nil {
|
||||||
t.Fatalf("write audit: %v", err)
|
t.Fatalf("write audit: %v", err)
|
||||||
|
|||||||
@@ -197,6 +197,34 @@ func TestHandleWebUIAuthSessionSetsCrossSiteCookieForAllowedOrigin(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleWebUIAuthSessionKeepsLaxCookieForSameIPDifferentPort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
srv := NewServer("0.0.0.0", 0, "secret-token", nil)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "http://134.195.210.114:18790/api/auth/session", nil)
|
||||||
|
req.Host = "134.195.210.114:18790"
|
||||||
|
req.Header.Set("Origin", "http://134.195.210.114:3000")
|
||||||
|
req.Header.Set("Authorization", "Bearer secret-token")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
srv.withCORS(http.HandlerFunc(srv.handleWebUIAuthSession)).ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
cookies := rec.Result().Cookies()
|
||||||
|
if len(cookies) != 1 {
|
||||||
|
t.Fatalf("expected one cookie, got %d", len(cookies))
|
||||||
|
}
|
||||||
|
if cookies[0].SameSite != http.SameSiteLaxMode {
|
||||||
|
t.Fatalf("expected SameSite=Lax for same-IP direct session, got %v", cookies[0].SameSite)
|
||||||
|
}
|
||||||
|
if cookies[0].Secure {
|
||||||
|
t.Fatalf("expected non-secure cookie for plain HTTP direct IP session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleWebUIUploadDoesNotExposeAbsolutePath(t *testing.T) {
|
func TestHandleWebUIUploadDoesNotExposeAbsolutePath(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
1
workspace/embedkeep.txt
Normal file
1
workspace/embedkeep.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
workspace assets placeholder
|
||||||
Reference in New Issue
Block a user