Specify pass algorithm, remove some duplication
This commit is contained in:
parent
65e25c608f
commit
634cb477ef
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "fwdekker/death-notifier",
|
"name": "fwdekker/death-notifier",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"version": "0.0.30", "_comment_version": "Also update version in `package.json`!",
|
"version": "0.0.31", "_comment_version": "Also update version in `package.json`!",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "death-notifier",
|
"name": "death-notifier",
|
||||||
"version": "0.0.30", "_comment_version": "Also update version in `composer.json`!",
|
"version": "0.0.31", "_comment_version": "Also update version in `composer.json`!",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"author": "Florine W. Dekker",
|
"author": "Florine W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
;<?php exit(); ?>
|
;<?php exit(); ?>
|
||||||
|
|
||||||
|
# TODO: Add feature for global site message
|
||||||
|
# TODO: Add i18n
|
||||||
|
|
||||||
[admin]
|
[admin]
|
||||||
# Password to use the CLI of `api.php`. Until this value is changed from its default, the feature is disabled
|
# Password to use the CLI of `api.php`. Until this value is changed from its default, the feature is disabled
|
||||||
cli_secret = REPLACE THIS WITH A SECRET VALUE
|
cli_secret = REPLACE THIS WITH A SECRET VALUE
|
||||||
|
|
|
@ -173,6 +173,7 @@
|
||||||
<div class="row hidden" id="accountRow">
|
<div class="row hidden" id="accountRow">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h2>Manage account</h2>
|
<h2>Manage account</h2>
|
||||||
|
<!-- TODO: Position these buttons more nicely -->
|
||||||
<form id="logoutForm" novalidate>
|
<form id="logoutForm" novalidate>
|
||||||
<p class="formValidationInfo"><span class="validationInfo"></span></p>
|
<p class="formValidationInfo"><span class="validationInfo"></span></p>
|
||||||
<button id="logoutButton">Log out</button>
|
<button id="logoutButton">Log out</button>
|
||||||
|
@ -228,6 +229,7 @@
|
||||||
</label>
|
</label>
|
||||||
<button id="updatePasswordButton">Change password</button>
|
<button id="updatePasswordButton">Change password</button>
|
||||||
</form>
|
</form>
|
||||||
|
<!-- TODO: Add forgot password button after logging in -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -78,6 +78,22 @@ class UserManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if there is a user with the given email address.
|
||||||
|
*
|
||||||
|
* @param string $email the email address to check
|
||||||
|
* @return bool `true` if there is a user with the given email address
|
||||||
|
*/
|
||||||
|
private function email_is_used(string $email): bool
|
||||||
|
{
|
||||||
|
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE email=:email);");
|
||||||
|
$stmt->bindValue(":email", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result[0] === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new user.
|
* Registers a new user.
|
||||||
*
|
*
|
||||||
|
@ -99,11 +115,7 @@ class UserManager
|
||||||
$this->conn->beginTransaction();
|
$this->conn->beginTransaction();
|
||||||
|
|
||||||
// Check if email address is already in use
|
// Check if email address is already in use
|
||||||
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE email=:email);");
|
if ($this->email_is_used($email)) {
|
||||||
$stmt->bindValue(":email", $email);
|
|
||||||
$stmt->execute();
|
|
||||||
$result = $stmt->fetch();
|
|
||||||
if ($result[0] === 1) {
|
|
||||||
$this->conn->rollBack();
|
$this->conn->rollBack();
|
||||||
return new Response(
|
return new Response(
|
||||||
payload: ["target" => "email", "message" => "Email address already in use."],
|
payload: ["target" => "email", "message" => "Email address already in use."],
|
||||||
|
@ -116,8 +128,7 @@ class UserManager
|
||||||
VALUES (:email, :password)
|
VALUES (:email, :password)
|
||||||
RETURNING email_verification_token;");
|
RETURNING email_verification_token;");
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
// TODO: Specify password hash function, for forwards compatibility
|
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
||||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_DEFAULT));
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$email_verification_token = $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
$email_verification_token = $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
||||||
$this->mailer->queue_register_password($email, $email_verification_token);
|
$this->mailer->queue_register_password($email, $email_verification_token);
|
||||||
|
@ -239,14 +250,8 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reject update if email address was changed too recently (within, say, 5 minutes)
|
|
||||||
|
|
||||||
// Check if email address is already in use
|
// Check if email address is already in use
|
||||||
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE email=:email);");
|
if ($this->email_is_used($email)) {
|
||||||
$stmt->bindValue(":email", $email);
|
|
||||||
$stmt->execute();
|
|
||||||
$result = $stmt->fetch();
|
|
||||||
if ($result[0] === 1) {
|
|
||||||
$this->conn->rollBack();
|
$this->conn->rollBack();
|
||||||
return new Response(
|
return new Response(
|
||||||
payload: ["target" => "email", "message" => "Email address already in use."],
|
payload: ["target" => "email", "message" => "Email address already in use."],
|
||||||
|
@ -283,6 +288,7 @@ class UserManager
|
||||||
{
|
{
|
||||||
$this->conn->beginTransaction();
|
$this->conn->beginTransaction();
|
||||||
|
|
||||||
|
// Check if email is verified
|
||||||
$stmt = $this->conn->prepare("SELECT email, email_verification_token, email_verification_token_timestamp
|
$stmt = $this->conn->prepare("SELECT email, email_verification_token, email_verification_token_timestamp
|
||||||
FROM users
|
FROM users
|
||||||
WHERE uuid=:uuid;");
|
WHERE uuid=:uuid;");
|
||||||
|
@ -297,6 +303,7 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if new verification can be sent
|
||||||
$minutes_since_last_verify_email =
|
$minutes_since_last_verify_email =
|
||||||
(new DateTime("@{$user["email_verification_token_timestamp"]}"))->diff(new DateTime(), absolute: true)->i;
|
(new DateTime("@{$user["email_verification_token_timestamp"]}"))->diff(new DateTime(), absolute: true)->i;
|
||||||
if ($minutes_since_last_verify_email < self::MINUTES_BETWEEN_VERIFICATION_EMAILS) {
|
if ($minutes_since_last_verify_email < self::MINUTES_BETWEEN_VERIFICATION_EMAILS) {
|
||||||
|
@ -313,6 +320,7 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue verification email
|
||||||
$stmt = $this->conn->prepare("UPDATE users
|
$stmt = $this->conn->prepare("UPDATE users
|
||||||
SET email_verification_token_timestamp=unixepoch()
|
SET email_verification_token_timestamp=unixepoch()
|
||||||
WHERE uuid=:uuid;");
|
WHERE uuid=:uuid;");
|
||||||
|
@ -336,7 +344,7 @@ class UserManager
|
||||||
{
|
{
|
||||||
$this->conn->beginTransaction();
|
$this->conn->beginTransaction();
|
||||||
|
|
||||||
|
// Check if token is correct for email
|
||||||
$stmt = $this->conn->prepare("SELECT email_verification_token_timestamp
|
$stmt = $this->conn->prepare("SELECT email_verification_token_timestamp
|
||||||
FROM users
|
FROM users
|
||||||
WHERE email=:email AND email_verification_token=:token;");
|
WHERE email=:email AND email_verification_token=:token;");
|
||||||
|
@ -357,6 +365,7 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if token is still valid
|
||||||
$token_timestamp = $results[0]["email_verification_token_timestamp"];
|
$token_timestamp = $results[0]["email_verification_token_timestamp"];
|
||||||
$minutes_since_creation = (new DateTime("@$token_timestamp"))->diff(new DateTime(), absolute: true)->i;
|
$minutes_since_creation = (new DateTime("@$token_timestamp"))->diff(new DateTime(), absolute: true)->i;
|
||||||
if ($minutes_since_creation > self::MINUTES_VALID_VERIFICATION) {
|
if ($minutes_since_creation > self::MINUTES_VALID_VERIFICATION) {
|
||||||
|
@ -370,6 +379,7 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set as verified
|
||||||
$stmt = $this->conn->prepare("UPDATE users SET email_verification_token=null WHERE email=:email;");
|
$stmt = $this->conn->prepare("UPDATE users SET email_verification_token=null WHERE email=:email;");
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
@ -419,7 +429,7 @@ class UserManager
|
||||||
password_reset_token=null
|
password_reset_token=null
|
||||||
WHERE uuid=:uuid;");
|
WHERE uuid=:uuid;");
|
||||||
$stmt->bindValue(":uuid", $uuid);
|
$stmt->bindValue(":uuid", $uuid);
|
||||||
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_DEFAULT));
|
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_BCRYPT));
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// Respond
|
// Respond
|
||||||
|
@ -491,9 +501,9 @@ class UserManager
|
||||||
* @return Response a satisfied `Response` with payload `null` if the password reset token is currently valid, or an
|
* @return Response a satisfied `Response` with payload `null` if the password reset token is currently valid, or an
|
||||||
* unsatisfied `Response` otherwise
|
* unsatisfied `Response` otherwise
|
||||||
*/
|
*/
|
||||||
public function validate_password_reset_token(string $email, string $token): Response
|
public function validate_password_reset_token(string $email, string $token, bool $use_transaction = true): Response
|
||||||
{
|
{
|
||||||
$this->conn->beginTransaction();
|
if ($use_transaction) $this->conn->beginTransaction();
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("SELECT password_reset_token_timestamp
|
$stmt = $this->conn->prepare("SELECT password_reset_token_timestamp
|
||||||
FROM users WHERE email=:email AND password_reset_token=:token;");
|
FROM users WHERE email=:email AND password_reset_token=:token;");
|
||||||
|
@ -502,7 +512,7 @@ class UserManager
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if (sizeof($results) === 0) {
|
if (sizeof($results) === 0) {
|
||||||
$this->conn->rollBack();
|
if ($use_transaction) $this->conn->rollBack();
|
||||||
return new Response(
|
return new Response(
|
||||||
payload: [
|
payload: [
|
||||||
"target" => null,
|
"target" => null,
|
||||||
|
@ -515,7 +525,7 @@ class UserManager
|
||||||
$token_timestamp = $results[0]["password_reset_token_timestamp"];
|
$token_timestamp = $results[0]["password_reset_token_timestamp"];
|
||||||
$minutes_since_creation = (new DateTime("@$token_timestamp"))->diff(new DateTime(), absolute: true)->i;
|
$minutes_since_creation = (new DateTime("@$token_timestamp"))->diff(new DateTime(), absolute: true)->i;
|
||||||
if ($minutes_since_creation > self::MINUTES_VALID_PASSWORD_RESET) {
|
if ($minutes_since_creation > self::MINUTES_VALID_PASSWORD_RESET) {
|
||||||
$this->conn->rollBack();
|
if ($use_transaction) $this->conn->rollBack();
|
||||||
return new Response(
|
return new Response(
|
||||||
payload: [
|
payload: [
|
||||||
"target" => null,
|
"target" => null,
|
||||||
|
@ -525,7 +535,7 @@ class UserManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->conn->commit();
|
if ($use_transaction) $this->conn->commit();
|
||||||
return new Response(payload: null, satisfied: true);
|
return new Response(payload: null, satisfied: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,35 +554,9 @@ class UserManager
|
||||||
{
|
{
|
||||||
$this->conn->beginTransaction();
|
$this->conn->beginTransaction();
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("SELECT password_reset_token_timestamp
|
$token_is_valid = $this->validate_password_reset_token($email, $token, false);
|
||||||
FROM users WHERE email=:email AND password_reset_token=:token;");
|
if (!$token_is_valid->satisfied)
|
||||||
$stmt->bindValue(":email", $email);
|
return $token_is_valid;
|
||||||
$stmt->bindValue(":token", $token);
|
|
||||||
$stmt->execute();
|
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if (sizeof($results) === 0) {
|
|
||||||
$this->conn->rollBack();
|
|
||||||
return new Response(
|
|
||||||
payload: [
|
|
||||||
"target" => null,
|
|
||||||
"message" => "This password reset link is invalid. Maybe you already reset your password?"
|
|
||||||
],
|
|
||||||
satisfied: false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$token_timestamp = $results[0]["password_reset_token_timestamp"];
|
|
||||||
$minutes_since_creation = (new DateTime("@$token_timestamp"))->diff(new DateTime(), absolute: true)->i;
|
|
||||||
if ($minutes_since_creation > self::MINUTES_VALID_PASSWORD_RESET) {
|
|
||||||
$this->conn->rollBack();
|
|
||||||
return new Response(
|
|
||||||
payload: [
|
|
||||||
"target" => null,
|
|
||||||
"message" => "This password reset link has expired."
|
|
||||||
],
|
|
||||||
satisfied: false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($password_new !== $password_confirm) {
|
if ($password_new !== $password_confirm) {
|
||||||
$this->conn->rollBack();
|
$this->conn->rollBack();
|
||||||
|
@ -583,10 +567,9 @@ class UserManager
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $this->conn->prepare("UPDATE users
|
$stmt = $this->conn->prepare("UPDATE users
|
||||||
SET password=:password,
|
SET password=:password, password_reset_token=null
|
||||||
password_reset_token=null
|
|
||||||
WHERE email=:email;");
|
WHERE email=:email;");
|
||||||
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_DEFAULT));
|
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_BCRYPT));
|
||||||
$stmt->bindValue(":email", $email);
|
$stmt->bindValue(":email", $email);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue