Improve CSRF handling and session management
This commit is contained in:
parent
7d0b08e6b7
commit
c8322d09c4
171
src/main/api.php
171
src/main/api.php
|
@ -34,59 +34,77 @@ if (!file_exists($config["database"]["filename"])) {
|
|||
|
||||
// Start session
|
||||
session_start();
|
||||
if (!isset($_SESSION["token"])) {
|
||||
$_SESSION["token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
// Read JSON from POST, if it's there
|
||||
if (empty($_POST)) {
|
||||
$_POST = json_decode(file_get_contents("php://input"), associative: true);
|
||||
}
|
||||
|
||||
$response = array();
|
||||
$response["satisfied"] = false;
|
||||
$response["token"] = $_SESSION["token"];
|
||||
|
||||
if (isset($_POST["action"])) {
|
||||
// Process POST
|
||||
switch ($_POST["action"]) {
|
||||
case "register":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($_SESSION["uuid"])) {
|
||||
exit("\"already logged in\"");
|
||||
$response["message"] = "already logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["email"], $_POST["password"], $_POST["password_confirm"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
|
||||
exit("\"invalid email\"");
|
||||
$response["message"] = "invalid email";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($_POST["password"] !== $_POST["password_confirm"]) {
|
||||
exit("\"differing passwords\"");
|
||||
$response["message"] = "differing passwords";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READONLY);
|
||||
$email_is_in_use = $db->get_user_by_email($_POST["email"]) !== null;
|
||||
$db->close();
|
||||
if ($email_is_in_use) {
|
||||
exit("\"email already in use\"");
|
||||
$response["message"] = "email already in use";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READWRITE);
|
||||
try {
|
||||
$uuid = bin2hex(random_bytes(16));
|
||||
} catch (\Exception) {
|
||||
exit("false");
|
||||
} catch (\Exception $exception) {
|
||||
$response["message"] = "unknown database error";
|
||||
break;
|
||||
}
|
||||
$db->add_user($uuid, $_POST["email"], $_POST["password"]);
|
||||
$db->close();
|
||||
exit("true");
|
||||
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "login":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["email"], $_POST["password"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READWRITE);
|
||||
|
@ -94,58 +112,77 @@ if (isset($_POST["action"])) {
|
|||
$db->close();
|
||||
|
||||
if ($user === null || !password_verify($_POST["password"], $user["password"])) {
|
||||
exit("\"wrong password\"");
|
||||
$response["message"] = "wrong password";
|
||||
break;
|
||||
}
|
||||
|
||||
$_SESSION["uuid"] = $user["uuid"];
|
||||
exit("true");
|
||||
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "logout":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
exit("true");
|
||||
session_start();
|
||||
$_SESSION["token"] = bin2hex(random_bytes(32));
|
||||
|
||||
$response["satisfied"] = true;
|
||||
$response["token"] = $_SESSION["token"];
|
||||
break;
|
||||
case "user-update-email":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["email"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
|
||||
exit("\"invalid email\"");
|
||||
$response["message"] = "invalid email";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Check if user exists
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READONLY);
|
||||
if ($db->get_user_by_email($_POST["email"]) !== null) {
|
||||
exit("\"email already in use\"");
|
||||
$response["message"] = "email already in use";
|
||||
break;
|
||||
}
|
||||
$db->close();
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READWRITE);
|
||||
$db->set_user_email($_SESSION["uuid"], $_POST["email"]);
|
||||
$db->close();
|
||||
exit("\"true\"");
|
||||
|
||||
$response["satisfied"] = "true";
|
||||
break;
|
||||
case "user-update-password":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["password_old"], $_POST["password_new"], $_POST["password_confirm"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Check if user exists
|
||||
|
@ -155,24 +192,30 @@ if (isset($_POST["action"])) {
|
|||
$db->close();
|
||||
|
||||
if ($user === null || !password_verify($_POST["password_old"], $user["password"])) {
|
||||
exit("\"wrong password\"");
|
||||
$response["message"] = "wrong password";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($_POST["password_new"] !== $_POST["password_confirm"]) {
|
||||
exit("\"differing passwords\"");
|
||||
$response["message"] = "differing passwords";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READWRITE);
|
||||
$db->set_user_password($_SESSION["uuid"], $_POST["password"]);
|
||||
$db->close();
|
||||
exit("true");
|
||||
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "user-delete":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Check if user exists
|
||||
|
@ -182,43 +225,52 @@ if (isset($_POST["action"])) {
|
|||
$db->close();
|
||||
|
||||
session_destroy();
|
||||
exit("true");
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "add-tracking":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["person_name"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READONLY);
|
||||
$tracking_already_exists = $db->has_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
||||
$db->close();
|
||||
if ($tracking_already_exists) {
|
||||
exit("\"tracking already exists\"");
|
||||
$response["message"] = "tracking already exists";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READWRITE);
|
||||
$db->add_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
||||
$db->close();
|
||||
|
||||
exit("true");
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "delete-tracking":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST["person_name"])) {
|
||||
exit("\"missing inputs\"");
|
||||
$response["message"] = "missing inputs";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Check if tracking exists
|
||||
|
@ -227,10 +279,12 @@ if (isset($_POST["action"])) {
|
|||
$db->remove_tracking($_SESSION["uuid"], $_POST["person_name"]);
|
||||
$db->close();
|
||||
|
||||
exit("true");
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "send-test-email":
|
||||
if (!isset($_POST["token"]) || !isset($_SESSION["token"]) || $_POST["token"] !== $_SESSION["token"]) {
|
||||
exit("\"no token, or invalid token\"");
|
||||
$response["message"] = "no token, or invalid token";
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Send this to logged-in user
|
||||
|
@ -250,7 +304,8 @@ if (isset($_POST["action"])) {
|
|||
$mail->setFrom($config["mail"]["username"], $config["mail"]["from_name"]);
|
||||
$mail->addAddress($config["mail"]["to_address_test"]);
|
||||
} catch (Exception) {
|
||||
exit("false");
|
||||
$response["message"] = "unknown mail error occurred";
|
||||
break;
|
||||
}
|
||||
|
||||
$mail->Subject = "Test mail";
|
||||
|
@ -259,37 +314,55 @@ if (isset($_POST["action"])) {
|
|||
try {
|
||||
$mail->send();
|
||||
} catch (Exception) {
|
||||
exit("false");
|
||||
$response["message"] = "unknown mail error occurred";
|
||||
break;
|
||||
}
|
||||
|
||||
exit("true");
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
default:
|
||||
$response["message"] = "unknown POST action '" . $_POST["action"] . "'";
|
||||
break;
|
||||
}
|
||||
} else if (isset($_GET["action"])) {
|
||||
// Process GET
|
||||
switch ($_GET["action"]) {
|
||||
case "get-user-data":
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READONLY);
|
||||
$user_data = $db->get_user_by_uuid($_SESSION["uuid"]);
|
||||
$db->close();
|
||||
|
||||
exit(json_encode($user_data));
|
||||
$response["message"] = $user_data;
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "list-trackings":
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
exit("\"not logged in\"");
|
||||
$response["message"] = "not logged in";
|
||||
break;
|
||||
}
|
||||
|
||||
$db = new Database($config["database"]["filename"], SQLITE3_OPEN_READONLY);
|
||||
$trackings = $db->list_trackings($_SESSION["uuid"]);
|
||||
$db->close();
|
||||
|
||||
exit(json_encode($trackings));
|
||||
$response["message"] = $trackings;
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
case "is-alive":
|
||||
exit(json_encode((new MyMediawiki())->people_are_alive(array("Janelle Monáe", "John Malkovich", "Adolf Hitler"))));
|
||||
$response["message"] = ((new MyMediawiki())->people_are_alive(array("Janelle Monáe", "John Malkovich", "Adolf Hitler")));
|
||||
$response["satisfied"] = true;
|
||||
break;
|
||||
default:
|
||||
$response["message"] = "unknown GET action '" . $_GET["action"] . "'";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$response["message"] = "unknown method";
|
||||
}
|
||||
|
||||
exit("\"unknown action\"");
|
||||
exit(json_encode($response));
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
<?php
|
||||
session_start();
|
||||
$_SESSION["token"] = bin2hex(random_bytes(32));
|
||||
?>
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -45,7 +39,6 @@ $_SESSION["token"] = bin2hex(random_bytes(32));
|
|||
<p>Already have an account? Welcome back!</p>
|
||||
<form id="loginForm" onsubmit="return false;">
|
||||
<p class="error"></p>
|
||||
<input type="hidden" name="token" value="<?= $_SESSION["token"] ?>" />
|
||||
<label>
|
||||
Email
|
||||
<input type="email" name="email" />
|
||||
|
@ -67,7 +60,6 @@ $_SESSION["token"] = bin2hex(random_bytes(32));
|
|||
</p>
|
||||
<form id="registerForm" onsubmit="return false;">
|
||||
<p class="error"></p>
|
||||
<input type="hidden" name="token" value="<?= $_SESSION["token"] ?>" />
|
||||
<label>
|
||||
Email
|
||||
<input type="email" name="email" />
|
||||
|
@ -103,7 +95,6 @@ $_SESSION["token"] = bin2hex(random_bytes(32));
|
|||
<h2>Manage account</h2>
|
||||
<form id="logoutForm" onsubmit="return false;">
|
||||
<p class="error"></p>
|
||||
<input type="hidden" name="token" value="<?= $_SESSION["token"] ?>" />
|
||||
<button>Log out</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -113,7 +104,6 @@ $_SESSION["token"] = bin2hex(random_bytes(32));
|
|||
Verified? TODO
|
||||
<form action="api.php" method="post">
|
||||
<input type="hidden" name="action" value="user-update-email" />
|
||||
<input type="hidden" name="token" value="<?= $_SESSION["token"] ?>" />
|
||||
<label>
|
||||
Email
|
||||
<input type="email" name="email" />
|
||||
|
@ -126,7 +116,6 @@ $_SESSION["token"] = bin2hex(random_bytes(32));
|
|||
Last changed: TODO
|
||||
<form action="api.php" method="post">
|
||||
<input type="hidden" name="action" value="user-update-password" />
|
||||
<input type="hidden" name="token" value="<?= $_SESSION["token"] ?>" />
|
||||
<label>
|
||||
Old password
|
||||
<input type="password" name="password_old" />
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// @ts-ignore
|
||||
const {$, doAfterLoad, footer, header, nav} = window.fwdekker;
|
||||
|
||||
let csrfToken: string|null = null;
|
||||
|
||||
|
||||
function refreshTrackings() {
|
||||
fetch("api.php?action=list-trackings")
|
||||
.then(it => it.json())
|
||||
.then(trackings => {
|
||||
.then(response => {
|
||||
$("#trackings tbody").remove();
|
||||
$("#trackings").append(document.createElement("tbody"));
|
||||
|
||||
|
@ -18,7 +20,7 @@ function refreshTrackings() {
|
|||
headerRow.append(headerPersonName, headerIsDeceased, headerDelete);
|
||||
$("#trackings tbody").append(headerRow);
|
||||
|
||||
trackings.forEach((tracking: any) => {
|
||||
response["message"].forEach((tracking: any) => {
|
||||
const trackingRow = document.createElement("tr");
|
||||
const trackingPersonName = document.createElement("td");
|
||||
trackingPersonName.innerText = tracking["person_name"];
|
||||
|
@ -38,14 +40,14 @@ function refreshTrackings() {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
action: "delete-tracking",
|
||||
token: $("#loginForm input[name=token]").value,
|
||||
token: csrfToken,
|
||||
person_name: tracking["person_name"],
|
||||
})
|
||||
})
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it !== true) {
|
||||
$("#trackingsError").innerText = it;
|
||||
if (!it["satisfied"]) {
|
||||
$("#trackingsError").innerText = it["message"];
|
||||
return;
|
||||
}
|
||||
refreshTrackings();
|
||||
|
@ -72,14 +74,14 @@ function refreshTrackings() {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
action: "add-tracking",
|
||||
token: $("#loginForm input[name=token]").value,
|
||||
token: csrfToken,
|
||||
person_name: createPersonInput.value,
|
||||
})
|
||||
})
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it !== true) {
|
||||
$("#trackingsError").innerText = it;
|
||||
if (!it["satisfied"]) {
|
||||
$("#trackingsError").innerText = it["message"];
|
||||
return;
|
||||
}
|
||||
refreshTrackings();
|
||||
|
@ -127,15 +129,15 @@ doAfterLoad(async () => {
|
|||
body: JSON.stringify({
|
||||
action: "login",
|
||||
// TODO: Deal with tokens in a smarter way. Allow refreshing them! Also remove duplication...
|
||||
token: $("#loginForm input[name=token]").value,
|
||||
token: csrfToken,
|
||||
email: $("#loginForm input[name=email]").value,
|
||||
password: $("#loginForm input[name=password]").value,
|
||||
})
|
||||
})
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it !== true) {
|
||||
$("#loginForm .error").innerText = it;
|
||||
if (!it["satisfied"]) {
|
||||
$("#loginForm .error").innerText = it["message"];
|
||||
return;
|
||||
}
|
||||
$("#loginForm").reset();
|
||||
|
@ -157,7 +159,7 @@ doAfterLoad(async () => {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
action: "register",
|
||||
token: $("#registerForm input[name=token]").value,
|
||||
token: csrfToken,
|
||||
email: $("#registerForm input[name=email]").value,
|
||||
password: $("#registerForm input[name=password]").value,
|
||||
password_confirm: $("#registerForm input[name=password_confirm]").value,
|
||||
|
@ -165,8 +167,8 @@ doAfterLoad(async () => {
|
|||
})
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it !== true) {
|
||||
$("#registerForm .error").innerText = it;
|
||||
if (!it["satisfied"]) {
|
||||
$("#registerForm .error").innerText = it["message"];
|
||||
return;
|
||||
}
|
||||
$("#registerForm").reset();
|
||||
|
@ -185,13 +187,15 @@ doAfterLoad(async () => {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
action: "logout",
|
||||
token: $("#logoutForm input[name=token]").value,
|
||||
token: csrfToken,
|
||||
})
|
||||
})
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it !== true) {
|
||||
$("#logoutForm .error").innerText = it;
|
||||
csrfToken = it["token"];
|
||||
|
||||
if (!it["satisfied"]) {
|
||||
$("#logoutForm .error").innerText = it["message"];
|
||||
return;
|
||||
}
|
||||
$("#logoutForm").reset();
|
||||
|
@ -202,9 +206,11 @@ doAfterLoad(async () => {
|
|||
});
|
||||
|
||||
fetch("api.php?action=get-user-data")
|
||||
.then(it => it.text())
|
||||
.then(it => it.json())
|
||||
.then(it => {
|
||||
if (it === "\"not logged in\"") {
|
||||
csrfToken = it["token"];
|
||||
|
||||
if (!it["satisfied"]) {
|
||||
loginRow.classList.remove("hidden");
|
||||
} else {
|
||||
trackingRow.classList.remove("hidden");
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"target": "es6",
|
||||
"strict": true,
|
||||
"rootDir": "./src/main/js/",
|
||||
"outDir": "./dist/js/"
|
||||
"outDir": "./dist/js/",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"src/main/js/**/*.ts"
|
||||
|
|
Loading…
Reference in New Issue