diff --git a/unikernel.ml b/unikernel.ml index c294ef77..d617cca8 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -693,15 +693,18 @@ struct | Some cookie -> ( match Middleware.user_from_auth_cookie cookie users with | Ok user -> ( + let email_verification_uuid = User_model.generate_uuid () in let updated_user = - User_model.update_user user ~updated_at:now () + User_model.update_user user ~updated_at:now + ~email_verification_uuid:(Some email_verification_uuid) + () in Store.update_user !store updated_user >>= function | Ok store' -> store := store'; let verification_link = Utils.Email.generate_verification_link - updated_user.uuid now + email_verification_uuid in Logs.info (fun m -> m "Verification link is: %s" verification_link); @@ -733,10 +736,12 @@ struct let _, (t : Storage.t) = !store in let users = User_model.create_user_uuid_map t.users in let now = Ptime.v (P.now_d_ps ()) in - let veification_token = + let verification_token = String.sub path 19 (String.length path - 19) in - match User_model.verify_email users veification_token now with + match + User_model.verify_email_token users verification_token now + with | Ok user -> ( Store.update_user !store user >>= function | Ok store' -> diff --git a/user_model.ml b/user_model.ml index c5cee0aa..0b394fe9 100644 --- a/user_model.ml +++ b/user_model.ml @@ -26,6 +26,7 @@ type user = { cookies : cookie list; created_at : Ptime.t; updated_at : Ptime.t; + email_verification_uuid : Uuidm.t option; } let week = 604800 (* a week = 7 days * 24 hours * 60 minutes * 60 seconds *) @@ -184,7 +185,8 @@ let user_of_json = function get "tokens" xs, get "cookies" xs, get "created_at" xs, - get "updated_at" xs ) + get "updated_at" xs, + get "email_verification_uuid" xs ) with | ( Some (`String name), Some (`String email), @@ -194,7 +196,8 @@ let user_of_json = function Some (`List tokens), Some (`List cookies), Some (`String updated_at_str), - Some (`String created_at_str) ) -> + Some (`String created_at_str), + Some email_verification_uuid ) -> let created_at = match Utils.TimeHelper.ptime_of_string created_at_str with | Ok ptime -> Some ptime @@ -222,6 +225,18 @@ let user_of_json = function Ok (cookie :: acc)) (Ok []) cookies in + let* email_verification_uuid = + match email_verification_uuid with + | `Null -> Ok None + | `String s -> + let* uuid = + Option.to_result + ~none:(`Msg "invalid UUID for email verification UUID") + (Uuidm.of_string s) + in + Ok (Some uuid) + | _ -> Error (`Msg "invalid json data for email verification UUID") + in Ok { name; @@ -233,6 +248,7 @@ let user_of_json = function cookies; created_at = Option.get created_at; updated_at = Option.get updated_at; + email_verification_uuid; } | _ -> Error (`Msg "invalid json for user")) | _ -> Error (`Msg "invalid json for user") @@ -302,13 +318,14 @@ let create_user ~name ~email ~password ~created_at = cookies = [ session ]; created_at; updated_at = created_at; + email_verification_uuid = None; } let check_if_user_exists email users = List.find_opt (fun user -> user.email = Utils.Json.clean_string email) users let update_user user ?name ?email ?email_verified ?password ?tokens ?cookies - ?updated_at () = + ?updated_at ?email_verification_uuid () = { user with name = (match name with Some name -> name | _ -> user.name); @@ -322,6 +339,8 @@ let update_user user ?name ?email ?email_verified ?password ?tokens ?cookies (match updated_at with | Some updated_at -> updated_at | _ -> user.updated_at); + email_verification_uuid = + Option.value ~default:user.email_verification_uuid email_verification_uuid; } let update_cookies (cookies : cookie list) (cookie : cookie) : cookie list = @@ -335,73 +354,36 @@ let is_valid_cookie (cookie : cookie) now = let is_email_verified user = Option.is_some user.email_verified -let verify_email users token timestamp = - let token_data = String.split_on_char '/' token in - let uuid = - match List.nth_opt token_data 0 with - | Some user_uuid -> - Logs.err (fun m -> m "email verification: user uuid is %s" user_uuid); - user_uuid - | None -> - Logs.err (fun m -> m "email verification: empty uuid in signature"); - "" - in - - let token_signature = - match List.nth_opt token_data 1 with - | Some token -> - Logs.err (fun m -> m "email verification: token sig is %s" token); - token - | None -> - Logs.err (fun m -> m "email verification: empty signature"); - "" - in - let decoded_uuid = - match Base64.decode uuid with - | Ok d_uuid -> - Logs.err (fun m -> - m "email verification: decoded user uuid is %s" d_uuid); - d_uuid - | Error (`Msg s) -> - Logs.err (fun m -> - m "email verification: can't decode uuid with error %s" s); - "" +let verify_email_token users token timestamp = + let ( let* ) = Result.bind in + let* uuid = + Option.to_result ~none:(`Msg "invalid UUID") (Uuidm.of_string token) in - let user = find_user_by_key decoded_uuid users in - match user with - | Some u -> ( - let is_valid_link = - 3600 > Utils.TimeHelper.diff_in_seconds timestamp u.updated_at - in - match is_valid_link with - | true -> ( - let computed_signature = - Utils.Email.generate_signature u.uuid u.updated_at + match + List.find_opt + (fun (_, user) -> + match user.email_verification_uuid with + | Some uu -> Uuidm.equal uu uuid + | None -> false) + users + with + | None -> + Logs.err (fun m -> m "email verification: Token couldn't be found."); + Error (`Msg "No token was found.") + | Some (_, u) -> ( + match Utils.TimeHelper.diff_in_seconds timestamp u.updated_at < 3600 with + | true -> + let updated_user = + update_user u ~email_verified:(Some timestamp) ~updated_at:timestamp + ~email_verification_uuid:None () in - Logs.err (fun m -> - m "email verification: token signature is %s" token_signature); - Logs.err (fun m -> - m "email verification: computed signature is %s" - computed_signature); - match String.equal token_signature computed_signature with - | true -> - let updated_user = - update_user u ~email_verified:(Some timestamp) - ~updated_at:timestamp () - in - Ok updated_user - | false -> - Logs.err (fun m -> m "email verification: This link is invalid"); - Error (`Msg "This link is invalid.")) + Ok updated_user | false -> - Logs.err (fun m -> m "email verification: This link has expired"); + Logs.err (fun m -> m "email verification: This link is expired."); Error (`Msg "This link has expired. Please sign in to get a new verification \ link.")) - | None -> - Logs.err (fun m -> m "email verification: This account does not exist."); - Error (`Msg "This account does not exist.") let user_auth_cookie_from_user (user : user) = List.find_opt diff --git a/utils.ml b/utils.ml index 83c55b2b..e080fefc 100644 --- a/utils.ml +++ b/utils.ml @@ -62,33 +62,6 @@ module Email = struct Logs.err (fun m -> m "Emile-Email-Validation: %a" Emile.pp_error s); false - let generate_signature uuid timestamp = - let timestamp_str = TimeHelper.string_of_ptime timestamp in - Base64.encode_string - (Cstruct.to_string - (Mirage_crypto.Hash.SHA256.digest - (Cstruct.of_string (uuid ^ "-" ^ timestamp_str)))) - - let generate_verification_link uuid timestamp = - let signature = generate_signature uuid timestamp in - let encoded_uuid = Base64.encode_string uuid in - "/auth/verify/token=" ^ encoded_uuid ^ "/" ^ signature - - let send_verification_email _email _now = - let config_data = - { - smtp_host = "sandbox.smtp.mailtrap.io"; - smtp_port = 25; - username = "2b08693767f1d9"; - password = "55168d154ddc56"; - } - in - let authentication = - { - Sendmail.mechanism = PLAIN; - username = config_data.username; - password = config_data.password; - } - in - authentication + let generate_verification_link uuid = + "/auth/verify/token=" ^ Uuidm.to_string uuid end