forked from mschae/cors_plug
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcors_plug.ex
97 lines (82 loc) · 2.85 KB
/
cors_plug.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
defmodule CORSPlug do
import Plug.Conn
def defaults do
[
origin: "*",
credentials: true,
max_age: 1728000,
headers: ["Authorization", "Content-Type", "Accept", "Origin",
"User-Agent", "DNT","Cache-Control", "X-Mx-ReqToken",
"Keep-Alive", "X-Requested-With", "If-Modified-Since",
"X-CSRF-Token"],
expose: [],
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
]
end
def init(options) do
Keyword.merge(defaults(), options)
end
def call(conn, options) do
conn = put_in(conn.resp_headers, conn.resp_headers ++ headers(conn, options))
case conn.method do
"OPTIONS" -> conn |> send_resp(204, "") |> halt
_method -> conn
end
end
# headers specific to OPTIONS request
defp headers(conn = %Plug.Conn{method: "OPTIONS"}, options) do
headers(%{conn | method: nil}, options) ++ [
{"access-control-max-age", "#{options[:max_age]}"},
{"access-control-allow-headers", allowed_headers(options[:headers], conn)},
{"access-control-allow-methods", Enum.join(options[:methods], ",")}
]
end
# universal headers
defp headers(conn, options) do
allowed_origin = origin(options[:origin], conn)
[
{"access-control-allow-origin", allowed_origin},
{"access-control-expose-headers", Enum.join(options[:expose], ",")},
{"access-control-allow-credentials", "#{options[:credentials]}"},
{"vary", vary(allowed_origin)}
]
end
# Allow all requested headers
defp allowed_headers(["*"], conn) do
get_req_header(conn, "access-control-request-headers")
|> List.first
end
defp allowed_headers(key, _conn) do
Enum.join(key, ",")
end
# return origin if it matches regex, otherwise "null" string
defp origin(%Regex{} = regex, conn) do
req_origin = conn |> request_origin |> to_string
if req_origin =~ regex, do: req_origin, else: "null"
end
# normalize non-list to list
defp origin(key, conn) when not is_list(key) do
origin(List.wrap(key), conn)
end
# whitelist internal requests
defp origin([:self], conn) do
request_origin(conn) || "*"
end
# return "*" if origin list is ["*"]
defp origin(["*"], _conn) do
"*"
end
# return request origin if in origin list, otherwise "null" string
# see: https://www.w3.org/TR/cors/#access-control-allow-origin-response-header
defp origin(origins, conn) when is_list(origins) do
req_origin = request_origin(conn)
if req_origin in origins, do: req_origin, else: "null"
end
defp request_origin(%Plug.Conn{req_headers: headers}) do
Enum.find_value(headers, fn({k, v}) -> k =~ ~r/^origin$/i && v end)
end
# Set the Vary response header
# see: https://www.w3.org/TR/cors/#resource-implementation
defp vary("*"), do: ""
defp vary(_allowed_origin), do: "Origin"
end