package sihl-token
Token service implementations for Sihl
Install
Dune Dependency
Authors
Maintainers
Sources
3.0.5.tar.gz
md5=20516760578c31cfc0e56db9bb84cebc
sha512=787252e6acd1010ae3ffd8105f4abff4f66c5d6efffee827f2ffc6695a2ad7cea6b859dc62398acf4c7e8b08ffa44a5974fa14faf68f623db14e91e96f0e65e6
doc/src/sihl-token/sihl_token.ml.html
Source file sihl_token.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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
let log_src = Logs.Src.create ("sihl.service." ^ Sihl.Contract.Token.name) module Logs = (val Logs.src_log log_src : Logs.LOG) module Make (Repo : Repo.Sig) : Sihl.Contract.Token.Sig = struct type config = { token_length : int option } let config token_length = { token_length } let schema = let open Conformist in make [ optional (int ~default:80 "TOKEN_LENGTH") ] config ;; let is_valid_token token = let open Repo.Model in String.equal (Status.to_string token.status) (Status.to_string Status.Active) && Ptime.is_later token.expires_at ~than:(Ptime_clock.now ()) ;; let make id ?(expires_in = Sihl.Time.OneDay) ?now ?(length = 80) data = let open Repo.Model in let value = Sihl.Random.base64 length in let expires_in = Sihl.Time.duration_to_span expires_in in let now = Option.value ~default:(Ptime_clock.now ()) now in let expires_at = match Ptime.add_span now expires_in with | Some expires_at -> expires_at | None -> failwith ("Could not parse expiry date for token with id " ^ id) in let status = Status.Active in let created_at = Ptime_clock.now () in { id; value; data; status; expires_at; created_at } ;; let create ?ctx ?secret:_ ?expires_in data = let open Repo.Model in let id = Uuidm.v `V4 |> Uuidm.to_string in let length = Option.value ~default:30 (Sihl.Configuration.read schema).token_length in let token = make id ?expires_in ~length data in let%lwt () = Repo.insert ?ctx token in Repo.find_by_id ?ctx id |> Lwt.map (fun token -> token.value) ;; let read ?ctx ?secret:_ ?force token_value ~k = let open Repo.Model in let%lwt token = Repo.find_opt ?ctx token_value in match token with | None -> Lwt.return None | Some token -> (match is_valid_token token, force with | true, _ | false, Some () -> (match List.find_opt (fun (key, _) -> String.equal k key) token.data with | Some (_, value) -> Lwt.return (Some value) | None -> Lwt.return None) | false, None -> Lwt.return None) ;; let read_all ?ctx ?secret:_ ?force token = let open Repo.Model in let%lwt token = Repo.find ?ctx token in match is_valid_token token, force with | true, _ | false, Some () -> Lwt.return (Some token.data) | false, None -> Lwt.return None ;; let verify ?ctx ?secret:_ token = let%lwt token = Repo.find_opt ?ctx token in match token with | Some _ -> Lwt.return true | None -> Lwt.return false ;; let deactivate ?ctx token = let open Repo.Model in let%lwt token = Repo.find ?ctx token in let updated = { token with status = Status.Inactive } in Repo.update ?ctx updated ;; let activate ?ctx token = let open Repo.Model in let%lwt token = Repo.find ?ctx token in let updated = { token with status = Status.Active } in Repo.update ?ctx updated ;; let is_active ?ctx token = let open Repo.Model in let%lwt token = Repo.find ?ctx token in match token.status with | Status.Active -> Lwt.return true | Status.Inactive -> Lwt.return false ;; let is_expired ?ctx ?secret:_ token = let open Repo.Model in let%lwt token = Repo.find ?ctx token in Lwt.return (Ptime.is_earlier token.expires_at ~than:(Ptime_clock.now ())) ;; let is_valid ?ctx ?secret:_ token = let open Repo.Model in let%lwt token = Repo.find_opt ?ctx token in match token with | None -> Lwt.return false | Some token -> (match token.status with | Status.Inactive -> Lwt.return false | Status.Active -> Lwt.return (Ptime.is_later token.expires_at ~than:(Ptime_clock.now ()))) ;; let start () = (* Make sure that configuration is valid *) Sihl.Configuration.require schema; Lwt.return () ;; let stop () = Lwt.return () let lifecycle = Sihl.Container.create_lifecycle Sihl.Contract.Token.name ~dependencies:(fun () -> Repo.lifecycles) ~start ~stop ;; let register () = Repo.register_migration (); Repo.register_cleaner (); let configuration = Sihl.Configuration.make ~schema () in Sihl.Container.Service.create ~configuration lifecycle ;; end module MakeJwt (Repo : Blacklist_repo.Sig) : Sihl.Contract.Token.Sig = struct let calculate_exp expires_in = Sihl.Time.date_from_now (Ptime_clock.now ()) expires_in |> Ptime.to_float_s |> Int.of_float |> string_of_int ;; let create ?ctx:_ ?secret ?(expires_in = Sihl.Time.OneWeek) data = let secret = Option.value ~default:(Sihl.Configuration.read_secret ()) secret in let data = match List.find_opt (fun (k, _) -> String.equal k "exp") data with | Some (_, v) -> (match int_of_string_opt v with | Some _ -> data | None -> let exp = calculate_exp expires_in in List.cons ("exp", exp) data) | None -> let exp = calculate_exp expires_in in List.cons ("exp", exp) data in match Jwto.encode HS512 secret data with | Error msg -> raise @@ Sihl.Contract.Token.Exception msg | Ok token -> Lwt.return token ;; let deactivate ?ctx:_ token = Repo.insert token let activate ?ctx:_ token = Repo.delete token let is_active ?ctx:_ token = Repo.has token |> Lwt.map not let read ?ctx:_ ?secret ?force token_value ~k = let secret = Option.value ~default:(Sihl.Configuration.read_secret ()) secret in match Jwto.decode_and_verify secret token_value, force with | Error msg, None -> Logs.warn (fun m -> m "Failed to decode and verify token: %s" msg); Lwt.return None | Ok token, None -> let%lwt is_active = is_active token_value in if is_active then ( match List.find_opt (fun (key, _) -> String.equal k key) (Jwto.get_payload token) with | Some (_, value) -> Lwt.return (Some value) | None -> Lwt.return None) else Lwt.return None | Ok token, Some () -> (match List.find_opt (fun (key, _) -> String.equal k key) (Jwto.get_payload token) with | Some (_, value) -> Lwt.return (Some value) | None -> Lwt.return None) | Error msg, Some () -> Logs.warn (fun m -> m "Failed to decode and verify token: %s" msg); (match Jwto.decode token_value with | Error msg -> Logs.warn (fun m -> m "Failed to decode token: %s" msg); Lwt.return None | Ok token -> (match List.find_opt (fun (key, _) -> String.equal k key) (Jwto.get_payload token) with | Some (_, value) -> Lwt.return (Some value) | None -> Lwt.return None)) ;; let read_all ?ctx:_ ?secret ?force token_value = let secret = Option.value ~default:(Sihl.Configuration.read_secret ()) secret in match Jwto.decode_and_verify secret token_value, force with | Error msg, None -> Logs.warn (fun m -> m "Failed to decode and verify token: %s" msg); Lwt.return None | Ok token, Some () -> Lwt.return (Some (Jwto.get_payload token)) | Ok token, None -> let%lwt is_active = is_active token_value in if is_active then Lwt.return (Some (Jwto.get_payload token)) else Lwt.return None | Error msg, Some () -> Logs.warn (fun m -> m "Failed to decode and verify token: %s" msg); (match Jwto.decode token_value with | Error msg -> Logs.warn (fun m -> m "Failed to decode token: %s" msg); Lwt.return None | Ok token -> Lwt.return (Some (Jwto.get_payload token))) ;; let verify ?ctx:_ ?secret token = let secret = Option.value ~default:(Sihl.Configuration.read_secret ()) secret in match Jwto.decode_and_verify secret token with | Ok _ -> Lwt.return true | Error _ -> Lwt.return false ;; let is_expired ?ctx:_ ?secret token_value = let secret = Option.value ~default:(Sihl.Configuration.read_secret ()) secret in match Jwto.decode_and_verify secret token_value with | Ok token -> (match List.find_opt (fun (k, _) -> String.equal k "exp") (Jwto.get_payload token) with | Some (_, exp) -> let exp = exp |> int_of_string_opt |> Option.map float_of_int in (match Option.bind exp Ptime.of_float_s with | Some expiration_date -> let is_expired = Ptime.is_earlier expiration_date ~than:(Ptime_clock.now ()) in Lwt.return is_expired | None -> raise @@ Sihl.Contract.Token.Exception (Format.sprintf "Invalid 'exp' claim found in token '%s'" token_value)) | None -> Lwt.return false) | Error msg -> Logs.warn (fun m -> m "Failed to decode and verify token: %s" msg); Lwt.return true ;; let is_valid ?ctx ?secret token = let%lwt is_expired = is_expired ?ctx ?secret token in Lwt.return (not is_expired) ;; let start () = Lwt.return () let stop () = Lwt.return () let lifecycle = Sihl.Container.create_lifecycle Sihl.Contract.Token.name ~dependencies:(fun () -> Repo.lifecycles) ~start ~stop ;; let register () = Repo.register_migration (); Repo.register_cleaner (); Sihl.Container.Service.create lifecycle ;; end module MariaDb = Make (Repo.MariaDb (Sihl.Database.Migration.MariaDb)) module PostgreSql = Make (Repo.PostgreSql (Sihl.Database.Migration.PostgreSql)) module JwtInMemory = MakeJwt (Blacklist_repo.InMemory) module JwtMariaDb = MakeJwt (Blacklist_repo.MariaDb) module JwtPostgreSql = MakeJwt (Blacklist_repo.PostgreSql)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>