Skip to content

Commit c58fc8f

Browse files
SteffenDEclaudejosevalim
authored
Add /config endpoint and remove X-Frame-Options headers (#88)
Co-authored-by: Claude <[email protected]> Co-authored-by: José Valim <[email protected]>
1 parent 2e56e81 commit c58fc8f

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ If you want to use Docker for development, you either need to enable the configu
2929

3030
### Content security policy
3131

32-
If you have enabled Content-Security-Policy, Tidewave will automatically enable "unsafe-eval" under `script-src` in order for contextual browser testing to work correctly.
32+
If you have enabled Content-Security-Policy, Tidewave will automatically enable "unsafe-eval" under `script-src` in order for contextual browser testing to work correctly. It also disables the `frame-ancestors` directive.
3333

3434
### Web server requirements
3535

lib/tidewave/middleware.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Tidewave::Middleware
1414
SSE_ROUTE = "mcp".freeze
1515
MESSAGES_ROUTE = "mcp/message".freeze
1616
SHELL_ROUTE = "shell".freeze
17+
CONFIG_ROUTE = "config".freeze
1718

1819
INVALID_IP = <<~TEXT.freeze
1920
For security reasons, Tidewave does not accept remote connections by default.
@@ -62,23 +63,26 @@ def call(env)
6263
case [ request.request_method, path ]
6364
when [ "GET", [ TIDEWAVE_ROUTE ] ]
6465
return home(request)
66+
when [ "GET", [ TIDEWAVE_ROUTE, CONFIG_ROUTE ] ]
67+
return config_endpoint(request)
6568
when [ "POST", [ TIDEWAVE_ROUTE, SHELL_ROUTE ] ]
6669
return shell(request)
6770
end
6871
end
6972

70-
@app.call(env)
73+
status, headers, body = @app.call(env)
74+
75+
# Remove CSP and X-Frame-Options headers for non-Tidewave routes
76+
# to allow embedding the app in Tidewave
77+
headers.delete("X-Frame-Options")
78+
79+
[ status, headers, body ]
7180
end
7281

7382
private
7483

7584
def home(request)
76-
config = {
77-
"project_name" => @project_name,
78-
"framework_type" => "rails",
79-
"tidewave_version" => Tidewave::VERSION,
80-
"team" => @team
81-
}
85+
config = config_data
8286

8387
html = <<~HTML
8488
<html>
@@ -95,6 +99,19 @@ def home(request)
9599
[ 200, { "Content-Type" => "text/html" }, [ html ] ]
96100
end
97101

102+
def config_endpoint(request)
103+
[ 200, { "Content-Type" => "application/json" }, [ JSON.generate(config_data) ] ]
104+
end
105+
106+
def config_data
107+
{
108+
"project_name" => @project_name,
109+
"framework_type" => "rails",
110+
"tidewave_version" => Tidewave::VERSION,
111+
"team" => @team
112+
}
113+
end
114+
98115
def forbidden(message)
99116
Rails.logger.warn(message)
100117
[ 403, { "Content-Type" => "text/plain" }, [ message ] ]

lib/tidewave/railtie.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class Railtie < Rails::Railtie
3232
content_security_policy.directives["script-src"].try do |script_src|
3333
script_src << "'unsafe-eval'" unless script_src.include?("'unsafe-eval'")
3434
end
35+
36+
content_security_policy.directives.delete("frame-ancestors")
3537
end
3638
end
3739
end

spec/middleware_spec.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ def app
2929
end
3030
end
3131

32+
describe "header removal" do
33+
let(:downstream_app_with_headers) do
34+
->(env) { [ 200, { "X-Frame-Options" => "DENY" }, [ "App with headers" ] ] }
35+
end
36+
37+
def app
38+
described_class.new(downstream_app_with_headers, config)
39+
end
40+
41+
it "removes X-Frame-Options headers from all responses" do
42+
get "/some-route"
43+
expect(last_response.status).to eq(200)
44+
expect(last_response.headers["X-Frame-Options"]).to be_nil
45+
expect(last_response.body).to eq("App with headers")
46+
end
47+
48+
it "removes headers from tidewave routes as well" do
49+
get "/tidewave/some-sub-route"
50+
expect(last_response.headers["X-Frame-Options"]).to be_nil
51+
end
52+
end
53+
3254
describe "IP validation" do
3355
context "when remote access is allowed" do
3456
before do
@@ -113,6 +135,21 @@ def app
113135
end
114136
end
115137

138+
describe "/tidewave/config" do
139+
it "returns JSON configuration" do
140+
config.team = { id: "dashbit" }
141+
get "/tidewave/config"
142+
expect(last_response.status).to eq(200)
143+
expect(last_response.headers["Content-Type"]).to eq("application/json")
144+
145+
parsed_config = JSON.parse(last_response.body)
146+
expect(parsed_config["framework_type"]).to eq("rails")
147+
expect(parsed_config["tidewave_version"]).to eq(Tidewave::VERSION)
148+
expect(parsed_config["team"]).to eq({ "id" => "dashbit" })
149+
expect(parsed_config).to have_key("project_name")
150+
end
151+
end
152+
116153
describe "/tidewave/shell" do
117154
def parse_binary_response(body)
118155
chunks = []

0 commit comments

Comments
 (0)