123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384openLwt.Syntax(* Can be used to fetch token in view for forms *)letctx_token_key:stringCore.Ctx.key=Core.Ctx.create_key()(*TODO [aerben] optional*)letget_tokenctx=Core.Ctx.findctx_token_keyctxexceptionNo_csrf_tokenofstring(* 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 *)moduleMake(TokenService:Token.Sig.SERVICE)(SessionService:Session.Sig.SERVICE)(RandomService:Utils.Random.Service.Sig.SERVICE)=structletcsrf_token_length=20letcreate_secretctx=let*token=TokenService.createctx~kind:"csrf"~length:csrf_token_length()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.setctx~key:"csrf"~value:token.idinLwt.returntoken;;letm()=letfilterhandlerctx=(* Check if session already has a secret (token) *)let*id=SessionService.getctx~key:"csrf"inlet*secret=matchidwith(* Create a secret if no secret found in session *)|None->create_secretctx|Sometoken_id->let*token=TokenService.find_by_id_optctxtoken_idin(matchtokenwith(* Create a secret if invalid token in session *)|None->create_secretctx(* Return valid secret from session *)|Somesecret->Lwt.returnsecret)in(* Randomize and scramble secret (XOR with salt) to make a token *)(* Do this to mitigate BREACH attacks: http://breachattack.com/#mitigations *)letsalt=RandomService.base64~bytes:csrf_token_lengthinlettoken=salt^Utils.Encryption.xorsaltsecret.valueinletctx=Core.Ctx.addctx_token_keytokenctxin(* Don't check for CSRF token in GET requests *)(* TODO don't check for HEAD, OPTIONS and TRACE either *)ifHttp.Req.is_getctxthenhandlerctxelselet*value=Http.Req.urlencodedctx"csrf"inmatchvaluewith(* Give 403 if no token provided *)|None->Http.Res.(html|>set_status403)|>Lwt.return|Somevalue->lettoken=Utils.Encryption.decrypt_with_salt~salted_cipher:value~salt_length:csrf_token_lengthinlet*provided_token=TokenService.find_optctxtokenin(matchprovided_tokenwith|Sometkp->ifnot@@Token.equalsecrettkpthen(* Give 403 if provided token doesn't match session token *)Http.Res.(html|>set_status403)|>Lwt.returnelse(* Provided token matches and is valid => Invalidate it so it can't be
reused *)let*()=TokenService.invalidatectxtkpinhandlerctx|None->(* Give 403 if provided token does not exist *)Http.Res.(html|>set_status403)|>Lwt.return)inMiddleware_core.create~name:"csrf"filter;;end