Skip to content

Commit

Permalink
Merge pull request #65 from robur-coop/csrf
Browse files Browse the repository at this point in the history
Csrf tokens
  • Loading branch information
PizieDust authored Oct 17, 2024
2 parents 187c3e1 + 883b2bd commit ff34d39
Show file tree
Hide file tree
Showing 11 changed files with 601 additions and 355 deletions.
29 changes: 23 additions & 6 deletions assets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ async function saveConfig() {
const pkeyInput = document.getElementById("private-key").value;
const formAlert = document.getElementById("form-alert");
const formButton = document.getElementById('config-button');
const molly_csrf = document.getElementById("molly-csrf").value.trim();
formButton.classList.add("disabled");
formButton.innerHTML = `<i class="fa-solid fa-spinner animate-spin"></i>`
formButton.innerHTML = `Processing <i class="fa-solid fa-spinner animate-spin text-primary-800"></i>`
formButton.disabled = true;
if (ipInput === '' || portInput === '' || certificateInput === '' || pkeyInput === '') {
formAlert.classList.remove("hidden");
formAlert.classList.add("text-secondary-500");
Expand All @@ -96,7 +98,13 @@ async function saveConfig() {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ "server_ip": ipInput, "server_port": Number(portInput), "certificate": certificateInput, "private_key": pkeyInput })
body: JSON.stringify({
"server_ip": ipInput,
"server_port": Number(portInput),
"certificate": certificateInput,
"private_key": pkeyInput,
"molly_csrf": molly_csrf
})
})
const data = await response.json();
if (data.status === 200) {
Expand All @@ -119,6 +127,7 @@ async function saveConfig() {
}
}
formButton.innerHTML = "Update"
formButton.disabled = false;
}

