Skip to content

Commit c2a11a6

Browse files
authored
Merge pull request #114 from hernoufM/cookies
Cookies
2 parents 5253f7d + a8d2cc3 commit c2a11a6

24 files changed

Lines changed: 707 additions & 93 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ _local
44
*~
55
*.merlin
66
*.install
7+
*.exe
8+
.ocamlformat
9+
test/session/*.js

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ clean:
1616
doc:
1717
@dune build @doc
1818
@rsync -ru _build/default/_doc/_html/* docs/
19+
20+
build-tests:
21+
@opam install ocurl websocket-lwt-unix js_of_ocaml
22+
@dune build test
23+

src/common/ezAPI.ml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,38 +104,40 @@ let forge0 url s params = forge url s Req.dummy params
104104
let forge1 url s arg1 params = forge url s (Req.dummy, arg1) params
105105
let forge2 url s arg1 arg2 params = forge url s ((Req.dummy, arg1), arg2) params
106106

107+
(* access_control allows to set acces control that will be included in server response headers. *)
107108
let raw_service :
108109
type i. ?section:Doc.section -> ?name:string -> ?descr:string -> ?meth:Meth.t ->
109110
input:i io -> output:'o io -> ?errors:'e Err.case list -> ?params:Param.t list ->
110-
?security:'s list -> ?register:bool -> ?input_example:i ->
111-
?output_example:'o -> (Req.t, 'a) Path.t -> ('a, i, 'o, 'e, 's) service =
111+
?security:'s list -> ?access_control:(string * string) list -> ?register:bool ->
112+
?input_example:i -> ?output_example:'o -> (Req.t, 'a) Path.t ->
113+
('a, i, 'o, 'e, 's) service =
112114
fun ?section ?name ?descr ?meth ~input ~output ?(errors=[]) ?(params=[])
113-
?(security=[]) ?register ?input_example ?output_example path ->
115+
?(security=[]) ?access_control ?register ?input_example ?output_example path ->
114116
let meth = match meth, input with
115117
| None, Empty -> `GET
116118
| None, _ -> `POST
117119
| Some m, _ -> m in
118120
let s = Service.make ~meth ~input ~output
119-
~errors ~params ~security path in
121+
~errors ~params ~security ?access_control path in
120122
let doc = Doc.make ?name ?descr ?register ?section ?input_example ?output_example s in
121123
{ s; doc }
122124

123125
let post_service ?section ?name ?descr ?(meth=`POST)
124126
~input ~output ?errors ?params
125-
?security ?register ?input_example ?output_example
127+
?security ?register ?access_control ?input_example ?output_example
126128
path =
127129
raw_service ?section ?name ?descr ~input:(Json input) ~output:(Json output)
128-
?errors ~meth ?params ?security ?register ?input_example ?output_example path
130+
?errors ~meth ?params ?security ?access_control ?register ?input_example ?output_example path
129131

130132
let service ?section ?name ?descr ?(meth=`GET) ~output ?errors ?params
131-
?security ?register ?output_example path =
133+
?security ?access_control ?register ?output_example path =
132134
raw_service ?section ?name ?descr ~input:Empty ~output:(Json output)
133-
?errors ~meth ?params ?security ?register ?output_example path
135+
?errors ~meth ?params ?security ?access_control ?register ?output_example path
134136

135137
let ws_service ?section ?name ?descr ~input ~output ?errors ?params
136-
?security ?register ?output_example path =
138+
?security ?access_control ?register ?output_example path =
137139
raw_service ?section ?name ?descr ~input ~output
138-
?errors ~meth:`GET ?params ?security ?register ?output_example path
140+
?errors ~meth:`GET ?params ?security ?access_control ?register ?output_example path
139141

140142
let register service =
141143
service.doc.Doc.doc_registered <- true;

src/common/security.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ type basic_desc = { basic_name : string }
1616
type bearer = [ `Bearer of bearer_desc ]
1717
type basic = [ `Basic of basic_desc ]
1818
type header = [ `Header of string apikey ]
19-
type cookie = [ `Cookie of string apikey ]
19+
(* cookie name * max age atribute (defaults to 1 day) *)
20+
type cookie = [ `Cookie of string apikey * int64 option ]
2021
type query = [ `Query of Param.t apikey ]
2122
type scheme = [
2223
| none
@@ -32,7 +33,7 @@ let ref_name = function
3233
| `Nosecurity u -> unreachable u
3334
| `Basic { basic_name = ref_name }
3435
| `Bearer { bearer_name = ref_name; format=_ }
35-
| `Cookie { ref_name; name=_ }
36+
| `Cookie ({ ref_name; name=_ }, _ )
3637
| `Header { ref_name; name=_ }
3738
| `Query { ref_name; name=_ } -> ref_name
3839

src/common/service.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@ type ('args, 'input, 'output, 'error, 'security) t = {
5151
meth : Meth.t;
5252
params : Param.t list;
5353
security: ([< Security.scheme ] as 'security) list;
54+
access_control : (string * string) list
5455
}
5556

56-
let make ?(meth : Meth.t =`GET) ?(params=[]) ?(security=[]) ?(errors=[]) ~input ~output path =
57-
{ path ; input ; output; errors; meth; params; security }
57+
let make =
58+
fun ?(meth : Meth.t =`GET) ?(params=[]) ?(security=[]) ?(errors=[])
59+
?(access_control=[]) ~input ~output path ->
60+
{ path ; input ; output; errors; meth; params; security; access_control }
5861

5962
let input s = s.input
6063
let output s = s.output
@@ -69,5 +72,6 @@ let meth s = s.meth
6972
let path s = s.path
7073
let security s = s.security
7174
let params s = s.params
75+
let access_control s = s.access_control
7276

7377
let error s ~code = Err.get ~code s.errors

src/server/cohttp/ezAPIServerCohttp.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ let dispatch ?catch s io req body =
8080
>>= function
8181
| `ws (Ok ra) -> Lwt.return ra
8282
| `ws (Error _) ->
83-
let headers = Header.of_list access_control_headers in
83+
let headers = Header.of_list default_access_control_headers in
8484
let status = Code.status_of_code 501 in
8585
Server.respond_string ~headers ~status ~body:"" () >|= fun (r, b) ->
8686
`Response (r, b)
8787
| `http {Answer.code; body; headers} ->
88-
let headers = headers @ access_control_headers in
88+
let headers = merge_headers_with_default headers in
8989
let status = Code.status_of_code code in
9090
debug ~v:(if code >= 200 && code < 300 then 1 else 0) "Reply computed to %S: %d" path_str code;
9191
debug ~v:3 "Reply content:\n %s" body;

src/server/directory.ml

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ let rec resolve :
9292
(resolved_directory, lookup_error) result Lwt.t =
9393
fun prefix dir args path ->
9494
match path, dir with
95-
| [], dir -> Lwt.return_ok (Dir (dir, args))
95+
| [], dir ->
96+
Lwt.return_ok (Dir (dir, args))
9697
| _name :: _path, { subdirs = None, None; _ } -> Lwt.return_error `Not_found
9798
| name :: path, { subdirs = Some static, None; _ } ->
9899
begin match StringMap.find_opt name static with
@@ -114,28 +115,33 @@ let rec resolve :
114115
| Error msg -> Lwt.return_error @@
115116
`Cannot_parse (arg.Arg.description, msg, name :: prefix)
116117

117-
let io_to_answer : type a. code:int -> a io -> a -> string Answer.t = fun ~code io body ->
118+
(* Note : headers are merged with predefined headers *)
119+
let io_to_answer : type a. code:int -> headers:(string * string) list -> a io -> a -> string Answer.t =
120+
fun ~code ~headers io body ->
118121
match io with
119-
| Empty -> {Answer.code; body=""; headers=[]}
122+
| Empty -> {Answer.code; body=""; headers}
120123
| Raw l ->
121124
let content_type = match l with
122125
| [] -> "application/octet-stream"
123126
| h :: _ -> Mime.to_string h in
124-
{Answer.code; body; headers=["content-type", content_type]}
127+
{Answer.code; body; headers=("content-type", content_type)::headers}
125128
| Json enc ->
126129
{Answer.code; body = EzEncoding.construct enc body;
127-
headers=["content-type", "application/json"]}
130+
headers=("content-type", "application/json")::headers}
128131

129132
let ser_handler :
130-
type i o e. ?content_type:string -> ('a -> i -> (o, e) result Answer.t Lwt.t) -> 'a ->
133+
type i o e. ?content_type:string -> access_control:(string * string) list
134+
-> ('a -> i -> (o, e) result Answer.t Lwt.t) -> 'a ->
131135
i io -> o io -> e Json_encoding.encoding ->
132136
string -> (string Answer.t, handler_error) result Lwt.t =
133-
fun ?content_type handler args input output errors ->
134-
let handle_result {Answer.code; body; _} = match body with
135-
| Ok o -> io_to_answer ~code output o
137+
fun ?content_type ~access_control handler args input output errors ->
138+
let handle_result {Answer.code; body; headers} =
139+
match body with
140+
| Ok o -> io_to_answer ~code ~headers:(headers @ access_control) output o
136141
| Error e ->
137142
{Answer.code; body = EzEncoding.construct errors e;
138-
headers=["content-type", "application/json"]} in
143+
headers=("content-type", "application/json")::access_control }
144+
in
139145
match input with
140146
| Empty -> (fun _ ->
141147
Lwt.catch
@@ -185,11 +191,15 @@ let lookup ?meth ?content_type dir r path : (lookup_ok, lookup_error) result Lwt
185191
begin match MethMap.bindings dir.services with
186192
| [] -> Lwt.return_error `Not_found
187193
| l ->
194+
(* Todo : combine access control headers correctly. *)
195+
let access_control = List.fold_left (fun acc (_,rs) -> match rs with
196+
| Http {service; _} when acc = [] -> Service.access_control service
197+
| _ -> acc) [] l in
188198
let meths = Meth.headers @@ List.map fst l in
189199
let sec_set = List.fold_left (fun acc (_, rs) -> match rs with
190200
| Http {service; _} -> Security.StringSet.union acc (Security.headers (Service.security service))
191201
| Websocket _ -> acc) Security.StringSet.empty l in
192-
Lwt.return_ok @@ `options (meths @ (Security.header sec_set))
202+
Lwt.return_ok @@ `options (access_control @ meths @ (Security.header sec_set))
193203
end
194204
| Some `HEAD -> Lwt.return_ok `head
195205
| Some (#Meth.t as m) ->
@@ -198,7 +208,8 @@ let lookup ?meth ?content_type dir r path : (lookup_ok, lookup_error) result Lwt
198208
let input = Service.input service in
199209
let output = Service.output service in
200210
let errors = Service.errors_encoding service in
201-
let h = ser_handler ?content_type handler args input output errors in
211+
let access_control = Service.access_control service in
212+
let h = ser_handler ?content_type ~access_control handler args input output errors in
202213
Lwt.return_ok @@ `http h
203214
| `GET, Some (Websocket {service; react; bg; onclose; step}) ->
204215
let input = Service.input service in

src/server/ezAPIServerUtils.ml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,30 @@ let handle ?meth ?content_type ?ws s r path body =
149149
| Some ws -> ws ?onclose ?step ~react ~bg r.Req.req_id
150150
end >|= fun ra -> `ws ra
151151

152-
let access_control_headers = [
152+
(* Default access control headers *)
153+
let default_access_control_headers = [
153154
"access-control-allow-origin", "*";
154-
"access-control-allow-headers", "accept, content-type" ]
155+
"access-control-allow-headers", "accept, content-type"
156+
]
157+
158+
(* merge headers correctly with default one *)
159+
let merge_headers_with_default headers : (string * string) list =
160+
(* combining existing headers *)
161+
let l = List.fold_left
162+
(fun acc ((hn,hv) as h) ->
163+
match List.assoc_opt hn default_access_control_headers with
164+
| None -> h::acc
165+
| Some _ when hn = "access-control-allow-origin" ->
166+
h::acc
167+
| Some v when hn = "access-control-allow-headers" ->
168+
(hn, hv ^ "," ^ v)::acc
169+
| _ -> acc)
170+
[]
171+
headers
172+
in
173+
(* Adding default if not present *)
174+
List.fold_left (fun acc ((hn,_) as h) ->
175+
match List.assoc_opt hn l with
176+
| None -> h::acc
177+
| _ -> acc
178+
) l default_access_control_headers

src/server/ezOpenAPI.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ module Encoding = struct
480480
Some (bearer_name, Makers.mk_security_scheme ?format ~scheme:"bearer" "http")
481481
| `Header { EzAPI.Security.ref_name; name } ->
482482
Some (ref_name, Makers.mk_security_scheme ~loc:"header" ~name "apiKey")
483-
| `Cookie { EzAPI.Security.ref_name; name } ->
483+
| `Cookie ({ EzAPI.Security.ref_name; name }, _ ) ->
484484
Some (ref_name, Makers.mk_security_scheme ~loc:"cookie" ~name "apiKey")
485485
| `Query { EzAPI.Security.ref_name; name } ->
486486
Some (ref_name, Makers.mk_security_scheme ~loc:"query" ~name:name.EzAPI.Param.param_id "apiKey")

src/server/httpaf/ezAPIServerHttpAf.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ let connection_handler :
266266
| Some c -> c path_str exn >|= fun a -> `http a)
267267
>>= function
268268
| `ws (Error _) ->
269-
let headers = Headers.of_list access_control_headers in
269+
let headers = Headers.of_list default_access_control_headers in
270270
let status = Status.unsafe_of_code 501 in
271271
let response = Response.create ~headers status in
272272
Reqd.respond_with_string reqd response "";
@@ -276,7 +276,7 @@ let connection_handler :
276276
| `http {Answer.code; body; headers} ->
277277
let status = Status.unsafe_of_code code in
278278
debug ~v:(if code = 200 then 1 else 0) "Reply computed to %S: %d" path_str code;
279-
let headers = headers @ access_control_headers in
279+
let headers = merge_headers_with_default headers in
280280
let headers = Headers.of_list headers in
281281
let len = String.length body in
282282
let headers = Headers.add headers "content-length" (string_of_int len) in

0 commit comments

Comments
 (0)