0
48%
MPL-2.0
bs-cors
0.0.4
Buckelscript bindings to cors middleware

bs-cors

bs-cors is a Buckelscript binding to cors, made by Troy Goode, an expressjs middleware that can be used to enable Cross-origin resource sharing with various options.

Installation

This is a Node.js module available through the npm registry. Installation is done using the npm install command:

$ npm install bs-cors

then add it as a dependency to your bs-config.json :

...
  "bs-dependencies": ["bs-express", "bs-cors", ...],
...

Usage

Simple Usage (Enable All CORS Requests)

open  Express;
let app = express();

App.use(app,Cors.cors());


Middleware.from((_next, _req) => {
      [("msg", Js.Json.string("This is CORS-enabled for only example.com !"))]
    |> Js.Dict.fromList
    |> Js.Json.object_
    |> Response.sendJson
})
|> App.get(app, ~path="/products/:id");

let onListen = e =>
  switch (e) {
  | exception (Js.Exn.Error(e)) =>
    (
      switch (Js.Exn.message(e)) {
      | None => "UNKNOWN ERROR"
      | Some(msg) => msg
      }
    )
    |> Js.log;
    Node.Process.exit(1);
  | _ => "CORS-enabled web server listening on port 8080" |> Js.log
  };

let server = App.listen(app, ~port=8080, ~onListen, ());

Enable CORS for a Single Route

open Express;
let app = express();

[|
  Cors.cors(),
  Middleware.from((_next, _req) => {
    [("msg", Js.Json.string("This is CORS-enabled for only example.com !"))]
    |> Js.Dict.fromList
    |> Js.Json.object_
    |> Response.sendJson
  }),
|]
|> App.getWithMany(app, ~path="/products/:id");

let onListen = e =>
  switch (e) {
  | exception (Js.Exn.Error(e)) =>
    (
      switch (Js.Exn.message(e)) {
      | None => "UNKNOWN ERROR"
      | Some(msg) => msg
      }
    )
    |> Js.log;
    Node.Process.exit(1);
  | _ => "CORS-enabled web server listening on port 8080" |> Js.log
  };

let server = App.listen(app, ~port=8080, ~onListen, ());

Configuring CORS

open Express;
let app = express();

[|
  Cors.cors(
    ~origin=Cors.String("example.com"),
    ~optionsSuccessStatus=Response.StatusCode.Accepted,
    (),
  ), // some legacy browsers (IE11, various SmartTVs) choke on 204
  Middleware.from((_next, _req) => {
    [("msg", Js.Json.string("This is CORS-enabled for only example.com !"))]
    |> Js.Dict.fromList
    |> Js.Json.object_
    |> Response.sendJson
  }),
|]
|> App.getWithMany(app, ~path="/products/:id");


let onListen = e =>
  switch (e) {
  | exception (Js.Exn.Error(e)) =>
    (
      switch (Js.Exn.message(e)) {
      | None => "UNKNOWN ERROR"
      | Some(msg) => msg
      }
    )
    |> Js.log;
    Node.Process.exit(1);
  | _ => "CORS-enabled web server listening on port 8080" |> Js.log
  };

let server = App.listen(app, ~port=8080, ~onListen, ());

Configuring CORS w/ Dynamic Origin

open Express;
let app = express();

let whitelist = [|"http:\/\/example1.com", "http:\/\/example2.com"|];
let originFunction:
  (option(string), (option(Js.Exn.t), bool) => unit) => unit =
  (origin, callback) =>
    switch (origin) {
    /* If you do not want to block REST tools or server-to-server requests */
    | None => callback(None, true)
    | Some(o) =>
      if (Array.exists(item => item === o, whitelist)) {
        callback(None, true);
      } else {
        callback(Js.Exn.raiseError("Not allowed by CORS"), false);
      }
    };

[|
  Cors.cors(~origin=Function(originFunction), ()),
  Middleware.from((_next, _req) => {
    [("msg", Js.Json.string("This is CORS-enabled for only example.com !"))]
    |> Js.Dict.fromList
    |> Js.Json.object_
    |> Response.sendJson
  }),
|]
|> App.getWithMany(app, ~path="/products/:id");