function closeBanner() {
Expand Down Expand Up @@ -146,6 +155,7 @@ async function deployUnikernel() {
const name = document.getElementById("unikernel-name").value.trim();
const arguments = document.getElementById("unikernel-arguments").value.trim();
const binary = document.getElementById("unikernel-binary").files[0];
const molly_csrf = document.getElementById("molly-csrf").value.trim();
const formAlert = document.getElementById("form-alert");
if (!name || !binary) {
formAlert.classList.remove("hidden", "text-primary-500");
Expand All @@ -158,6 +168,7 @@ async function deployUnikernel() {
formData.append("name", name);
formData.append("binary", binary)
formData.append("arguments", arguments)
formData.append("molly_csrf", molly_csrf)
try {
const response = await fetch("/unikernel/create", {
method: 'POST',
Expand Down Expand Up @@ -191,10 +202,13 @@ async function deployUnikernel() {

async function destroyUnikernel(name) {
try {
const molly_csrf = document.getElementById("molly-csrf").value.trim();
const response = await fetch(`/unikernel/destroy/${name}`, {
method: 'GET',
mode: "no-cors"
method: 'POST',
body: JSON.stringify({ "name": name, "molly_csrf": molly_csrf }),
headers: { 'Content-Type': 'application/json' }
})

const data = await response.json();
if (data.status === 200) {
postAlert("bg-primary-300", `Successful: ${data.data}`);
Expand Down Expand Up @@ -225,9 +239,10 @@ function buttonLoading(btn, load, text) {

async function toggleUserStatus(uuid, endpoint) {
try {
const molly_csrf = document.getElementById("molly-csrf").value.trim();
const response = await fetch(endpoint, {
method: 'POST',
body: JSON.stringify({ uuid: uuid }),
body: JSON.stringify({ uuid, molly_csrf }),
headers: { 'Content-Type': 'application/json' }
});

Expand Down Expand Up @@ -282,6 +297,7 @@ async function updatePolicy() {
const formAlert = document.getElementById("form-alert");
const user_id = document.getElementById("user_id").innerText;
const policyButton = document.getElementById("set-policy-btn");
const molly_csrf = document.getElementById("molly-csrf").value.trim();
try {
buttonLoading(policyButton, true, "Processing...")
const response = await fetch("/api/admin/u/policy/update", {
Expand All @@ -296,7 +312,8 @@ async function updatePolicy() {
"block": Number(storage_size),
"cpuids": cpuids,
"bridges": bridges,
"user_uuid": user_id
"user_uuid": user_id,
"molly_csrf": molly_csrf
})
})
const data = await response.json();
Expand Down
84 changes: 79 additions & 5 deletions middleware.ml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
type handler = Httpaf.Reqd.t -> unit Lwt.t
type middleware = handler -> handler

let has_session_cookie (reqd : Httpaf.Reqd.t) =
let get_csrf now =
User_model.(
generate_cookie ~name:"molly_csrf"
~uuid:(Uuidm.to_string (generate_uuid ()))
~created_at:now ~expires_in:3600)

let has_cookie cookie_name (reqd : Httpaf.Reqd.t) =
let headers = (Httpaf.Reqd.request reqd).headers in
match Httpaf.Headers.get headers "Cookie" with
| Some cookies ->
Expand All @@ -10,7 +16,7 @@ let has_session_cookie (reqd : Httpaf.Reqd.t) =
(fun cookie ->
let parts = String.trim cookie |> String.split_on_char '=' in
match parts with
| [ name; _ ] -> String.equal name "molly_session"
| [ name; _ ] -> String.equal name cookie_name
| _ -> false)
cookie_list
| _ -> None
Expand Down Expand Up @@ -92,13 +98,32 @@ let redirect_to_dashboard reqd ?(msg = "") () =
Httpaf.Reqd.respond_with_string reqd response msg;
Lwt.return_unit

let cookie_value_from_auth_cookie cookie =
let http_response ~title ?(header_list = []) ?(data = "") reqd http_status =
let code = Httpaf.Status.to_code http_status
and success = Httpaf.Status.is_successful http_status in
let status = { Utils.Status.code; title; data; success } in
let data = Utils.Status.to_json status in
let headers =
Httpaf.Headers.(
add_list
(of_list
[
("Content-Type", "application/json");
("Content-length", string_of_int (String.length data));
])
header_list)
in
let response = Httpaf.Response.create ~headers http_status in
Httpaf.Reqd.respond_with_string reqd response data;
Lwt.return_unit

let cookie_value cookie =
match String.split_on_char '=' (String.trim cookie) with
| _ :: s :: _ -> Ok (String.trim s)
| _ -> Error (`Msg "Bad cookie")

let user_from_auth_cookie cookie users =
match cookie_value_from_auth_cookie cookie with
match cookie_value cookie with
| Ok cookie_value -> (
match User_model.find_user_by_key cookie_value users with
| Some user -> Ok user
Expand All @@ -108,7 +133,7 @@ let user_from_auth_cookie cookie users =
Error (`Msg s)

let user_of_cookie users now reqd =
match has_session_cookie reqd with
match has_cookie "molly_session" reqd with
| Some auth_cookie -> (
match user_from_auth_cookie auth_cookie users with
| Ok user -> (
Expand Down Expand Up @@ -137,6 +162,15 @@ let user_of_cookie users now reqd =
m "auth-middleware: No molly-session in cookie header.");
Error (`Msg "User not found")

let session_cookie_value reqd =
match has_cookie "molly_session" reqd with
| Some cookie -> (
match cookie_value cookie with
| Ok "" -> Ok None
| Ok x -> Ok (Some x)
| Error _ as e -> e)
| None -> Error (`Msg "no cookie found")

let auth_middleware now users handler reqd =
match user_of_cookie users now reqd with
| Ok user ->
Expand All @@ -161,3 +195,43 @@ let is_user_admin_middleware api_meth now users handler reqd =
"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 ()

let csrf_match ~input_csrf ~check_csrf =
String.equal (Utils.Json.clean_string input_csrf) check_csrf

let csrf_cookie_verification form_csrf reqd =
match has_cookie "molly_csrf" reqd with
| Some cookie -> (
match cookie_value cookie with
| Ok token -> csrf_match ~input_csrf:form_csrf ~check_csrf:token
| Error (`Msg err) ->
Logs.err (fun m -> m "Error retrieving csrf value from cookie %s" err);
false)
| None ->
Logs.err (fun m -> m "Couldn't find csrf cookie.");
false

let csrf_verification users now form_csrf handler reqd =
match user_of_cookie users now reqd with
| Ok user -> (
let user_csrf_token =
List.find_opt
(fun (cookie : User_model.cookie) ->
String.equal cookie.name "molly_csrf")
user.User_model.cookies
in
match user_csrf_token with
| Some csrf_token ->
if
User_model.is_valid_cookie csrf_token now
&& csrf_match ~check_csrf:csrf_token.value ~input_csrf:form_csrf
then handler reqd
else
http_response ~title:"CSRF Token Mismatch"
~data:"CSRF token mismatch error. Please referesh and try again."
reqd `Bad_request
| None ->
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 ()
3 changes: 2 additions & 1 deletion settings_page.ml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let settings_layout (configuration : Configuration.t) =
let settings_layout ~csrf (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
Expand All @@ -11,6 +11,7 @@ let settings_layout (configuration : Configuration.t) =
div
~a:[ a_class [ "px-3 flex justify-between items-center" ] ]
[
Utils.csrf_form_input csrf;
div
[
p
Expand Down
8 changes: 5 additions & 3 deletions sign_up.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
open Tyxml

let register_page ~icon () =
let register_page ~csrf ~icon =
let page =
Html.(
html
Expand Down Expand Up @@ -38,6 +38,7 @@ let register_page ~icon () =
];
]
[
Utils.csrf_form_input csrf;
div
~a:[ a_class [ "w-full max-w-lg mt-16 pb-16 mx-auto" ] ]
[
Expand Down Expand Up @@ -255,7 +256,8 @@ let register_page ~icon () =
document.getElementById('register-button')\n\
\ registerButton.addEventListener('click', async \
function() {\n\
\ const name = \
const form_csrf = document.getElementById('molly-csrf').value\n\
\ const name = \
document.getElementById('name').value\n\
\ const email = \
document.getElementById('email').value\n\
Expand Down Expand Up @@ -304,7 +306,7 @@ let register_page ~icon () =
'application/json',\n\
\ },\n\
\ body: JSON.stringify({ name, \
email, password })\n\
email, password, form_csrf })\n\
\ })\n\
\ const data = await response.json();\n\
\ if (data.status === 200) {\n\
Expand Down
Loading

0 comments on commit ff34d39

Please sign in to comment.