Massively clean up JS and PHP
This commit is contained in:
parent
29de649fda
commit
e6def72c2e
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.14",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.14",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -145,11 +145,10 @@ if (isset($_POST["action"])) {
|
|||
case "login":
|
||||
$response = validate_csrf()
|
||||
?? validate_logged_out()
|
||||
?? validate_has_arguments($_POST["email"], $_POST["password"]);
|
||||
if ($response !== null) break;
|
||||
?? validate_has_arguments($_POST["email"], $_POST["password"])
|
||||
?? $user_manager->check_login($_POST["email"], $_POST["password"]);
|
||||
|
||||
[$response, $uuid] = $user_manager->check_login($_POST["email"], $_POST["password"]);
|
||||
if ($uuid !== null) $_SESSION["uuid"] = $uuid;
|
||||
if ($response->satisfied) $_SESSION["uuid"] = $response->payload["uuid"];
|
||||
break;
|
||||
case "logout":
|
||||
$response = validate_csrf() ?? validate_logged_in();
|
||||
|
@ -206,14 +205,31 @@ if (isset($_POST["action"])) {
|
|||
}
|
||||
} elseif (isset($_GET["action"])) {
|
||||
// GET requests; do not alter state
|
||||
$response = match ($_GET["action"]) {
|
||||
"get-user-data" => validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]),
|
||||
"list-trackings" => validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]),
|
||||
default => new Response(
|
||||
payload: ["target" => null, "message" => "Unknown GET action '" . $_GET["action"] . "'."],
|
||||
satisfied: false
|
||||
),
|
||||
};
|
||||
switch ($_GET["action"]) {
|
||||
case "start-session":
|
||||
if (!isset($_SESSION["uuid"])) {
|
||||
$response = new Response(payload: null, satisfied: true);
|
||||
break;
|
||||
}
|
||||
|
||||
$response = $user_manager->get_user_data($_SESSION["uuid"]);
|
||||
if (!$response->satisfied) {
|
||||
session_destroy();
|
||||
session_start();
|
||||
}
|
||||
break;
|
||||
case "get-user-data":
|
||||
$response = validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]);
|
||||
break;
|
||||
case "list-trackings":
|
||||
$response = validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]);
|
||||
break;
|
||||
default:
|
||||
$response = new Response(
|
||||
payload: ["target" => null, "message" => "Unknown GET action '" . $_GET["action"] . "'."],
|
||||
satisfied: false
|
||||
);
|
||||
}
|
||||
} elseif ($argc > 1) {
|
||||
// CLI
|
||||
if (hash_equals($config["admin"]["update_secret"], "REPLACE THIS WITH A SECRET VALUE"))
|
||||
|
|
|
@ -4,11 +4,21 @@
|
|||
}
|
||||
|
||||
|
||||
table form, table button {
|
||||
/* Trackings table */
|
||||
#trackings form, #trackings button, #addTrackingPersonName {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
#addTrackingPersonName, #addTrackingButton {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#addTrackingPersonName {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
|
||||
/* Input validation elements */
|
||||
.validationInfo {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
|
|
|
@ -34,9 +34,12 @@
|
|||
|
||||
<section class="container">
|
||||
<div class="row">
|
||||
<p id="validationInfoShared">
|
||||
<span class="validationInfo"></span>
|
||||
</p>
|
||||
<div class="column">
|
||||
<!-- Move form-specific messages closer to the form! -->
|
||||
<p id="validationInfoShared">
|
||||
<span class="validationInfo"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row hidden" id="loginRow">
|
||||
|
@ -54,7 +57,7 @@
|
|||
<input id="loginPassword" type="password" name="password" />
|
||||
<span class="validationInfo"></span>
|
||||
</label>
|
||||
<button>Log in</button>
|
||||
<button id="loginButton">Log in</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
|
@ -81,7 +84,7 @@
|
|||
<input id="registerPasswordConfirm" type="password" name="passwordConfirm" />
|
||||
<span class="validationInfo"></span>
|
||||
</label>
|
||||
<button>Create account</button>
|
||||
<button id="registerButton">Create account</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -99,18 +102,16 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<td colspan="3">
|
||||
<form id="addTrackingForm" novalidate>
|
||||
<label for="addTrackingPersonName">
|
||||
<input id="addTrackingPersonName" type="text" name="personName"
|
||||
autocomplete="on" />
|
||||
<button id="addTrackingButton">Add</button>
|
||||
<span class="validationInfo"></span>
|
||||
</label>
|
||||
<button>Add</button>
|
||||
</form>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -122,25 +123,27 @@
|
|||
<h2>Manage account</h2>
|
||||
<!-- TODO: Add way to delete account -->
|
||||
<form id="logoutForm" novalidate>
|
||||
<button>Log out</button>
|
||||
<button id="logoutButton">Log out</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3>Change email</h3>
|
||||
Current email: TODO<br />
|
||||
Verified? TODO
|
||||
<p>
|
||||
<b>Current email:</b> <span id="emailCurrent">TODO</span><br />
|
||||
<b>Validated:</b> <span id="emailValidated">TODO</span>
|
||||
</p>
|
||||
<form id="updateEmailForm" novalidate>
|
||||
<label for="updateEmailEmail">
|
||||
Email
|
||||
<input id="updateEmailEmail" type="email" name="email" autocomplete="on" />
|
||||
<span class="validationInfo"></span>
|
||||
</label>
|
||||
<button>Change email</button>
|
||||
<button id="updateEmailButton">Change email</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3>Change password</h3>
|
||||
Last changed: TODO
|
||||
<p>Last changed: <span id="passwordLastChanged">TODO</span></p>
|
||||
<form id="updatePasswordForm" novalidate>
|
||||
<label for="updatePasswordPasswordOld">
|
||||
Old password
|
||||
|
@ -157,7 +160,7 @@
|
|||
<input id="updatePasswordPasswordConfirm" type="password" name="passwordConfirm" />
|
||||
<span class="validationInfo"></span>
|
||||
</label>
|
||||
<button>Change password</button>
|
||||
<button id="updatePasswordButton">Change password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -82,6 +82,15 @@ function showError(element: HTMLElement, message?: string) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a GET request to the API.
|
||||
*
|
||||
* @param params the GET parameters to send
|
||||
* @param form the form to display validation info in
|
||||
* @param onSatisfied the callback to execute if the request returns successfully
|
||||
* @param onUnsatisfied the callback to execute if the request returns unsuccessfully
|
||||
* @param onError the callback to execute if there was an HTTP error
|
||||
*/
|
||||
function getApi(
|
||||
params: Record<string, string>,
|
||||
form: HTMLFormElement,
|
||||
|
@ -92,6 +101,15 @@ function getApi(
|
|||
interactWithApi("api.php?" + new URLSearchParams(params), undefined, form, onSatisfied, onUnsatisfied, onError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a POST request to the API.
|
||||
*
|
||||
* @param params the POST parameters to send
|
||||
* @param form the form to display validation info in
|
||||
* @param onSatisfied the callback to execute if the request returns successfully
|
||||
* @param onUnsatisfied the callback to execute if the request returns unsuccessfully
|
||||
* @param onError the callback to execute if there was an HTTP error
|
||||
*/
|
||||
function postApi(
|
||||
params: object,
|
||||
form: HTMLFormElement,
|
||||
|
@ -115,6 +133,16 @@ function postApi(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the API.
|
||||
*
|
||||
* @param url the URL to send the request to
|
||||
* @param options the options to send with the request
|
||||
* @param form the form to display validation info in
|
||||
* @param onSatisfied the callback to execute if the request returns successfully
|
||||
* @param onUnsatisfied the callback to execute if the request returns unsuccessfully
|
||||
* @param onError the callback to execute if there was an HTTP error
|
||||
*/
|
||||
function interactWithApi(
|
||||
url: string,
|
||||
options: object | undefined,
|
||||
|
@ -152,10 +180,11 @@ function interactWithApi(
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes the list of trackings in the table.
|
||||
*/
|
||||
function refreshTrackings() {
|
||||
function refreshTrackings(): void {
|
||||
getApi(
|
||||
{action: "list-trackings"},
|
||||
sharedMessageElement,
|
||||
|
@ -182,9 +211,7 @@ function refreshTrackings() {
|
|||
|
||||
const deleteCell = document.createElement("td");
|
||||
const deleteForm = document.createElement("form");
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.innerText = "remove";
|
||||
deleteButton.addEventListener("submit", (event: SubmitEvent) => {
|
||||
deleteForm.addEventListener("submit", (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
postApi(
|
||||
|
@ -193,22 +220,36 @@ function refreshTrackings() {
|
|||
() => refreshTrackings()
|
||||
)
|
||||
});
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.innerText = "remove";
|
||||
deleteForm.append(deleteButton);
|
||||
deleteCell.append(deleteForm);
|
||||
row.append(deleteCell);
|
||||
|
||||
tableBody.insertBefore(row, lastRow);
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// TODO
|
||||
},
|
||||
() => {
|
||||
// TODO
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes displays of the user's data.
|
||||
*
|
||||
* @param userData the most up-to-date information on the user
|
||||
*/
|
||||
function refreshUserData(userData: any): void {
|
||||
// Email
|
||||
$("#emailCurrent").innerText = userData.email;
|
||||
|
||||
// Password update time
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const updateTime = new Date(userData.password_update_time * 1000);
|
||||
updateTime.setHours(0, 0, 0, 0);
|
||||
const diff = (+today - +updateTime) / 86400000;
|
||||
$("#passwordLastChanged").innerText = diff === 0 ? "today" : diff + " days ago";
|
||||
}
|
||||
|
||||
|
||||
// Initialize template
|
||||
doAfterLoad(() => {
|
||||
|
@ -226,10 +267,12 @@ doAfterLoad(() => {
|
|||
|
||||
// Event handlers and so on
|
||||
doAfterLoad(() => {
|
||||
// Find rows
|
||||
const loginRow = $("#loginRow");
|
||||
const trackingRow = $("#trackingRow");
|
||||
const accountRow = $("#accountRow");
|
||||
|
||||
// Find forms
|
||||
const loginForm = $("#loginForm");
|
||||
const registerForm = $("#registerForm");
|
||||
const logoutForm = $("#logoutForm");
|
||||
|
@ -237,6 +280,35 @@ doAfterLoad(() => {
|
|||
const updatePasswordForm = $("#updatePasswordForm");
|
||||
const addTrackingForm = $("#addTrackingForm");
|
||||
|
||||
// Add common event code
|
||||
function onLogin() {
|
||||
loginRow.classList.add("hidden")
|
||||
trackingRow.classList.remove("hidden");
|
||||
accountRow.classList.remove("hidden");
|
||||
refreshTrackings();
|
||||
|
||||
loginForm.reset();
|
||||
clearMessages(loginForm);
|
||||
registerForm.reset();
|
||||
clearMessages(registerForm);
|
||||
}
|
||||
|
||||
function onLogout() {
|
||||
loginRow.classList.remove("hidden")
|
||||
trackingRow.classList.add("hidden");
|
||||
accountRow.classList.add("hidden");
|
||||
|
||||
addTrackingForm.reset();
|
||||
clearMessages(addTrackingForm);
|
||||
updateEmailForm.reset();
|
||||
clearMessages(updateEmailForm);
|
||||
updatePasswordForm.reset();
|
||||
clearMessages(updatePasswordForm);
|
||||
addTrackingForm.reset();
|
||||
clearMessages(addTrackingForm);
|
||||
}
|
||||
|
||||
// Add event handlers
|
||||
loginForm.addEventListener("submit", (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -248,11 +320,10 @@ doAfterLoad(() => {
|
|||
password: $("#loginPassword").value,
|
||||
},
|
||||
loginForm,
|
||||
() => {
|
||||
loginRow.classList.add("hidden");
|
||||
trackingRow.classList.remove("hidden");
|
||||
accountRow.classList.remove("hidden");
|
||||
refreshTrackings();
|
||||
(response) => {
|
||||
$("#emailCurrent").innerText = response.payload.email;
|
||||
$("#passwordLastChanged").innerText = response.payload.password_last_update;
|
||||
onLogin();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -285,11 +356,7 @@ doAfterLoad(() => {
|
|||
token: csrfToken,
|
||||
},
|
||||
logoutForm,
|
||||
() => {
|
||||
loginRow.classList.remove("hidden");
|
||||
trackingRow.classList.add("hidden");
|
||||
accountRow.classList.add("hidden");
|
||||
}
|
||||
() => onLogout()
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -303,7 +370,11 @@ doAfterLoad(() => {
|
|||
email: $("#updateEmailEmail").value,
|
||||
},
|
||||
updateEmailForm,
|
||||
() => sharedMessageElement.innerText = "Email updated successfully!"
|
||||
() => {
|
||||
$("#emailCurrent").innerText = $("#updateEmailEmail").value;
|
||||
updateEmailForm.reset();
|
||||
showSuccess(sharedMessageElement, "Email updated successfully!");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -314,12 +385,16 @@ doAfterLoad(() => {
|
|||
{
|
||||
action: "update-password",
|
||||
token: csrfToken,
|
||||
password_old: $("#updatePasswordPasswordOld"),
|
||||
password_new: $("#updatePasswordPasswordNew"),
|
||||
password_confirm: $("#updatePasswordPasswordConfirm"),
|
||||
password_old: $("#updatePasswordPasswordOld").value,
|
||||
password_new: $("#updatePasswordPasswordNew").value,
|
||||
password_confirm: $("#updatePasswordPasswordConfirm").value,
|
||||
},
|
||||
updatePasswordForm,
|
||||
() => sharedMessageElement.innerText = "Password updated successfully!"
|
||||
() => {
|
||||
$("#passwordLastChanged").innerText = "today";
|
||||
updatePasswordForm.reset();
|
||||
showSuccess(sharedMessageElement, "Password updated successfully!");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -340,21 +415,17 @@ doAfterLoad(() => {
|
|||
);
|
||||
});
|
||||
|
||||
|
||||
// TODO: Add appropriate message when session is expired
|
||||
// TODO: Log out if session is expired
|
||||
// Show content depending on whether user is logged in
|
||||
getApi(
|
||||
// TODO: Rename to `start-session` for semantic reasons?
|
||||
{action: "get-user-data"},
|
||||
{action: "start-session"},
|
||||
sharedMessageElement,
|
||||
() => {
|
||||
trackingRow.classList.remove("hidden");
|
||||
accountRow.classList.remove("hidden");
|
||||
refreshTrackings();
|
||||
},
|
||||
() => {
|
||||
clearMessage(sharedMessageElement);
|
||||
loginRow.classList.remove("hidden")
|
||||
(response: ServerResponse) => {
|
||||
if (response.payload === null) {
|
||||
onLogout();
|
||||
} else {
|
||||
onLogin();
|
||||
refreshUserData(response.payload);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -64,6 +64,11 @@ class TrackingManager
|
|||
*/
|
||||
public function add_tracking(string $user_uuid, string $person_name): Response
|
||||
{
|
||||
if (trim($person_name) === "")
|
||||
return new Response(
|
||||
payload: ["target" => "personName", "message" => "Invalid page name: empty."],
|
||||
satisfied: false
|
||||
);
|
||||
if (strlen($person_name) > self::MAX_TITLE_LENGTH)
|
||||
return new Response(
|
||||
payload: ["target" => "personName", "message" => "Invalid page name: too long."],
|
||||
|
|
|
@ -57,7 +57,8 @@ class UserManager
|
|||
public function install(): void
|
||||
{
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$conn->exec("CREATE TABLE users(uuid text primary key not null, email text not null, password text not null);");
|
||||
$conn->exec("CREATE TABLE users(uuid text primary key not null, email text not null,
|
||||
password text not null, password_update_time int not null);");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +118,8 @@ class UserManager
|
|||
}
|
||||
|
||||
// Register user
|
||||
$stmt = $conn->prepare("INSERT INTO users (uuid, email, password) VALUES (:uuid, :email, :password);");
|
||||
$stmt = $conn->prepare("INSERT INTO users (uuid, email, password, password_update_time)
|
||||
VALUES (:uuid, :email, :password, unixepoch());");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_DEFAULT));
|
||||
|
@ -151,44 +153,38 @@ class UserManager
|
|||
*
|
||||
* @param string $email the email address of the user whose password should be checked
|
||||
* @param string $password the password to check against the specified user
|
||||
* @return array{Response, ?string} the first element is a response with message `null` if the login was successful,
|
||||
* or a response with a message explaining what went wrong otherwise; the second element is the UUID of the user
|
||||
* that was logged in as, or `null` if the login should not be performed
|
||||
* @return Response a response with user data if the login was successful, or a response with a message explaining
|
||||
* what went wrong otherwise
|
||||
*/
|
||||
public function check_login(string $email, string $password): array
|
||||
public function check_login(string $email, string $password): Response
|
||||
{
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > self::MAX_EMAIL_LENGTH)
|
||||
return [
|
||||
new Response(
|
||||
payload: ["target" => "email", "message" => "Invalid email address."],
|
||||
satisfied: false
|
||||
),
|
||||
null
|
||||
];
|
||||
return new Response(
|
||||
payload: ["target" => "email", "message" => "Invalid email address."],
|
||||
satisfied: false
|
||||
);
|
||||
if (strlen($password) > self::MAX_PASSWORD_LENGTH)
|
||||
return [
|
||||
new Response(
|
||||
payload: ["target" => "password", "message" => "Incorrect combination of email and password."],
|
||||
satisfied: false
|
||||
),
|
||||
null
|
||||
];
|
||||
return new Response(
|
||||
payload: ["target" => "password", "message" => "Incorrect combination of email and password."],
|
||||
satisfied: false
|
||||
);
|
||||
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$stmt = $conn->prepare("SELECT uuid, password FROM users WHERE email=:email;");
|
||||
$stmt = $conn->prepare("SELECT uuid, email, password, password_update_time FROM users WHERE email=:email;");
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return (sizeof($results) === 0 || !password_verify($password, $results[0]["password"]))
|
||||
? [
|
||||
new Response(
|
||||
payload: ["target" => "password", "message" => "Incorrect combination of email and password."],
|
||||
satisfied: false
|
||||
),
|
||||
null
|
||||
]
|
||||
: [new Response(payload: null, satisfied: true), $results[0]["uuid"]];
|
||||
if (sizeof($results) === 0 || !password_verify($password, $results[0]["password"])) {
|
||||
return new Response(
|
||||
payload: ["target" => "password", "message" => "Incorrect combination of email and password."],
|
||||
satisfied: false
|
||||
);
|
||||
}
|
||||
|
||||
$user = $results[0];
|
||||
unset($user["password"]);
|
||||
return new Response(payload: $user, satisfied: true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,12 +196,15 @@ class UserManager
|
|||
public function get_user_data(string $uuid): Response
|
||||
{
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE uuid=:uuid;");
|
||||
$stmt = $conn->prepare("SELECT uuid, email, password_update_time FROM users WHERE uuid=:uuid;");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->execute();
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($user === false)
|
||||
return new Response(payload: ["target" => "uuid", "message" => "Invalid user."], satisfied: false);
|
||||
return new Response(
|
||||
payload: ["target" => "uuid", "message" => "Something went wrong. Please try logging in again."],
|
||||
satisfied: false
|
||||
);
|
||||
|
||||
return new Response(payload: $user, satisfied: true);
|
||||
}
|
||||
|
@ -305,7 +304,8 @@ class UserManager
|
|||
}
|
||||
|
||||
// Update password
|
||||
$stmt = $conn->prepare("UPDATE users SET password=:password WHERE uuid=:uuid;");
|
||||
$stmt = $conn->prepare("UPDATE users SET password=:password, password_update_time=unixepoch()
|
||||
WHERE uuid=:uuid;");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_DEFAULT));
|
||||
$stmt->execute();
|
||||
|
|
Loading…
Reference in New Issue