Skip to content

Commit 6f86823

Browse files
committed
Cookie support
1 parent e4a39e4 commit 6f86823

18 files changed

Lines changed: 8314 additions & 81 deletions

.ocamlformat

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
profile = ocamlformat
2+
break-cases = fit
3+
margin = 77
4+
parse-docstrings = true
5+
wrap-comments = true
6+
line-endings = lf

src/common/ezAPI.ml

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

97+
(* access_control allows to set acces control that will be included in server response headers. *)
9798
let raw_service :
9899
type i. ?section:Doc.section -> ?name:string -> ?descr:string -> ?meth:Meth.t ->
99100
input:i io -> output:'o io -> ?errors:'e Err.case list -> ?params:Param.t list ->
100-
?security:'s list -> ?register:bool -> ?input_example:i ->
101-
?output_example:'o -> (Req.t, 'a) Path.t -> ('a, i, 'o, 'e, 's) service =
101+
?security:'s list -> ?access_control:(string * string) list -> ?register:bool ->
102+
?input_example:i -> ?output_example:'o -> (Req.t, 'a) Path.t ->
103+
('a, i, 'o, 'e, 's) service =
102104
fun ?section ?name ?descr ?meth ~input ~output ?(errors=[]) ?(params=[])
103-
?(security=[]) ?register ?input_example ?output_example path ->
105+
?(security=[]) ?access_control ?register ?input_example ?output_example path ->
104106
let meth = match meth, input with
105107
| None, Empty -> `GET
106108
| None, _ -> `POST
107109
| Some m, _ -> m in
108110
let s = Service.make ~meth ~input ~output
109-
~errors ~params ~security path in
111+
~errors ~params ~security ?access_control path in
110112
let doc = Doc.make ?name ?descr ?register ?section ?input_example ?output_example s in
111113
{ s; doc }
112114

113115
let post_service ?section ?name ?descr ?(meth=`POST)
114116
~input ~output ?errors ?params
115-
?security ?register ?input_example ?output_example
117+
?security ?register ?access_control ?input_example ?output_example
116118
path =
117119
raw_service ?section ?name ?descr ~input:(Json input) ~output:(Json output)
118-
?errors ~meth ?params ?security ?register ?input_example ?output_example path
120+
?errors ~meth ?params ?security ?access_control ?register ?input_example ?output_example path
119121

120122
let service ?section ?name ?descr ?(meth=`GET) ~output ?errors ?params
121-
?security ?register ?output_example path =
123+
?security ?access_control ?register ?output_example path =
122124
raw_service ?section ?name ?descr ~input:Empty ~output:(Json output)
123-
?errors ~meth ?params ?security ?register ?output_example path
125+
?errors ~meth ?params ?security ?access_control ?register ?output_example path
124126

125127
let ws_service ?section ?name ?descr ~input ~output ?errors ?params
126-
?security ?register ?output_example path =
128+
?security ?access_control ?register ?output_example path =
127129
raw_service ?section ?name ?descr ~input ~output
128-
?errors ~meth:`GET ?params ?security ?register ?output_example path
130+
?errors ~meth:`GET ?params ?security ?access_control ?register ?output_example path
129131

130132
let register service =
131133
service.doc.Doc.doc_registered <- true;

