package sihl
The modular functional web framework
Install
Dune Dependency
Authors
Maintainers
Sources
sihl-queue-0.1.9.tbz
sha256=77f0813d75a88edd14b3396e8b848d94c31c28803299b4b1bd4b78b1de4a2e80
sha512=a8907bc35ea14b7c3a7d638979a2a274860202b2de58b84b5621a4908db001ace493d8aa2e5383f4c8b1847efd256938592f63ef75a41521284b3640d3a7442a
doc/src/sihl.middleware/middleware_csrf.ml.html
Source file middleware_csrf.ml
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
module Core = Sihl_core module Utils = Sihl_utils module Http = Sihl_http module Token = Sihl_token module Session = Sihl_session open Lwt.Syntax let log_src = Logs.Src.create ~doc:"CSRF Middleware" "sihl.middleware.csrf" module Logs = (val Logs.src_log log_src : Logs.LOG) let key : string Opium_kernel.Hmap.key = Opium_kernel.Hmap.Key.create ("csrf token", Sexplib.Std.sexp_of_string) ;; exception Crypto_failed of string (* Can be used to fetch token in view for forms *) let find req = Opium_kernel.Hmap.find_exn key (Opium_kernel.Request.env req) let find_opt req = try Some (find req) with | _ -> None ;; let set token req = let env = Opium_kernel.Request.env req in let env = Opium_kernel.Hmap.add key token env in { req with env } ;; (* TODO (https://docs.djangoproject.com/en/3.0/ref/csrf/#how-it-works) Check other Django specifics namely: * Testing views with custom HTTP client * Allow Sihl user to make views exempt * Enable subdomain * HTML caching token handling *) module Make (TokenService : Token.Sig.SERVICE) (SessionService : Session.Sig.SERVICE) = struct let create_secret session = let* token = TokenService.create ~kind:"csrf" ~length:20 () in (* Store the ID in the session *) (* Storing the token directly could mean it ends up on the client if the cookie backend is used for session storage *) let* () = SessionService.set session ~key:"csrf" ~value:token.id in Lwt.return token ;; let m () = let filter handler req = (* Check if session already has a secret (token) *) let session = match Middleware_session.find_opt req with | Some session -> session | None -> Logs.info (fun m -> m "Have you applied the session middleware?"); raise (Crypto_failed "No session found") in let* id = SessionService.get session ~key:"csrf" in let* secret = match id with (* Create a secret if no secret found in session *) | None -> create_secret session | Some token_id -> let* token = TokenService.find_by_id_opt token_id in (match token with (* Create a secret if invalid token in session *) | None -> create_secret session (* Return valid secret from session *) | Some secret -> Lwt.return secret) in (* Randomize and scramble secret (XOR with salt) to make a token *) (* Do this to mitigate BREACH attacks: http://breachattack.com/#mitigations *) let secret_length = String.length secret.value in let salt = Core.Random.bytes ~nr:secret_length in let secret_value = secret.value |> String.to_seq |> List.of_seq in let encrypted = match Utils.Encryption.xor salt secret_value with | None -> Logs.err (fun m -> m "MIDDLEWARE: Failed to encrypt CSRF secret"); raise @@ Crypto_failed "Failed to encrypt CSRF secret" | Some enc -> enc in let token = encrypted |> List.append salt |> List.to_seq |> String.of_seq (* Make the token transmittable without encoding problems *) |> Base64.encode_string ~alphabet:Base64.uri_safe_alphabet in let req = set token req in (* Don't check for CSRF token in GET requests *) (* TODO don't check for HEAD, OPTIONS and TRACE either *) if Http.Request.is_get req then handler req else let* value = Http.Request.urlencoded "csrf" req in match value with (* Give 403 if no token provided *) | None -> Http.Response.(create () |> set_status 403) |> Lwt.return | Some value -> let decoded = Base64.decode ~alphabet:Base64.uri_safe_alphabet value in let decoded = match decoded with | Ok decoded -> decoded | Error (`Msg msg) -> Logs.err (fun m -> m "MIDDLEWARE: Failed to decode CSRF token. %s" msg); raise @@ Crypto_failed ("Failed to decode CSRF token. " ^ msg) in let salted_cipher = decoded |> String.to_seq |> List.of_seq in let decrypted_secret = match Utils.Encryption.decrypt_with_salt ~salted_cipher ~salt_length:(List.length salted_cipher / 2) with | None -> Logs.err (fun m -> m "MIDDLEWARE: Failed to decrypt CSRF token"); raise @@ Crypto_failed "Failed to decrypt CSRF token" | Some dec -> dec in let* provided_secret = TokenService.find_opt (decrypted_secret |> List.to_seq |> String.of_seq) in (match provided_secret with | Some ps -> if not @@ Token.equal secret ps then (* Give 403 if provided secret doesn't match session secret *) Http.Response.(create () |> set_status 403) |> Lwt.return else (* Provided secret matches and is valid => Invalidate it so it can't be reused *) let* () = TokenService.invalidate ps in handler req | None -> (* Give 403 if provided secret does not exist *) Http.Response.(create () |> set_status 403) |> Lwt.return) in Opium_kernel.Rock.Middleware.create ~name:"csrf" ~filter ;; end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>