let onListen = e =>
  switch (e) {
  | exception (Js.Exn.Error(e)) =>
    (
      switch (Js.Exn.message(e)) {
      | None => "UNKNOWN ERROR"
      | Some(msg) => msg
      }
    )
    |> Js.log;
    Node.Process.exit(1);
  | _ => "CORS-enabled web server listening on port 8080" |> Js.log
  };

let server = App.listen(app, ~port=8080, ~onListen, ());

Enabling CORS Pre-Flight

Certain CORS requests are considered 'complex' and require an initial OPTIONS request (called the "pre-flight request"). An example of a 'complex' CORS request is one that uses an HTTP verb other than GET/HEAD/POST (such as DELETE) or that uses custom headers. To enable pre-flighting, you must add a new OPTIONS handler for the route you want to support:

open Express;
let app = express();

App.options(app, ~path="/products/:id", Cors.cors());
[|
  Cors.cors(),
  Middleware.from((_next, _req) => {
    [("msg", Js.Json.string("This is CORS-enabled for all origins!"))]
    |> Js.Dict.fromList
    |> Js.Json.object_
    |> Response.sendJson
  }),
|]
|> App.deleteWithMany(app, ~path="/products/:id");


let onListen = e =>
  switch (e) {
  | exception (Js.Exn.Error(e)) =>
    (
      switch (Js.Exn.message(e)) {
      | None => "UNKNOWN ERROR"
      | Some(msg) => msg
      }
    )
    |> Js.log;
    Node.Process.exit(1);
  | _ => "CORS-enabled web server listening on port 8080" |> Js.log
  };

You can also enable pre-flight across-the-board like so:

/* include before other routes */
App.options(app, ~path="*", Cors.cors());

Configuring CORS Asynchronously

TODO

Configuration Options

The type of the cors middleware function is :

let cors:
  (
    ~origin: origin=?,
    ~methods: array(Express.Request.httpMethod)=?,
    ~allowedHeaders: option(array(string))=?,
    ~exposedHeaders: option(array(string))=?,
    ~credentials: bool=?,
    ~maxAge: option(int)=?,
    ~preflightContinue: bool=?,
    ~optionsSuccessStatus: Express.Response.StatusCode.t=?,
    unit
  ) =>
  Express.Middleware.t;
  • origin: Configures the Access-Control-Allow-Origin CORS header. It is a variant with thoses constructors:

    • Boolean(bool) - set origin to Boolean(true) to reflect the request origin, as defined by req.header('Origin'), or set it to Boolean(false) to disable CORS.
    • String(string) - set origin to a specific origin. For example if you set it to String("http:\/\/example.com") only requests from "http://example.com" will be allowed.
    • RegExp(Js.Re.t) - set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern RegExp([%re "/example\.com$/"]) will reflect any request that is coming from an origin ending with "example.com".
    • Array(array(string)) - set origin to an array of valid origins using strings.
    • Array(array(Js.Re.t)) - set origin to an array of valid origins using RegExp.
    • Function((option(string), (option(Js.Exn.t), bool) => unit) => unit) - set origin to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback
  • methods: Configures the Access-Control-Allow-Methods CORS header. Expects an array (ex: Express.Request.([|Get, Put, Post|])).
  • allowedHeaders: Configures the Access-Control-Allow-Headers CORS header. Expects an optional array (ex: Some([|"Content-Type", "Authorization"|])). If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.
  • exposedHeaders: Configures the Access-Control-Expose-Headers CORS header. Expects an optional array (ex: Some(["Content-Range", "X-Content-Range"])). If not specified, no custom headers are exposed.
  • credentials: Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, or false to be omitted.
  • maxAge: Configures the Access-Control-Max-Age CORS header. Set to an optional integer to pass the header, otherwise it is omitted.
  • preflightContinue: Pass the CORS preflight response to the next handler.
  • optionsSuccessStatus: Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204.

The default configuration is the equivalent of:

cors( ~origin=String("*"),
      ~methods=[|
                 Request.Get,
                 Request.Head,
                 Request.Put,
                 Request.Patch,
                 Request.Post,
                 Request.Delete,
               |],
      ~allowedHeaders=None,
      ~exposedHeaders=None,
      ~credentials=false,
      ~maxAge=None,
      ~preflightContinue=false,
      ~optionsSuccessStatus=Response.StatusCode.NoContent,
      ()
);

License

MPL-2.0

Author

Thomas Haessle

Thanks to

Troy Goode for building cors !!!