mschae / cors_plug Goto Github PK
View Code? Open in Web Editor NEWAn Elixir Plug to add CORS.
Home Page: https://hex.pm/packages/cors_plug
License: Other
An Elixir Plug to add CORS.
Home Page: https://hex.pm/packages/cors_plug
License: Other
Do you think if it's necessary to do check the request's origin against the configuration on OPTIONS request?
I'm trying to write a cors policy using regex in order to allow many ports, but I can't.
✅ It's working
plug CORSPlug, origin: ["http://localhost:8080",
"https://others-addresses.net"
]
plug CORSPlug, origin: [~r/http:\/\/localhost:/d+/,
"https://others-addresses.net"
]
plug CORSPlug, origin: [~r/http:\/\/localhost:8080/,
"https://others-addresses.net"
]
I'm developing my application with webpack devserver so, when I open two webpack devserver, the port is incremented. For example, the first server will use localhost:8080
and the second will use localhost:8081
.
So, I would like to allow both on my cors policy.
Just spent a bit of time figuring this one out... was wondering if this was intended or a bug? Thank you!
endpoint.ex :
import AppName.ConfigHelpers
...
plug CORSPlug, origin: cors_origins(), headers: cors_headers()
Inside ConfigHelpers :
def cors_origins() do
if System.get_env("ENV") == "production" do
[@origin_regex, "https://other.com"]
else
["*"]
end
end
def cors_headers() do
["test_wrong_header"]
end
The origins are good, but I have an impression that all headers are allowed, If I deploy with that list [“test_wrong_header”], the request is accepted.
Hey there, I'm not sure of the status of your plug here, hopefully you're still around. Thanks for your time on this library.
I ran into a small issue with the default config. If you don't consider this a bug, perhaps this paper trail will be helpful for some other future traveler.
When creating a request in browser-land javascript, like this:
fetch(url, {
credentials: true,
mode: "cors"
})
By default CORSPlug gives an invalid response:
access-control-allow-origin: *
access-control-allow-credentials: true
[...]
Firefox fails with an ambiguous "null" and Chrome shows the message:
Access to fetch at '[url]' from origin '[other url]' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
The MDN Article on Access-Control-Allow-Origin explains further:
For requests without credentials, the literal value "*" can be specified as a wildcard; the value tells browsers to allow requesting code from any origin to access the resource. Attempting to use the wildcard with credentials results in an error.
A behavior I have seen elsewhere is that when the origin is specified as '*', credentials is disabled. In that case, it's silently disabled, which bit me in a different way.
If you create a phoenix api and host it on a subdomain, you will receive:
Bad value on output port 'tcp_inet'
errors in production. There is an issue on cowboy related to this as well. From what I have found on stack overflow it looks like it may be because atoms are being sent in the headers if you set a specific origin.
After upgrading from 1.3
to 1.4
I'm getting the following errors in my Phoenix application.
I've included a snippet of the error that shows up when I run my tests:
1) test does not update chosen user and renders errors when user is invalid (Udia.Web.UserControllerTest)
test/udia/web/controllers/user_controller_test.exs:154
** (FunctionClauseError) no function clause matching in anonymous fn/2 in Plug.Conn.merge_resp_headers/2
stacktrace:
(plug) lib/plug/conn.ex:663: anonymous fn({"access-control-allow-origin", nil}, [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "n988irgthhkv295okfquhdg4tq9rh3dj"}, {"vary", "Origin"}]) in Plug.Conn.merge_resp_headers/2
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
(plug) lib/plug/conn.ex:663: Plug.Conn.merge_resp_headers/2
(cors_plug) lib/cors_plug.ex:33: CORSPlug.call/2
(udia) lib/udia/web/endpoint.ex:1: Udia.Web.Endpoint.plug_builder_call/2
(udia) lib/udia/web/endpoint.ex:1: Udia.Web.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
test/udia/web/controllers/user_controller_test.exs:156: (test)
I'm using cors_plug with Phoenix framework. In the README, it says that I'll need to add plug CORSPlug
in the endpoint.
Problem is, I need different origin
headers on multiple environments. So, if I pre-compile the app and deploy then on different servers, I need cors_plug to read, in run-time, the value that I want for origins. Right now, when I run mix compile
, it reads the config file, gets the value for origin, and generates the app with the value that'll be there, at compile-time.
Is there a way to work-around this? Maybe pass a function to :origin
config, so that it'll be called everytime an OPTIONS
call is made?
I'm running into a situation where in my deploy (plug-only, no phoenix) the headers produced by this plug are not capitalized, and some browsers complain. Fairly easy to fix for me personally, just forked and rewrote.
I'm not sure what the "correct thing to do" is since I know that in elixir we tend to have headers be lower case internally.
Hello,
I cannot make it work in following expression:
origin: Regex.compile("^http://localhost:8080$|^https://example.com$")
What I'm doing here wrong?
Thank you
I took a look into the code because I was somehow confused having config :cors_plug, origin: [...]
in our config AND plug CORSPlug, origin: &Web.CORS.allowed_origins/0
in the endpoint. I wanted to understand why we have both and which of them has the highest prio.
While doing so, I stumbled upon this:
Line 47 in 1ca97ed
It gets called here:
Line 42 in 1ca97ed
AFAIK Application.get_all_env/1
will always return a list.
Requests that have the OPTIONS
method but without this header are not CORS request,
They should be passed to the downstream application and no headers added.
https://www.html5rocks.com/static/images/cors_server_flowchart.png
Hello,
It seems like the plug may be configured only in compilation time, so it is not possible to change configuration in runtime using, for example, an environment variable.
Am I wrong? Or I'm missing something?
I have an error during the release process
$ MIX_ENV=prod mix release
output :
Compiling 8 files (.ex)
==> Assembling release..
==> Building release kafka_test:0.1.1 using environment prod
==> One or more direct or transitive dependencies are missing from
:applications or :included_applications, they will not be included
in the release:
:cors_plug
:mix_docker
This can cause your application to fail at runtime. If you are sure
that this is not an issue, you may ignore this warning.
==> Including ERTS 8.3 from /usr/local/Cellar/erlang/19.3/lib/erlang/erts-8.3
==> Packaging release..
==> Release successfully built!
You can run it in one of the following ways:
Interactive: _build/prod/rel/kafka_test/bin/kafka_test console
Foreground: _build/prod/rel/kafka_test/bin/kafka_test foreground
Daemon: _build/prod/rel/kafka_test/bin/kafka_test start
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
any idea?
config :cors_plug,
origin: ["some origin here"],
max_age: 86400,
methods: ["GET", "POST"]
does not set the configuration of the plug, I still get back access-control-allow-origin: *
no matter which host I use to make the request.
any plans to support preflight options like Access-Control-Allow-Private-Network
I have added cors_plug to my deps.
I inserted the following into my dev.exs config:
config :cors_plug, origin: ["http://localhost:3000"]
I also inserted CORSplug into my endpoint.ex like this before the router:
plug CORSPlug
Still my header is empty. What am I doing wrong?
We are getting a "one-off" error every hundred or so requests to our api where the Access-Control-Allow-Origin
header and other headers are not present on the resp_headers
. You can see in the screenshots below that the "Working" request has all the appropriate Response Headers but the "Failed" request didn't add all of the Response Headers and thus, we get this error:
As far as how we're implementing CorsPlug
, we simply have plug CORSPlug
included at the top of our endpoint.ex
file with no origin
or any other options
specified so it just falls back to the defaults.
I've looked through the cors_plug.ex
source code and no clue why these headers wouldn't be added every hundred or so requests. Any thoughts as to what the issue might be?
Similarly to the discussion going on in ElxirForum configuration set with System.get_env/1
that is compiled isn't evaluated on run-time.
This is not a problem specific to this plug or plugs in general, but will affect anywhere we use System.get_env/1
in a compiled context (e.g. try module attributes).
This issue is intended to find solutions for the problem.
Suggested by @mariusbutuc:
Allow {:system, "VALUE"}
similar to how we configure phoenix.
My suggestion:
Use functions. That way we keep the problem separate from this plug:
defmodule MyApp.Endpoint
# ...
plug CorsPlug, max_age: max_age()
###
defp max_age, do: "MAX_AGE" |> System.get_env |> String.to_integer
end
This comes from W3C:
7.4. Avoid returning Access-Control-Allow-Origin: "null"
It may seem safe to return Access-Control-Allow-Origin: "null" , but the serialization of the Origin of any resource that uses a non-hierarchical scheme (such as data: or file: ) and sandboxed documents is defined to be "null". Many User Agents will grant such documents access to a response with an Access-Control-Allow-Origin: "null" header, and any origin can create a hostile document with a "null" Origin. The "null" value for the ACAO header should therefore be avoided.
The simple string comparison of CORS as applied to "null" is controversial. Some believe that "null" should be treated as a keyword token indicating the lack of an Origin, which, when tested, should never compare as equal to another "null" Origin. (As is the case with null values in SQL, for example.) It is unwise to build systems which rely on the "null" equals "null" comparison as this behavior may change in the future.
Perhaps we should consider a better strategy for origin/2
instead of returning null
.
I've struggled to get this configured to work right with my domains. I'm coming back around to try again (previously I just gave up and have injected the header at my load balancer level).
I'm sure it's just something odd about how I have things configured, but I cannot get this to work, and I feel like I'm stabbing blindly in the dark at things without a higher level of logging.
If I configure with the origin ["*"] I see the headers. I've tried every other possible combination I can think of even for local development ("http://localhost:4000" -- without protocol, with/without port, etc) and it never includes the headers, silently doing nothing.
Can I enable logging of some sort? Just to help me figure out what I have wrong and get it configured -- I'd like to know where I'm going sideways. Super frustrated. And I can see from other comments in this issues queue I'm not the only one who's had problems getting this configured. Having it silently do nothing is very problematic.
Using the latest version after upgrading to try to get it to work -- still no go.
What I'd actually like is if it stopped trying to be too smart and match domains, and instead just let me clobber in a cors header. My fallback, because I can't get this to work, is to just clobber it in at the load balancer level anyway.
According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
If you don't need credentials, omit this header entirely (rather than setting its value to false).
I have a regex to match urls. I am trying to match every subdomain url like http://example.localhost:3000/ . But, this is not working here.
plug CORSPlug, [ origin: [~r/http:\/\(.*)localhost:3000/,"http://localhost:3000"] ]
Hello,
I was playing around with Regexes in cors_plug
, and found that as soon as I changes this:
plug CORSPlug, origin: ~r/^http:\/\/localhost:8080$/
To this:
plug CORSPlug, origin: [~r/^http:\/\/localhost:8080$/]
It stops working.
I would think that it would be possible to do something like this without problems:
plug CORSPlug, origin: [~r/^http:\/\/localhost:8080$/, ~r/^https*\.some-other-domain\.com$/]
Or mix and match:
plug CORSPlug, origin: ["http://localhost:8080", ~r/^https*\.some-other-domain\.com$/]
Is there a specific reason why this is not possible at this moment (performance, etc)?
It is possible to pass a list with wildcard domain as valid origins?
config :cors_plug,
origin: ["https://*.example.com", "http://*.example.com"]
When I try passing an anonymous function into the :origin
option of CORSPlug
:
defmodule GraphQLService.Endpoint do
@moduledoc false
use Phoenix.Endpoint, otp_app: :graphql_service
use Absinthe.Phoenix.Endpoint
plug CORSPlug, origin: fn -> "*" end
plug Plug.Parsers,
parsers: [ :json, :multipart, :urlencoded ],
pass: ["*/*"],
json_decoder: Poison
plug Absinthe.Plug,
schema: GraphQLService.Schema
end
I get the following error:
app_1 | == Compilation error in file lib/graphql_service/endpoint.ex ==
app_1 | ** (ArgumentError) cannot escape #Function<0.65573484/0 in :elixir_compiler_1.__MODULE__/1>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, PIDs and remote functions in the format &Mod.fun/arity
app_1 | (elixir) src/elixir_quote.erl:119: :elixir_quote.argument_error/1
app_1 | (elixir) src/elixir_quote.erl:232: :elixir_quote.do_quote/3
app_1 | (elixir) src/elixir_quote.erl:406: :elixir_quote.do_splice/5
app_1 | (elixir) src/elixir_quote.erl:141: :elixir_quote.escape/2
app_1 | (elixir) lib/macro.ex:479: Macro.escape/2
app_1 | lib/plug/builder.ex:249: Plug.Builder.quote_plug_call/3
app_1 | lib/plug/builder.ex:210: Plug.Builder.quote_plug/4
app_1 | (elixir) lib/enum.ex:1829: Enum."-reduce/3-lists^foldl/2-0-"/3
After talking to some people on the Elixir Slack channel I got the impression that this isn't possible with macros. When I showed them the documentation on the README they seemed stumped as well.
I was able to pass a function into the option though.
defmodule GraphQLService.Endpoint do
@moduledoc false
use Phoenix.Endpoint, otp_app: :graphql_service
use Absinthe.Phoenix.Endpoint
plug CORSPlug, origin: &__MODULE__.get_cors_origin/0
plug Plug.Parsers,
parsers: [ :json, :multipart, :urlencoded ],
pass: ["*/*"],
json_decoder: Poison
plug Absinthe.Plug,
schema: GraphQLService.Schema
def get_cors_origin() do
"*"
end
end
The documentation is either missing some details or is possibly wrong.
Hey,
I have a bit strange case for me. E.g. I can not override the settings with Application config. As far as I see it happens because the init function is called during the compile time, and options are just automatically passed to the call function. So to summarize I cant use configs in runtime in order to set different origins.
It looks like its a behaviour expected by Phoenix: https://hexdocs.pm/plug/Plug.html#c:init/1
The result returned by init/1 is passed as second argument to call/2. Note that init/1 may be called during compilation and as such it must not return pids, ports or values that are specific to the runtime.
I'm using Elixir 1.9 and configuring the release using the recommended config/releases.exs
file with the following line:
config :cors_plug, origin: [System.fetch_env!("CORS_ORIGIN")]
Looks like the previous line is not working as expected and the built release is not allowing requests from the origin set on the CORS_ORIGIN
env var.
I'm doing something wrong? The other configs are working fine so I think this is an specific problem with cors_plug
.
If I define the following line on prod.exs
the requests works as expected (but of course I lose the possibility of configure the origin executing the release):
config :cors_plug, origin: ["http://192.168.1.128:8080"]
Or even remove it as whole project doesn't need exactly cowboy and Plug should manage that. If removal is not an option then relax it to ~> 1.0
or >= 1
I was reading through the source a little, but I only see the origin:
key. I imagine the expectation is I dynamically set the origin per request, but I'm not sure where I'd do this. Any tips would be much appreciated :)
Cheers
While working on my awesome elixir list (https://github.com/h4cc/awesome-elixir) i found two CORS Plugs :)
Maybe you two could team up and work together?
Is this plug really only compatible with plug 1.5? Phoenix 1.3.2 (latest stable release) doesn't even support 1.5, so currently cors_plug is unusable with the latest version of Phoenix. Perhaps a version string like ">= 1.3 and < 2.0.0"
would work better? Or ~> 1.3 or ~> 1.4 or ~> 1.5
?
I've come across a minor issue in origin checks - it's probably not exploitable in the wild, but I cannot be sure of that.
Should I report that here, or do you have an email for security-related issues I can use?
After upgrade to 1.4.0, my console spits the error:
Server: localhost:4000 (http)
Request: OPTIONS /graphql
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in anonymous fn/2 in Plug.Conn.merge_resp_headers/2
(plug) lib/plug/conn.ex:663: anonymous fn({"access-control-expose-headers", []}, [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "s83p1st550ks4i88vkahrdlopo2nk6am"}, {"vary", "Origin"}, {"access-control-allow-origin", "http://localhost:3000"}]) in Plug.Conn.merge_resp_headers/2
(elixir) lib/enum.ex:1811: Enum."-reduce/3-lists^foldl/2-0-"/3
(plug) lib/plug/conn.ex:663: Plug.Conn.merge_resp_headers/2
(cors_plug) lib/cors_plug.ex:33: CORSPlug.call/2
I'm very new to elixir, so am not sure if this related to this plug.
Thanks.
As a somewhwat related issue to 29, we're getting an empty access-control-expose-headers:
– with a CR immediately after the colon – on all our requests when using plug CORSPlug
(regardless of setting origin:
or not. This caused an internal http parsing lib written in C to bork out which took a while to debug… 💯
Non-null/emptystring return values for headers is not recommended if we want to make life easier for http parsers :)
Note: we're not using Phoenix, just a plain elixir app with plugs.
I'm trying to implement CORS on an elixir api. Spent a couple of hours at heroku trying to figure this out to no avail. So now I am on localhost debugging the app with the help of ngrok to pretend the "cross domain" request.
# ...
case Mix.env do
:dev -> plug CORSPlug, origin: ["*"]
:prod -> plug CORSPlug, origin: System.get_env("CORS_ALLOWED_ORIGIN")
end
plug HomeAccounting.Router
end
CORS_ALLOWED_ORIGIN="http://localhost:3333" DATABASE_URL="postgres://db/home_accounting_front" PORT=4000 MIX_ENV=prod iex -S mix phoenix.server
# ngrok
ngrok http 4000
# and the single page app server that consumes the elixir API:
API_URL=https://9e68e012.ngrok.io/api npm start
JS console shows:
XMLHttpRequest cannot load https://9e68e012.ngrok.io/api/expenditures. The 'Access-Control-Allow-Origin' header contains the invalid value 'null'. Origin 'http://localhost:3333' is therefore not allowed access.
This works if I skip ngrok, or run in development mode.
My program was simply using Plug and wasn't using Phoenix.
use Plug.Router
use Plug.ErrorHandler
use Sentry.Plug
plug(CORSPlug,
origin: &Router.get_origins/0
)
plug(Plug.Parsers,
parsers: [:urlencoded, :multipart, :json, CustomParser],
pass: ["*/*"],
json_decoder: Jason
)
def handle_errors(conn, _error_context) do
send_resp(conn, conn.status, "")
end
When the server crashes, it goes to handle_error
and send a response back without CORS headers. I had to manually call CORSPlug.call
and init
to add the headers back.
Is this a bug? Or this is intended behavior? Do we have a better way to handle this?
This is pretty close to a feature request, but it looks like the right place for it and I'm willing to do much of the work.
Is there any interest in making this a general CORS library, i.e. not just a plug.
I think work needed would be
Code.ensure_loaded?
cors
on hex. It is still available.Why.
a) Useful in tests of APIs to check that the headers include the expected CORS headers
b) I, and quite a few others, use Raxx/Ace
Hello,
thanks for the plug, really useful. But i've ran into an issue.
I need to have custom header, X-Auth-Token. I've changed it in my endpoint.ex like this
plug CORSPlug, origin: "http://localhost:4200", headers: ["X-Auth-Token"]
And I'm faced with this error in chrome:
Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response
I fiddled around trying to find what can be causing this (expecting it to be my issue) but I found nothing. When I add the header, it breaks down. I've dove into cors_plug code and sure enough, there are defaults and those defaults are merged.
def init(options) do
IO.inspect Dict.merge defaults, options
end
This yields
[credentials: true, max_age: 1728000, expose: [], methods: ["GET", "POST", "PUT", "PATCH", origin: "http://localhost:4200", headers: ["X-AUTH-TOKEN"]]
The headers are overriden.
Am I just using it wrong or is this not an unexpected behaviour? Should there be a deep merge?
My temporary fix is:
plug CORSPlug, [
origin: "http://localhost:4200",
headers: ["X-Auth-Token" | CORSPlug.defaults()[:headers]]
]
But I do not think this is the correct solution.
In case this is in fact a bug I'm willing to send in a pull request.
Preflight requests keep returning 404
. My routes are below; am I holding plug CORSPlug
wrong? Do I need to explicitly add a route for OPTIONS
? Do I need the plug CORSPlug
before :accepts
? In the example in the README, I noticed a reference to super
, but when I added that, I got a compile error.
By the way this is my first Elixir app.
defmodule Uploader.Router do
use Uploader.Web, :router
pipeline :api do
plug :accepts, ["json"]
plug CORSPlug
end
scope "/upload", Uploader do
pipe_through :api
post "/", UploadsController, :create
end
end
Please add a license file so we know what license this is released with.
Would it be possible to just pass the conn as an argument to the user-defined origin function in the following code?
Line 82 in 62e32b1
I don't believe this breaks anything, and it would make achieving my use-case much easier (building cors domains based on assigns data).
I'd be happy to submit a PR if it would help.
Thanks,
Matt
This setup currently produces an error on normal requests:
plug CORSPlug, origin: [~r/https?.*example\d?\.com$/]
a server configured with the above will throw FunctionClauseError
on any non-cors request, e.g get(conn, '/')
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.