src/common/security.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ type basic_desc = { basic_name : string }
66
type bearer = [ `Bearer of bearer_desc ]
77
type basic = [ `Basic of basic_desc ]
88
type header = [ `Header of string apikey ]
9-
type cookie = [ `Cookie of string apikey ]
9+
(* cookie name * max age atribute (defaults to 1 day) *)
10+
type cookie = [ `Cookie of string apikey * int64 option ]
1011
type query = [ `Query of Param.t apikey ]
1112
type scheme = [
1213
| none
@@ -22,7 +23,7 @@ let ref_name = function
2223
| `Nosecurity u -> unreachable u
2324
| `Basic { basic_name = ref_name }
2425
| `Bearer { bearer_name = ref_name; format=_ }
25-
| `Cookie { ref_name; name=_ }
26+
| `Cookie ({ ref_name; name=_ }, _ )
2627
| `Header { ref_name; name=_ }
2728
| `Query { ref_name; name=_ } -> ref_name
2829

src/common/service.ml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ type ('args, 'input, 'output, 'error, 'security) t = {
4141
meth : Meth.t;
4242
params : Param.t list;
4343
security: ([< Security.scheme ] as 'security) list;
44+
access_control : (string * string) list
4445
}
4546

46-
let make ?(meth : Meth.t =`GET) ?(params=[]) ?(security=[]) ?(errors=[]) ~input ~output path =
47-
{ path ; input ; output; errors; meth; params; security }
47+
let make =
48+
fun ?(meth : Meth.t =`GET) ?(params=[]) ?(security=[]) ?(errors=[])
49+
?(access_control=[]) ~input ~output path ->
50+
{ path ; input ; output; errors; meth; params; security; access_control }
4851

4952
let input s = s.input
5053
let output s = s.output
@@ -59,5 +62,6 @@ let meth s = s.meth
5962
let path s = s.path
6063
let security s = s.security
6164
let params s = s.params
65+
let access_control s = s.access_control
6266

6367
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
@@ -70,12 +70,12 @@ let dispatch ?catch s io req body =
7070
>>= function
7171
| `ws (Ok ra) -> Lwt.return ra
7272
| `ws (Error _) ->
73-
let headers = Header.of_list access_control_headers in
73+
let headers = Header.of_list default_access_control_headers in
7474
let status = Code.status_of_code 501 in
7575
Server.respond_string ~headers ~status ~body:"" () >|= fun (r, b) ->
7676
`Response (r, b)
7777
| `http {Answer.code; body; headers} ->
78-
let headers = headers @ access_control_headers in
78+
let headers = merge_headers_with_default headers in
7979
let status = Code.status_of_code code in
8080
debug ~v:(if code >= 200 && code < 300 then 1 else 0) "Reply computed to %S: %d" path_str code;
8181
debug ~v:3 "Reply content:\n %s" body;

src/server/directory.ml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ let rec resolve :
105105
| Error msg -> Lwt.return_error @@
106106
`Cannot_parse (arg.Arg.description, msg, name :: prefix)
107107

108+
(* Note : headers are merged with predefined headers *)
108109
let io_to_answer : type a. code:int -> headers:(string * string) list -> a io -> a -> string Answer.t =
109110
fun ~code ~headers io body ->
110111
match io with
@@ -119,15 +120,18 @@ fun ~code ~headers io body ->
119120
headers=("content-type", "application/json")::headers}
120121

