Source file web_authentication.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
open Sexplib.Std

let log_src = Logs.Src.create "sihl.middleware.authentication"

module Logs = (val Logs.src_log log_src : Logs.LOG)

type credentials =
  { email : string
  ; password : string
  }
[@@deriving sexp]

let key_login : credentials Opium.Context.key =
  Opium.Context.Key.create ("authenticate.login", sexp_of_credentials)
;;

let login ~email ~password res =
  let env = res.Opium.Response.env in
  let credentials = { email; password } in
  let env = Opium.Context.add key_login credentials env in
  { res with env }
;;

let default_site_error_handler _ =
  Lwt.return
    (Opium.Response.of_plain_text "" |> Opium.Response.set_status `Unauthorized)
;;

let session_middleware
    ?(key = "authn")
    ?(error_handler = default_site_error_handler)
    login
  =
  let open Lwt.Syntax in
  let filter handler req =
    let* resp = handler req in
    let env = resp.Opium.Response.env in
    match Web_session.find key req, Opium.Context.find key_login env with
    | Some _, Some { email; password } ->
      let* user = login ~email ~password in
      (match user with
      | Error error -> error_handler error
      | Ok user ->
        let resp = Web_session.set (key, Some user.Contract_user.id) resp in
        Lwt.return resp)
    | Some _, None -> Lwt.return resp
    | None, Some _ -> Lwt.return resp
    | None, None -> Lwt.return resp
  in
  Rock.Middleware.create ~name:"user.session" ~filter
;;

let default_json_error_handler _ =
  let msg = {|{"errors": ["Invalid email or password provided"]}|} in
  Lwt.return
    (Opium.Response.of_plain_text msg
    |> Opium.Response.set_status `Unauthorized
    |> Opium.Response.set_content_type "application/json")
;;

let token_middleware
    ?(key = "token")
    ?(error_handler = default_json_error_handler)
    login
    create_token
  =
  let open Lwt.Syntax in
  let filter handler req =
    let* resp = handler req in
    let env = resp.Opium.Response.env in
    match Opium.Context.find key_login env with
    | None -> Lwt.return resp
    | Some { email; password } ->
      let* user = login ~email ~password in
      (match user with
      | Error error -> error_handler error
      | Ok user ->
        let* token = create_token [ "user_id", user.Contract_user.id ] in
        let msg = Format.sprintf {|{"%s": "%s"}|} key token in
        Lwt.return
          (Opium.Response.of_plain_text msg
          |> Opium.Response.set_content_type "application/json"))
  in
  Rock.Middleware.create ~name:"user.token" ~filter
;;