diff --git a/example/d-form/README.md b/example/d-form/README.md index 8a80c227..0e7327bf 100644 --- a/example/d-form/README.md +++ b/example/d-form/README.md @@ -16,7 +16,8 @@ let show_form ?message request =

You entered: <%s message %>!

% end; - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
@@ -53,13 +54,13 @@ Try it in the [playground](http://dream.as/d-form).
-We didn't write a literal `
` tag in the template. Instead, we used -[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) to generate -the tag. [`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) also -snuck in a hidden `` field containing a CSRF token: +We wrote a literal `` tag in the template, and injected a field containing +a CSRF token into it using the +[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag) helper to +generate the `` tag. ```html - + diff --git a/example/d-form/form.eml.ml b/example/d-form/form.eml.ml index 7ee950b5..16ac8131 100644 --- a/example/d-form/form.eml.ml +++ b/example/d-form/form.eml.ml @@ -8,7 +8,8 @@ let show_form ?message request =

You entered: <%s message %>!

% end; - <%s! Dream.form_tag ~action:"/" request %> + + <%s! Dream.csrf_tag request %>
diff --git a/example/g-upload/README.md b/example/g-upload/README.md index b9f94ef9..de859551 100644 --- a/example/g-upload/README.md +++ b/example/g-upload/README.md @@ -11,7 +11,8 @@ sizes: let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>
@@ -85,13 +86,13 @@ streaming file uploads. [`Dream.multipart`](https://aantron.github.io/dream/#val-multipart) behaves just like [`Dream.form`](https://aantron.github.io/dream/#val-form) when it comes to [CSRF protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html). -See example [**`d-form`**](../d-form#files). We still use -[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) to generate -the form in the template. The only difference is that we now pass it -``~enctype:`Multipart_form_data`` to make its output look like this: +See example [**`d-form`**](../d-form#files). We use +[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag) to generate +the CSRF token in the template, and pass the `enctype="multipart/form-data"` +attribute as needed for forms to upload files: ```html -
+ diff --git a/example/g-upload/upload.eml.ml b/example/g-upload/upload.eml.ml index 5c2a1752..e40cbc1e 100644 --- a/example/g-upload/upload.eml.ml +++ b/example/g-upload/upload.eml.ml @@ -1,7 +1,8 @@ let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> + + <%s! Dream.csrf_tag request %>
diff --git a/example/h-sql/README.md b/example/h-sql/README.md index bf85abb1..4bd9bbea 100644 --- a/example/h-sql/README.md +++ b/example/h-sql/README.md @@ -35,7 +35,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
diff --git a/example/h-sql/sql.eml.ml b/example/h-sql/sql.eml.ml index fe406b97..582ad660 100644 --- a/example/h-sql/sql.eml.ml +++ b/example/h-sql/sql.eml.ml @@ -25,7 +25,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
diff --git a/example/w-flash/README.md b/example/w-flash/README.md index 515a547d..d25a1f0d 100644 --- a/example/w-flash/README.md +++ b/example/w-flash/README.md @@ -13,7 +13,8 @@ absolutely primitive form with just one field: let form request = - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
diff --git a/example/w-flash/flash.eml.ml b/example/w-flash/flash.eml.ml index 1b9cd121..8bba6415 100644 --- a/example/w-flash/flash.eml.ml +++ b/example/w-flash/flash.eml.ml @@ -1,7 +1,8 @@ let form request = - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
diff --git a/example/w-multipart-dump/multipart_dump.eml.ml b/example/w-multipart-dump/multipart_dump.eml.ml index 6521436c..879112fe 100644 --- a/example/w-multipart-dump/multipart_dump.eml.ml +++ b/example/w-multipart-dump/multipart_dump.eml.ml @@ -1,7 +1,8 @@ let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>

diff --git a/example/w-postgres/postgres.eml.ml b/example/w-postgres/postgres.eml.ml index 52ecd53c..b7d3b037 100644 --- a/example/w-postgres/postgres.eml.ml +++ b/example/w-postgres/postgres.eml.ml @@ -25,7 +25,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> + + <%s! Dream.csrf_tag request %>
diff --git a/example/w-upload-stream/README.md b/example/w-upload-stream/README.md index ac03a22d..9105ec1c 100644 --- a/example/w-upload-stream/README.md +++ b/example/w-upload-stream/README.md @@ -11,7 +11,8 @@ the total size of each uploaded file: let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>
@@ -70,7 +71,7 @@ Try it in the [playground](http://dream.as/w-upload-stream). The report page shows one file without a name ("None"). This is, in fact, the CSRF token generated by -[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) inside the +[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag) inside the template. To keep the example simple, we didn't check the CSRF token, nor filter out the `dream.csrf` field that it appears in. If you'd like to do so in your code, see diff --git a/example/w-upload-stream/upload_stream.eml.ml b/example/w-upload-stream/upload_stream.eml.ml index 743f5960..a0e35d7e 100644 --- a/example/w-upload-stream/upload_stream.eml.ml +++ b/example/w-upload-stream/upload_stream.eml.ml @@ -1,7 +1,8 @@ let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>
diff --git a/src/dream.ml b/src/dream.ml index 426a64cc..b5525c90 100644 --- a/src/dream.ml +++ b/src/dream.ml @@ -195,6 +195,7 @@ let verify_csrf_token = Csrf.verify_csrf_token ~now let form_tag ?method_ ?target ?enctype ?csrf_token ~action request = Tag.form_tag ~now ?method_ ?target ?enctype ?csrf_token ~action request +let csrf_tag = Tag.csrf_tag ~now (* Middleware *) diff --git a/src/dream.mli b/src/dream.mli index 504853ba..ed7a6e87 100644 --- a/src/dream.mli +++ b/src/dream.mli @@ -910,12 +910,13 @@ val origin_referrer_check : middleware (** {1 Forms} - {!Dream.form_tag} and {!Dream.val-form} round-trip secure forms. - {!Dream.form_tag} is used inside a template to generate a form header with a - CSRF token: + {!Dream.csrf_tag} and {!Dream.val-form} round-trip secure forms. + {!Dream.csrf_tag} is used inside a form template to generate a hidden field + with a CSRF token: {[ - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
]} @@ -956,13 +957,13 @@ type 'a form_result = [ val form : ?csrf:bool -> request -> (string * string) list form_result promise (** Parses the request body as a form. Performs CSRF checks. Use - {!Dream.form_tag} in a template to transparently generate forms that will - pass these checks. See {!section-templates} and example + {!Dream.csrf_tag} in a form template to transparently generate forms that + will pass these checks. See {!section-templates} and example {{:https://github.com/aantron/dream/tree/master/example/d-form#readme} [d-form]}. - [Content-Type:] must be [application/x-www-form-urlencoded]. - - The form must have a field named [dream.csrf]. {!Dream.form_tag} adds such + - The form must have a field named [dream.csrf]. {!Dream.csrf_tag} adds such a field. - {!Dream.form} calls {!Dream.verify_csrf_token} to check the token in [dream.csrf]. @@ -1047,15 +1048,14 @@ type multipart_form = val multipart : ?csrf:bool -> request -> multipart_form form_result promise (** Like {!Dream.form}, but also reads files, and [Content-Type:] must be - [multipart/form-data]. The [
] tag and CSRF token can be generated in a - template with + [multipart/form-data]. The CSRF token can be generated in a template with {[ - <%s! Dream.form_tag ~action:"/" - ~enctype:`Multipart_form_data request %> + + <%s! Dream.csrf_tag request %> ]} - See {!Dream.form_tag}, section {!section-templates}, and example + See section {!section-templates}, and example {{:https://github.com/aantron/dream/tree/master/example/g-upload#files} [g-upload]}. @@ -1090,7 +1090,7 @@ val upload : request -> part option promise {!Dream.upload} does not verify a CSRF token. There are several ways to add CSRF protection for an upload stream, including: - - Generate the form with {!Dream.form_tag}. Check for + - Generate a CSRF token with {!Dream.csrf_tag}. Check for [`Field ("dream.csrf", token)] during upload and call {!Dream.verify_csrf_token}. - Use {{:https://developer.mozilla.org/en-US/docs/Web/API/FormData} @@ -1104,8 +1104,9 @@ val upload_part : request -> string option promise It's usually not necessary to handle CSRF tokens directly. - - Form tag generator {!Dream.form_tag} generates and inserts a CSRF token - that {!Dream.val-form} and {!Dream.val-multipart} transparently verify. + - CSRF token field generator {!Dream.csrf_tag} generates and inserts a CSRF + token that {!Dream.val-form} and {!Dream.val-multipart} transparently + verify. - AJAX can be protected from CSRF by {!Dream.origin_referrer_check}. CSRF functions are exposed for creating custom schemes, and for @@ -1228,6 +1229,29 @@ let render message = unquoted attribute values, CSS in [