121122
let ser_handler :
122-
type i o e. ?content_type:string -> ('a -> i -> (o, e) result Answer.t Lwt.t) -> 'a ->
123+
type i o e. ?content_type:string -> access_control:(string * string) list
124+
-> ('a -> i -> (o, e) result Answer.t Lwt.t) -> 'a ->
123125
i io -> o io -> e Json_encoding.encoding ->
124126
string -> (string Answer.t, handler_error) result Lwt.t =
125-
fun ?content_type handler args input output errors ->
126-
let handle_result {Answer.code; body; headers} = match body with
127-
| Ok o -> io_to_answer ~code ~headers output o
127+
fun ?content_type ~access_control handler args input output errors ->
128+
let handle_result {Answer.code; body; headers} =
129+
match body with
130+
| Ok o -> io_to_answer ~code ~headers:(headers @ access_control) output o
128131
| Error e ->
129132
{Answer.code; body = EzEncoding.construct errors e;
130-
headers=["content-type", "application/json"]} in
133+
headers=("content-type", "application/json")::access_control }
134+
in
131135
match input with
132136
| Empty -> (fun _ ->
133137
Lwt.catch
@@ -177,11 +181,15 @@ let lookup ?meth ?content_type dir r path : (lookup_ok, lookup_error) result Lwt
177181
begin match MethMap.bindings dir.services with
178182
| [] -> Lwt.return_error `Not_found
179183
| l ->
184+
(* Todo : combine access control headers correctly. *)
185+
let access_control = List.fold_left (fun acc (_,rs) -> match rs with
186+
| Http {service; _} when acc = [] -> Service.access_control service
187+
| _ -> acc) [] l in
180188
let meths = Meth.headers @@ List.map fst l in
181189
let sec_set = List.fold_left (fun acc (_, rs) -> match rs with
182190
| Http {service; _} -> Security.StringSet.union acc (Security.headers (Service.security service))
183191
| Websocket _ -> acc) Security.StringSet.empty l in
184-
Lwt.return_ok @@ `options (meths @ (Security.header sec_set))
192+
Lwt.return_ok @@ `options (access_control @ meths @ (Security.header sec_set))
185193
end
186194
| Some `HEAD -> Lwt.return_ok `head
187195
| Some (#Meth.t as m) ->
@@ -190,7 +198,8 @@ let lookup ?meth ?content_type dir r path : (lookup_ok, lookup_error) result Lwt
190198
let input = Service.input service in
191199
let output = Service.output service in
192200
let errors = Service.errors_encoding service in
193-
let h = ser_handler ?content_type handler args input output errors in
201+
let access_control = Service.access_control service in
202+
let h = ser_handler ?content_type ~access_control handler args input output errors in
194203
Lwt.return_ok @@ `http h
195204
| `GET, Some (Websocket {service; react; bg; onclose; step}) ->
196205
let input = Service.input service in

src/server/ezAPIServerUtils.ml

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

142-
let access_control_headers = [
142+
(* Default access control headers *)
143+
let default_access_control_headers = [
143144
"access-control-allow-origin", "*";
144-
"access-control-allow-headers", "accept, content-type" ]
145+
"access-control-allow-headers", "accept, content-type"
146+
]
147+
148+
(* merge headers correctly with default one *)
149+
let merge_headers_with_default headers : (string * string) list =
150+
(* combining existing headers *)
151+
let l = List.fold_left
152+
(fun acc ((hn,hv) as h) ->
153+
match List.assoc_opt hn default_access_control_headers with
154+
| None -> h::acc
155+
| Some _ when hn = "access-control-allow-origin" ->
156+
h::acc
157+
| Some v when hn = "access-control-allow-headers" ->
158+
(hn, hv ^ "," ^ v)::acc
159+
| _ -> acc)
160+
[]
161+
headers
162+
in
163+
(* Adding default if not present *)
164+
List.fold_left (fun acc ((hn,_) as h) ->
165+
match List.assoc_opt hn l with
166+
| None -> h::acc
167+
| _ -> acc
168+
) l default_access_control_headers

src/server/ezOpenAPI.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ module Encoding = struct
470470
Some (bearer_name, Makers.mk_security_scheme ?format ~scheme:"bearer" "http")
471471
| `Header { EzAPI.Security.ref_name; name } ->
472472
Some (ref_name, Makers.mk_security_scheme ~loc:"header" ~name "apiKey")
473-
| `Cookie { EzAPI.Security.ref_name; name } ->
473+
| `Cookie ({ EzAPI.Security.ref_name; name }, _ ) ->
474474
Some (ref_name, Makers.mk_security_scheme ~loc:"cookie" ~name "apiKey")
475475
| `Query { EzAPI.Security.ref_name; name } ->
476476
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
@@ -256,7 +256,7 @@ let connection_handler :
256256
| Some c -> c path_str exn >|= fun a -> `http a)
257257
>>= function
258258
| `ws (Error _) ->
259-
let headers = Headers.of_list access_control_headers in
259+
let headers = Headers.of_list default_access_control_headers in
260260
let status = Status.unsafe_of_code 501 in
261261
let response = Response.create ~headers status in
262262
Reqd.respond_with_string reqd response "";
@@ -266,7 +266,7 @@ let connection_handler :
266266
| `http {Answer.code; body; headers} ->
267267
let status = Status.unsafe_of_code code in
268268
debug ~v:(if code = 200 then 1 else 0) "Reply computed to %S: %d" path_str code;
269-
let headers = headers @ access_control_headers in
269+
let headers = merge_headers_with_default headers in
270270
let headers = Headers.of_list headers in
271271
let len = String.length body in
272272
let headers = Headers.add headers "content-length" (string_of_int len) in

src/session/ezCookieServer.cohttp.ml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ open EzAPIServerUtils
1414
let cookie_re = Re.Str.regexp "[;,][ \t]*"
1515
let equals_re = Re.Str.regexp_string "="
1616

17+
let day_in_seconds = 86400L
18+
1719
let get ( req : Req.t ) =
1820
List.fold_left
1921
(fun acc header ->
@@ -30,13 +32,21 @@ let get ( req : Req.t ) =
3032
List.fold_left split_pair acc cookies
3133
) StringMap.empty (StringMap.find "cookie" req.Req.req_headers)
3234

33-
let set ?secure ?http_only ?expiration (req : Req.t) ~name ~value =
34-
let version = req.Req.req_version in
35-
Cohttp.Cookie.Set_cookie_hdr.serialize ~version @@
36-
Cohttp.Cookie.Set_cookie_hdr.make ?expiration ?secure ?http_only (name, value)
35+
(* TODO: Find a proper way to do that, Cohttp lib doesn't provide valid header when trying to clear header *)
36+
let set ?secure ?http_only ~expiration ~name ~value () =
37+
ignore secure;
38+
ignore http_only;
39+
"Set-Cookie", Printf.sprintf "%s=%s; Max-Age=%s" name value (Int64.to_string expiration)
40+
(* Cohttp.Cookie.Set_cookie_hdr.serialize ~version:`HTTP_1_0 @@
41+
Cohttp.Cookie.Set_cookie_hdr.make ~expiration ?secure ?http_only (name, value) *)
3742

38-
let clear req ~name =
39-
set req ~name ~value:"" ~expiration:(`Max_age 0L)
43+
let clear ~name () =
44+
set ~name ~value:"" ~expiration:0L ()
4045

41-
let set ?secure ?http_only req ~name ~value =
42-
set ?secure ?http_only req ~name ~value
46+
let set ?secure ?http_only ?expiration ~name ~value =
47+
let expiration =
48+
match expiration with
49+
| Some exp -> exp
50+
| None -> day_in_seconds
51+
in
52+
set ?secure ?http_only ~name ~value ~expiration

0 commit comments

Comments
 (0)