-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat (sync-service): support OPTIONS requests (#1649)
This PR fixes #1646 by adding support for HTTP OPTIONS requests.
- Loading branch information
Showing
4 changed files
with
119 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@core/sync-service": patch | ||
--- | ||
|
||
Support OPTIONS request required for preflight requests. |
53 changes: 53 additions & 0 deletions
53
packages/sync-service/lib/electric/plug/options_shape_plug.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
defmodule Electric.Plug.OptionsShapePlug do | ||
require Logger | ||
use Plug.Builder | ||
|
||
plug :add_allowed_methods_header | ||
plug :add_cache_max_age_header | ||
plug :filter_cors_headers | ||
plug :add_allow_origin_header | ||
plug :add_keep_alive | ||
plug :send_options_response | ||
|
||
@allowed_headers ["if-none-match"] | ||
|
||
defp add_allowed_methods_header(conn, _), | ||
do: Plug.Conn.put_resp_header(conn, "access-control-allow-methods", "GET, OPTIONS, DELETE") | ||
|
||
defp add_cache_max_age_header(conn, _) do | ||
conn | ||
|> Plug.Conn.put_resp_header("access-control-max-age", "86400") | ||
|> Plug.Conn.delete_resp_header("cache-control") | ||
end | ||
|
||
# Filters out the unsupported headers from the provided "access-control-request-headers" | ||
defp filter_cors_headers(conn, _) do | ||
case Plug.Conn.get_req_header(conn, "access-control-request-headers") do | ||
[] -> | ||
conn | ||
|
||
headers -> | ||
supported_headers = | ||
headers | ||
|> Enum.filter(&Enum.member?(@allowed_headers, String.downcase(&1))) | ||
|> Enum.join(",") | ||
|
||
case supported_headers do | ||
"" -> conn | ||
_ -> Plug.Conn.put_resp_header(conn, "access-control-allow-headers", supported_headers) | ||
end | ||
end | ||
end | ||
|
||
# Electric currently allows any origin to access the API | ||
defp add_allow_origin_header(conn, _) do | ||
case Plug.Conn.get_req_header(conn, "origin") do | ||
[origin] -> Plug.Conn.put_resp_header(conn, "access-control-allow-origin", origin) | ||
_ -> conn | ||
end | ||
end | ||
|
||
defp add_keep_alive(conn, _), do: Plug.Conn.put_resp_header(conn, "connection", "keep-alive") | ||
|
||
defp send_options_response(conn, _), do: send_resp(conn, 204, "") | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
packages/sync-service/test/electric/plug/options_shape_plug_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
defmodule Electric.Plug.OptionsShapePlugTest do | ||
use ExUnit.Case, async: true | ||
|
||
alias Electric.Plug.OptionsShapePlug | ||
|
||
@registry Registry.OptionsShapePlugTest | ||
@expected_allowed_methods MapSet.new(["GET", "OPTIONS", "DELETE"]) | ||
|
||
setup do | ||
start_link_supervised!({Registry, keys: :duplicate, name: @registry}) | ||
:ok | ||
end | ||
|
||
describe "OptionsShapePlug" do | ||
test "returns allowed methods" do | ||
conn = | ||
Plug.Test.conn("OPTIONS", "/?root_table=foo") | ||
|> OptionsShapePlug.call([]) | ||
|
||
assert conn.status == 204 | ||
|
||
allowed_methods = | ||
conn | ||
|> Plug.Conn.get_resp_header("access-control-allow-methods") | ||
|> List.first("") | ||
|> String.split(",") | ||
|> Enum.map(&String.trim/1) | ||
|> MapSet.new() | ||
|
||
assert allowed_methods == @expected_allowed_methods | ||
assert Plug.Conn.get_resp_header(conn, "access-control-max-age") == ["86400"] | ||
assert Plug.Conn.get_resp_header(conn, "connection") == ["keep-alive"] | ||
end | ||
|
||
test "handles access-control-request-headers" do | ||
header = "If-None-Match" | ||
|
||
conn = | ||
Plug.Test.conn("OPTIONS", "/?root_table=foo") | ||
|> Plug.Conn.put_req_header("access-control-request-headers", header) | ||
|> OptionsShapePlug.call([]) | ||
|
||
assert conn.status == 204 | ||
assert Plug.Conn.get_resp_header(conn, "access-control-allow-headers") == [header] | ||
end | ||
|
||
test "handles origin header" do | ||
origin = "https://example.com" | ||
|
||
conn = | ||
Plug.Test.conn("OPTIONS", "/?root_table=foo") | ||
# also checks that it is case insensitive | ||
|> Plug.Conn.put_req_header("origin", origin) | ||
|> OptionsShapePlug.call([]) | ||
|
||
assert conn.status == 204 | ||
assert Plug.Conn.get_resp_header(conn, "access-control-allow-origin") == [origin] | ||
end | ||
end | ||
end |