diff --git a/assets/main.js b/assets/main.js index 91765077..17c1696f 100644 --- a/assets/main.js +++ b/assets/main.js @@ -1,5 +1,16 @@ document.addEventListener('DOMContentLoaded', function () { AOS.init(); + + const flashMessage = getCookie('flash_msg'); + if (flashMessage) { + if (flashMessage.startsWith("error:")) { + postAlert("bg-secondary-300", flashMessage); + } else { + postAlert("bg-primary-300", flashMessage); + } + document.cookie = "flash_msg=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 UTC;"; + } + if (window.location.pathname.startsWith("/admin/user/")) { const tabs = document.querySelectorAll(".tab-link"); const tabPanes = document.querySelectorAll(".tab-pane"); @@ -25,6 +36,16 @@ document.addEventListener('DOMContentLoaded', function () { } }); +function getCookie(name) { + const cookies = document.cookie.split(";"); + for (let cookie of cookies) { + const [cookieName, cookieValue] = cookie.split("="); + if (cookieName === name) { + return decodeURIComponent(cookieValue); + } + } +} + function getUnikernelName(url) { const urlObj = new URL(url); const pathParts = urlObj.pathname.split('/'); @@ -142,7 +163,7 @@ function postAlert(bg_color, content) { alertContainer.classList.remove("block", `${bg_color}`) alertContainer.classList.add("hidden") alertContainer.removeChild(alert); - }, 1600); + }, 2500); } async function deployUnikernel() { @@ -427,7 +448,7 @@ async function updatePassword() { const new_password = document.getElementById("new-password").value; const confirm_password = document.getElementById("confirm-password").value; const formAlert = document.getElementById("form-alert"); - if (!current_password || !new_password || !confirm_password ) { + if (!current_password || !new_password || !confirm_password) { formAlert.classList.remove("hidden", "text-primary-500"); formAlert.classList.add("text-secondary-500"); formAlert.textContent = "Please fill in all the required passwords" @@ -488,3 +509,23 @@ async function closeSessions() { buttonLoading(sessionButton, false, "Logout all other sessions") } } + +async function logout() { + const logoutButton = document.getElementById("logout-button"); + try { + buttonLoading(logoutButton, true, "Closing session..") + const molly_csrf = document.getElementById("molly-csrf").value; + fetch('/logout', { + method: 'POST', + body: JSON.stringify( + { + molly_csrf, + }), + headers: { 'Content-Type': 'application/json' } + }); + setTimeout(() => window.location.reload(), 1000); + } catch (error) { + postAlert("bg-secondary-300", error); + buttonLoading(logoutButton, false, "Logout") + } +} diff --git a/dashboard.ml b/dashboard.ml index 5e790693..19054184 100644 --- a/dashboard.ml +++ b/dashboard.ml @@ -1,6 +1,6 @@ open Tyxml -let dashboard_layout (user : User_model.user) ~icon +let dashboard_layout ~csrf (user : User_model.user) ~icon ?(page_title = "Dashboard | Mollymawk") ?message ~content () = let page = Html.( @@ -18,6 +18,7 @@ let dashboard_layout (user : User_model.user) ~icon ]; ] [ + Utils.csrf_form_input csrf; div ~a: [ @@ -154,7 +155,7 @@ let dashboard_layout (user : User_model.user) ~icon a_class [ "absolute top-1/4 rounded-md right-4 z-50 w-fit \ - space-y-2 p-4 shadow border text-wrap hidden"; + space-y-2 p-4 shadow text-wrap hidden"; ]; ] []; @@ -544,6 +545,19 @@ let dashboard_layout (user : User_model.user) ~icon ]; ] else div []); + button + ~a: + [ + a_id "logout-button"; + a_onclick "logout()"; + a_class + [ + "my-3 py-3 rounded bg-secondary-500 \ + hover:bg-secondary-800 w-full text-gray-50 \ + font-semibold"; + ]; + ] + [ txt "Logout" ]; ]; ]; section diff --git a/middleware.ml b/middleware.ml index b062eb83..a96cc97e 100644 --- a/middleware.ml +++ b/middleware.ml @@ -21,7 +21,7 @@ let cookie cookie_name (reqd : Httpaf.Reqd.t) = (fun cookie -> let parts = cookie |> String.split_on_char '=' in match parts with - | [ name; _ ] -> String.equal name cookie_name + | [ name; _ ] -> String.equal (String.trim name) cookie_name | _ -> false) cookie_list | _ -> None @@ -29,42 +29,40 @@ let cookie cookie_name (reqd : Httpaf.Reqd.t) = let apply_middleware middlewares handler = List.fold_right (fun middleware acc -> middleware acc) middlewares handler -let redirect_to_login reqd ?(msg = "") () = - let header_list = - [ - ( "Set-Cookie", - User_model.session_cookie - ^ "=;Path=/;HttpOnly=true;Expires=2023-10-27T11:00:00.778Z" ); - ("location", "/sign-in"); - ("Content-Length", string_of_int (String.length msg)); - ] +let redirect_to_page ~path ?(clear_session = false) ?(with_error = false) reqd + ?(msg = "") () = + let msg_cookie = + if with_error then "flash_msg=error: " ^ Uri.pct_encode msg ^ ";" + else "flash_msg=" ^ Uri.pct_encode msg ^ ";" in - let headers = Httpaf.Headers.of_list header_list in - let response = Httpaf.Response.create ~headers `Found in - Httpaf.Reqd.respond_with_string reqd response msg; - Lwt.return_unit - -let redirect_to_register reqd ?(msg = "") () = let header_list = - [ - ( "Set-Cookie", - User_model.session_cookie - ^ "=;Path=/;HttpOnly=true;Expires=2023-10-27T11:00:00.778Z" ); - ("location", "/sign-up"); - ("Content-Length", string_of_int (String.length msg)); - ] + let session_header = + if clear_session then + [ + ( "Set-Cookie", + User_model.session_cookie + ^ "=; Path=/; HttpOnly=true; Expires=2023-10-27T11:00:00.778Z" ); + ] + else [] + in + session_header + @ [ + ("Set-Cookie", msg_cookie); + ("location", path); + ("Content-Length", string_of_int (String.length msg)); + ] in let headers = Httpaf.Headers.of_list header_list in let response = Httpaf.Response.create ~headers `Found in Httpaf.Reqd.respond_with_string reqd response msg; Lwt.return_unit -let redirect_to_error ~title ~data status user code api_meth reqd () = +let redirect_to_error ~title ~data status code api_meth reqd () = let error = { Utils.Status.code; title; success = false; data } in let data = if api_meth then Utils.Status.to_json error else - Dashboard.dashboard_layout user ~page_title:(title ^ " | Mollymawk") + Guest_layout.guest_layout ~page_title:(title ^ " | Mollymawk") ~content:(Error_page.error_layout error) ~icon:"/images/robur.png" () in @@ -179,15 +177,21 @@ let auth_middleware now users handler reqd = match user_of_cookie users now reqd with | Ok user -> if user.User_model.active then handler reqd - else redirect_to_login ~msg:"User account is deactivated." reqd () - | Error (`Msg msg) -> redirect_to_login ~msg reqd () + else + redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true + ~msg:"User account is deactivated." reqd () + | Error (`Msg msg) -> + redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true + ~msg reqd () let email_verified_middleware now users handler reqd = match user_of_cookie users now reqd with | Ok user -> if User_model.is_email_verified user then handler reqd else redirect_to_verify_email reqd () - | Error (`Msg msg) -> redirect_to_login ~msg reqd () + | Error (`Msg msg) -> + redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true + ~msg reqd () let is_user_admin_middleware api_meth now users handler reqd = match user_of_cookie users now reqd with @@ -197,8 +201,10 @@ let is_user_admin_middleware api_meth now users handler reqd = redirect_to_error ~title:"Unauthorized" ~data: "You don't have the necessary permissions to access this service." - `Unauthorized user 401 api_meth reqd () - | Error (`Msg msg) -> redirect_to_login ~msg reqd () + `Unauthorized 401 api_meth reqd () + | Error (`Msg err) -> + redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true + ~msg:err reqd () let csrf_match ~input_csrf ~check_csrf = String.equal input_csrf check_csrf @@ -237,4 +243,6 @@ let csrf_verification users now form_csrf handler reqd = http_response ~data:"Missing CSRF token. Please referesh and try again." ~title:"Missing CSRF Token" reqd `Bad_request) - | Error (`Msg err) -> redirect_to_login ~msg:err reqd () + | Error (`Msg err) -> + redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true + ~msg:err reqd () diff --git a/settings_page.ml b/settings_page.ml index 546b0fbc..7f246984 100644 --- a/settings_page.ml +++ b/settings_page.ml @@ -1,4 +1,4 @@ -let settings_layout ~csrf (configuration : Configuration.t) = +let settings_layout (configuration : Configuration.t) = let ip = Ipaddr.to_string configuration.server_ip in let port = string_of_int configuration.server_port in let certificate = X509.Certificate.encode_pem configuration.certificate in @@ -11,7 +11,6 @@ let settings_layout ~csrf (configuration : Configuration.t) = div ~a:[ a_class [ "px-3 flex justify-between items-center" ] ] [ - Utils.csrf_form_input csrf; div [ p diff --git a/sign_in.ml b/sign_in.ml index b3ffcf7d..c81b8822 100644 --- a/sign_in.ml +++ b/sign_in.ml @@ -38,6 +38,17 @@ let login_page ~icon () = ]; ] [ + div + ~a: + [ + a_id "alert-container"; + a_class + [ + "absolute top-1/4 rounded-md right-4 z-50 w-fit \ + space-y-2 p-4 shadow text-wrap hidden"; + ]; + ] + []; div ~a:[ a_class [ "w-full max-w-lg mt-16 pb-16 mx-auto" ] ] [ diff --git a/sign_up.ml b/sign_up.ml index 20f23d9c..3089bb56 100644 --- a/sign_up.ml +++ b/sign_up.ml @@ -49,8 +49,8 @@ let register_page ~csrf ~icon = a_class [ "absolute top-1/4 rounded-md right-4 z-50 \ - w-fit space-y-2 p-4 shadow border \ - text-wrap hidden"; + w-fit space-y-2 p-4 shadow text-wrap \ + hidden"; ]; ] []; diff --git a/unikernel.ml b/unikernel.ml index 13e5b218..575b930e 100644 --- a/unikernel.ml +++ b/unikernel.ml @@ -454,7 +454,7 @@ struct | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error) @@ -480,7 +480,9 @@ struct else Middleware.http_response reqd ~title:"Error" ~data:"Logged in user is not the to-be-verified one" `Bad_request - | Error (`Msg s) -> Middleware.redirect_to_login reqd ~msg:s () + | Error (`Msg s) -> + Middleware.redirect_to_page ~path:"/sign-in" ~clear_session:true + ~with_error:true reqd ~msg:s () let toggle_account_attribute json_dict store reqd ~key update_fn error_on_last ~error_message = @@ -546,31 +548,41 @@ struct <= 1) ~error_message:"Cannot remove last administrator" - let dashboard albatross reqd (user : User_model.user) = - (* TODO use uuid in the future *) - (Albatross.query albatross ~domain:user.name - (`Unikernel_cmd `Unikernel_info) - >|= function - | Error msg -> - Logs.err (fun m -> - m "error while communicating with albatross: %s" msg); - [] - | Ok (_hdr, `Success (`Unikernel_info unikernels)) -> unikernels - | Ok reply -> - Logs.err (fun m -> - m "expected a unikernel info reply, received %a" - (Vmm_commands.pp_wire ~verbose:false) - reply); - []) - >>= fun unikernels -> - Lwt.return - (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user - ~content: - (Unikernel_index.unikernel_index_layout unikernels - (Ptime.v (P.now_d_ps ()))) - ~icon:"/images/robur.png" ()) - `OK) + let dashboard store albatross reqd (user : User_model.user) = + let now = Ptime.v (P.now_d_ps ()) in + generate_csrf_token store user now reqd >>= function + | Ok csrf -> + (* TODO use uuid in the future *) + (Albatross.query albatross ~domain:user.name + (`Unikernel_cmd `Unikernel_info) + >|= function + | Error msg -> + Logs.err (fun m -> + m "error while communicating with albatross: %s" msg); + [] + | Ok (_hdr, `Success (`Unikernel_info unikernels)) -> unikernels + | Ok reply -> + Logs.err (fun m -> + m "expected a unikernel info reply, received %a" + (Vmm_commands.pp_wire ~verbose:false) + reply); + []) + >>= fun unikernels -> + Lwt.return + (reply reqd ~content_type:"text/html" + (Dashboard.dashboard_layout ~csrf user + ~content: + (Unikernel_index.unikernel_index_layout unikernels + (Ptime.v (P.now_d_ps ()))) + ~icon:"/images/robur.png" ()) + `OK) + | Error err -> + Lwt.return + (reply reqd ~content_type:"text/html" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" + ~content:(Error_page.error_layout err) + ~icon:"/images/robur.png" ()) + `Internal_server_error) let account_page store reqd (user : User_model.user) = match Middleware.session_cookie_value reqd with @@ -580,7 +592,7 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user + (Dashboard.dashboard_layout ~csrf user ~page_title:"Account | Mollymawk" ~content: (User_account.user_account_layout ~csrf user @@ -591,7 +603,7 @@ struct | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error)) @@ -606,7 +618,7 @@ struct in Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"401 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"401 | Mollymawk" ~content:(Error_page.error_layout error) ~icon:"/images/robur.png" ()) `Unauthorized) @@ -661,31 +673,48 @@ struct (Yojson.Basic.to_string (`Assoc json_dict))) `Bad_request - let close_sessions store reqd (user : User_model.user) = + let new_user_cookies ~user ~filter ~redirect store reqd = + let now = Ptime.v (P.now_d_ps ()) in + let cookies = List.filter filter user.User_model.cookies in + let updated_user = + User_model.update_user user ~cookies ~updated_at:now () + in + Store.update_user !store updated_user >>= function + | Ok store' -> + store := store'; + redirect + | Error (`Msg err) -> + Logs.warn (fun m -> m "Storage error with %s" err); + Middleware.http_response reqd ~title:"Error" ~data:err + `Internal_server_error + + let close_sessions ?to_logout_cookie ?(logout = false) store reqd + (user : User_model.user) = match Middleware.session_cookie_value reqd with | Ok cookie_value -> ( match User_model.user_session_cookie cookie_value user with - | Some cookie -> ( - let now = Ptime.v (P.now_d_ps ()) in - let cookies = - cookie - :: List.filter - (fun (cookie : User_model.cookie) -> - not (String.equal cookie.name User_model.session_cookie)) - user.cookies - in - let updated_user = - User_model.update_user user ~cookies ~updated_at:now () + | Some cookie -> + let filter, redirect = + match (to_logout_cookie, logout) with + | None, false -> + ( (fun (c : User_model.cookie) -> + not + (String.equal c.name User_model.session_cookie + && c.value <> cookie.value)), + Middleware.http_response reqd ~title:"OK" + ~data:"Closed all sessions succesfully" `OK ) + | _, true -> + ( (fun (c : User_model.cookie) -> + not (String.equal c.value cookie.value)), + Middleware.http_response reqd ~title:"OK" + ~data:"Logout succesful" `OK ) + | Some to_logout_cookie_value, false -> + ( (fun (c : User_model.cookie) -> + not (String.equal to_logout_cookie_value c.value)), + Middleware.redirect_to_page ~path:"/account" + ~msg:"Closed session succesfully" reqd () ) in - Store.update_user !store updated_user >>= function - | Ok store' -> - store := store'; - Middleware.http_response reqd ~title:"OK" - ~data:"Closed all sessions successfully" `OK - | Error (`Msg err) -> - Logs.warn (fun m -> m "Storage error with %s" err); - Middleware.http_response reqd ~title:"Error" ~data:err - `Internal_server_error) + new_user_cookies ~user ~filter ~redirect store reqd | None -> let error = { @@ -697,7 +726,7 @@ struct in Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"401 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"401 | Mollymawk" ~content:(Error_page.error_layout error) ~icon:"/images/robur.png" ()) `Unauthorized)) @@ -712,20 +741,31 @@ struct in Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"401 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"401 | Mollymawk" ~content:(Error_page.error_layout error) ~icon:"/images/robur.png" ()) `Unauthorized) let users store reqd (user : User_model.user) = - Lwt.return - (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"Users | Mollymawk" - ~content: - (Users_index.users_index_layout (snd store).Storage.users - (Ptime.v (P.now_d_ps ()))) - ~icon:"/images/robur.png" ()) - `OK) + let now = Ptime.v (P.now_d_ps ()) in + generate_csrf_token store user now reqd >>= function + | Ok csrf -> + Lwt.return + (reply reqd ~content_type:"text/html" + (Dashboard.dashboard_layout ~csrf user + ~page_title:"Users | Mollymawk" + ~content: + (Users_index.users_index_layout (snd !store).Storage.users + (Ptime.v (P.now_d_ps ()))) + ~icon:"/images/robur.png" ()) + `OK) + | Error err -> + Lwt.return + (reply reqd ~content_type:"text/html" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" + ~content:(Error_page.error_layout err) + ~icon:"/images/robur.png" ()) + `Internal_server_error) let settings store reqd (user : User_model.user) = let now = Ptime.v (P.now_d_ps ()) in @@ -733,9 +773,10 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"Settings | Mollymawk" + (Dashboard.dashboard_layout ~csrf user + ~page_title:"Settings | Mollymawk" ~content: - (Settings_page.settings_layout ~csrf + (Settings_page.settings_layout (snd !store).Storage.configuration) ~icon:"/images/robur.png" ()) ~header_list:[ ("X-MOLLY-CSRF", csrf) ] @@ -743,7 +784,7 @@ struct | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error) @@ -777,16 +818,16 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user + (Dashboard.dashboard_layout ~csrf user ~page_title:"Deploy a Unikernel | Mollymawk" - ~content:(Unikernel_create.unikernel_create_layout ~csrf) + ~content:Unikernel_create.unikernel_create_layout ~icon:"/images/robur.png" ()) ~header_list:[ ("X-MOLLY-CSRF", csrf) ] `OK) | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error) @@ -841,9 +882,9 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user + (Dashboard.dashboard_layout ~csrf user ~content: - (Unikernel_single.unikernel_single_layout ~csrf + (Unikernel_single.unikernel_single_layout (List.hd unikernels) now console_output) ~icon:"/images/robur.png" ()) ~header_list:[ ("X-MOLLY-CSRF", csrf) ] @@ -851,7 +892,7 @@ struct | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error) @@ -866,8 +907,7 @@ struct in Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user - ~page_title:"An Error Occured | Mollymawk" + (Guest_layout.guest_layout ~page_title:"An Error Occured | Mollymawk" ~content:(Error_page.error_layout error) ~icon:"/images/robur.png" ()) `Internal_server_error) @@ -1076,18 +1116,17 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user + (Dashboard.dashboard_layout ~csrf user ~page_title:(String.capitalize_ascii u.name ^ " | Mollymawk") ~content: - (User_single.user_single_layout ~csrf u unikernels policy - now) + (User_single.user_single_layout u unikernels policy now) ~icon:"/images/robur.png" ()) ~header_list:[ ("X-MOLLY-CSRF", csrf) ] `OK) | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error)) @@ -1102,7 +1141,7 @@ struct in Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user ~page_title:"404 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"404 | Mollymawk" ~content:(Error_page.error_layout status) ~icon:"/images/robur.png" ()) `Not_found) @@ -1124,20 +1163,19 @@ struct | Ok csrf -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user + (Dashboard.dashboard_layout ~csrf user ~page_title: (String.capitalize_ascii u.name ^ " | Mollymawk") ~content: - (Update_policy.update_policy_layout ~csrf u - ~user_policy ~unallocated_resources) + (Update_policy.update_policy_layout u ~user_policy + ~unallocated_resources) ~icon:"/images/robur.png" ()) ~header_list:[ ("X-MOLLY-CSRF", csrf) ] `OK) | Error err -> Lwt.return (reply reqd ~content_type:"text/html" - (Dashboard.dashboard_layout user - ~page_title:"500 | Mollymawk" + (Guest_layout.guest_layout ~page_title:"500 | Mollymawk" ~content:(Error_page.error_layout err) ~icon:"/images/robur.png" ()) `Internal_server_error)) @@ -1302,7 +1340,7 @@ struct (verify_email_token store reqd token)) | "/dashboard" -> check_meth `GET (fun () -> - authenticate store reqd (dashboard !albatross reqd)) + authenticate store reqd (dashboard store !albatross reqd)) | "/account" -> check_meth `GET (fun () -> authenticate store reqd (account_page store reqd)) @@ -1324,9 +1362,34 @@ struct | Error (`Msg msg) -> Middleware.http_response reqd ~title:"Error" ~data:(String.escaped msg) `Bad_request) + | "/logout" -> + check_meth `POST (fun () -> + extract_csrf_token reqd >>= function + | Ok (form_csrf, _) -> + authenticate ~form_csrf store reqd + (close_sessions ~logout:true store reqd) + | Error (`Msg msg) -> + Middleware.http_response reqd ~title:"Error" + ~data:(String.escaped msg) `Bad_request) + | path + when String.( + length path >= 23 && sub path 0 23 = "/account/session/close/") + -> + check_meth `GET (fun () -> + match + String.split_on_char '/' + (String.sub path 23 (String.length path - 23)) + with + | [ to_logout_cookie; form_csrf ] -> + authenticate ~form_csrf store reqd + (close_sessions ~to_logout_cookie store reqd) + | _ -> + Middleware.http_response reqd ~title:"Error" + ~data:"An error occured. Please refresh and try again" + `Bad_request) | "/admin/users" -> check_meth `GET (fun () -> - authenticate ~check_admin:true store reqd (users !store reqd)) + authenticate ~check_admin:true store reqd (users store reqd)) | path when String.(length path >= 12 && sub path 0 12 = "/admin/user/") -> check_meth `GET (fun () -> diff --git a/unikernel_create.ml b/unikernel_create.ml index 0b9af4dc..a5c2d1a8 100644 --- a/unikernel_create.ml +++ b/unikernel_create.ml @@ -1,4 +1,4 @@ -let unikernel_create_layout ~csrf = +let unikernel_create_layout = Tyxml_html.( section ~a:[ a_class [ "col-span-7 p-4 bg-gray-50 my-1" ] ] @@ -14,7 +14,6 @@ let unikernel_create_layout ~csrf = div ~a:[ a_class [ "space-y-6 mt-8 p-6 max-w-5xl mx-auto" ] ] [ - Utils.csrf_form_input csrf; p ~a:[ a_id "form-alert"; a_class [ "my-4 hidden" ] ] []; div [ diff --git a/unikernel_single.ml b/unikernel_single.ml index fdf4f70d..30195600 100644 --- a/unikernel_single.ml +++ b/unikernel_single.ml @@ -1,4 +1,4 @@ -let unikernel_single_layout ~csrf unikernel current_time console_output = +let unikernel_single_layout unikernel current_time console_output = let u_name, data = unikernel in Tyxml_html.( section @@ -44,7 +44,6 @@ let unikernel_single_layout ~csrf unikernel current_time console_output = [ div [ - Utils.csrf_form_input csrf; button ~a: [ @@ -64,7 +63,6 @@ let unikernel_single_layout ~csrf unikernel current_time console_output = ]; div [ - Utils.csrf_form_input csrf; button ~a: [ diff --git a/update_policy.ml b/update_policy.ml index 50edcceb..acf24902 100644 --- a/update_policy.ml +++ b/update_policy.ml @@ -1,10 +1,9 @@ -let update_policy_layout ~csrf (user : User_model.user) ~user_policy +let update_policy_layout (user : User_model.user) ~user_policy ~unallocated_resources = Tyxml_html.( section ~a:[ a_id "policy-form" ] [ - Utils.csrf_form_input csrf; h2 ~a:[ a_class [ "font-semibold text-2xl" ] ] [ txt ("Set Policy for " ^ user.name) ]; diff --git a/user_account.ml b/user_account.ml index 025f6840..b125d3ea 100644 --- a/user_account.ml +++ b/user_account.ml @@ -7,7 +7,6 @@ let user_account_layout ~csrf (user : User_model.user) ~active_cookie_value p ~a:[ a_class [ "text-3xl font-semibold uppercase" ] ] [ txt (user.name ^ " - Account") ]; - Utils.csrf_form_input csrf; section ~a:[ a_class [ "my-5" ] ] [ @@ -306,6 +305,27 @@ let user_account_layout ~csrf (user : User_model.user) ~active_cookie_value span [ txt "Expiry time not available" ]); ]; + (if + not + (String.equal cookie.value + active_cookie_value) + then + a + ~a: + [ + a_href + ("account/session/close/" + ^ cookie.value ^ "/" ^ csrf); + a_class + [ + "hover:text-secondary-800 \ + text-secondary-500 \ + transition-colors \ + cursor-pointer"; + ]; + ] + [ txt "Close session" ] + else div []); ]; ]) (List.filter diff --git a/user_model.ml b/user_model.ml index 4956fbdc..8a13f18b 100644 --- a/user_model.ml +++ b/user_model.ml @@ -569,7 +569,7 @@ let is_valid_cookie (cookie : cookie) now = < cookie.expires_in let is_email_verified user = Option.is_some user.email_verified -let password_validation password = String.length password > 8 +let password_validation password = String.length password >= 8 let verify_email_token users token timestamp = let* uuid = diff --git a/user_single.ml b/user_single.ml index 3032465b..9bfa1a1b 100644 --- a/user_single.ml +++ b/user_single.ml @@ -1,5 +1,4 @@ -let user_single_layout ~csrf (user : User_model.user) unikernels policy - current_time = +let user_single_layout (user : User_model.user) unikernels policy current_time = Tyxml_html.( section ~a:[ a_class [ "p-4 bg-gray-50 my-1" ] ] @@ -10,7 +9,6 @@ let user_single_layout ~csrf (user : User_model.user) unikernels policy section ~a:[ a_class [ "my-5" ] ] [ - Utils.csrf_form_input csrf; ul ~a: [