272 lines
9.9 KiB
PHP
272 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace com\fwdekker\deathnotifier\user;
|
|
|
|
use com\fwdekker\deathnotifier\Database;
|
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
|
use com\fwdekker\deathnotifier\mailer\MailManager;
|
|
use com\fwdekker\deathnotifier\Response;
|
|
use Monolog\Logger;
|
|
use PDO;
|
|
|
|
|
|
/**
|
|
* Manages interaction with the database in the context of users.
|
|
*/
|
|
class UserManager
|
|
{
|
|
/**
|
|
* The minimum length of a password;
|
|
*/
|
|
public const MIN_PASSWORD_LENGTH = 8;
|
|
/**
|
|
* The maximum length of a password.
|
|
*/
|
|
public const MAX_PASSWORD_LENGTH = 64;
|
|
/**
|
|
* The minimum number of minutes between two verification emails.
|
|
*/
|
|
public const MINUTES_BETWEEN_VERIFICATION_EMAILS = 5;
|
|
/**
|
|
* The maximum number of minutes after which a validation email expires.
|
|
*/
|
|
public const MINUTES_VALID_VERIFICATION = 60;
|
|
/**
|
|
* The minimum number of minutes between two password reset emails.
|
|
*/
|
|
public const MINUTES_BETWEEN_PASSWORD_RESETS = 5;
|
|
/**
|
|
* The maximum number of minutes after which a password reset email expires.
|
|
*/
|
|
public const MINUTES_VALID_PASSWORD_RESET = 60;
|
|
|
|
|
|
/**
|
|
* @var Logger the logger to use for logging
|
|
*/
|
|
private Logger $logger; // @phpstan-ignore-line Unused, but useful for debugging
|
|
/**
|
|
* @var PDO the database connection to interact with
|
|
*/
|
|
private PDO $conn;
|
|
/**
|
|
* @var MailManager the mailer to send emails with
|
|
*/
|
|
private MailManager $mailer;
|
|
|
|
|
|
/**
|
|
* Constructs a new user manager.
|
|
*
|
|
* @param PDO $conn the database connection to interact with
|
|
* @param MailManager $mailer the mailer to send emails with
|
|
*/
|
|
public function __construct(PDO $conn, MailManager $mailer)
|
|
{
|
|
$this->logger = LoggerUtil::with_name($this::class);
|
|
$this->conn = $conn;
|
|
$this->mailer = $mailer;
|
|
}
|
|
|
|
|
|
/**
|
|
* Populates the database with the necessary structures for users.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function install(): void
|
|
{
|
|
$this->conn->exec("CREATE TABLE users(uuid TEXT NOT NULL UNIQUE PRIMARY KEY DEFAULT(lower(hex(randomblob(16)))),
|
|
email TEXT NOT NULL UNIQUE,
|
|
email_verification_token TEXT DEFAULT(lower(hex(randomblob(16)))),
|
|
email_verification_token_timestamp INT NOT NULL DEFAULT(unixepoch()),
|
|
email_notifications_enabled INT NOT NULL DEFAULT(1),
|
|
password TEXT NOT NULL,
|
|
password_last_change INT NOT NULL DEFAULT(unixepoch()),
|
|
password_reset_token TEXT DEFAULT(null),
|
|
password_reset_token_timestamp INT NOT NULL DEFAULT(unixepoch()));");
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers a new user.
|
|
*
|
|
* Throws an exception if a user with the given email address is already registered.
|
|
*
|
|
* @param string $email the user's email address
|
|
* @param string $password the user's password
|
|
* @return string the user's UUID
|
|
*/
|
|
public function add_user(string $email, string $password): string
|
|
{
|
|
$stmt = $this->conn->prepare("INSERT INTO users (email, password)
|
|
VALUES (:email, :password)
|
|
RETURNING uuid;");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["uuid"];
|
|
}
|
|
|
|
/**
|
|
* Returns `true` if and only if a user exists with the given UUID.
|
|
*
|
|
* @param string $uuid the UUID to check existence of
|
|
* @return bool `true` if and only if a user exists with the given UUID
|
|
*/
|
|
public function has_user_with_uuid(string $uuid): bool
|
|
{
|
|
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE uuid=:uuid);");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->execute();
|
|
return $stmt->fetch()[0] === 1;
|
|
}
|
|
|
|
/**
|
|
* Returns `true` if and only if a user exists with the given email address.
|
|
*
|
|
* @param string $email the email address to check existence of
|
|
* @return bool `true` if and only if a user exists with the given email address
|
|
*/
|
|
public function has_user_with_email(string $email): bool
|
|
{
|
|
$stmt = $this->conn->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE email=:email);");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->execute();
|
|
return $stmt->fetch()[0] === 1;
|
|
}
|
|
|
|
/**
|
|
* Removes the user with the given UUID.
|
|
*
|
|
* @param string $uuid the UUID of the user to remove
|
|
* @return void
|
|
*/
|
|
public function remove_user_by_uuid(string $uuid): void
|
|
{
|
|
$stmt = $this->conn->prepare("DELETE FROM users WHERE uuid=:uuid;");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->execute();
|
|
}
|
|
|
|
/**
|
|
* Returns all data of the user with the given UUID.
|
|
*
|
|
* @param string $uuid the UUID of the user to return the data of
|
|
* @return array<string, mixed>|null all data of the user with the given UUID, or `null` if the user could not be
|
|
* found
|
|
*/
|
|
public function get_user_by_uuid(string $uuid): ?array
|
|
{
|
|
$stmt = $this->conn->prepare("SELECT * FROM users WHERE uuid=:uuid;");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->execute();
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
return empty($results) ? null : $results[0];
|
|
}
|
|
|
|
/**
|
|
* Returns all data of the user with the given email address.
|
|
*
|
|
* @param string $email the email address of the user to return the data of
|
|
* @return array<string, mixed>|null all data of the user with the given email address
|
|
*/
|
|
public function get_user_by_email(string $email): ?array
|
|
{
|
|
$stmt = $this->conn->prepare("SELECT * FROM users WHERE email=:email;");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->execute();
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
return empty($results) ? null : $results[0];
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the email address of the user with the given UUID, and resets the email verification token.
|
|
*
|
|
* Settings the email address to the current value resets the email verification token.
|
|
*
|
|
* @param string $uuid the UUID of the user whose email address should be updated
|
|
* @param string $email the new email address
|
|
* @return string the verification token for the updated email address
|
|
*/
|
|
public function set_email(string $uuid, string $email): string
|
|
{
|
|
$stmt = $this->conn->prepare("UPDATE users
|
|
SET email=:email,
|
|
email_verification_token=lower(hex(randomblob(16))),
|
|
email_verification_token_timestamp=unixepoch()
|
|
WHERE uuid=:uuid
|
|
RETURNING email_verification_token;");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
|
}
|
|
|
|
/**
|
|
* Sets the user's email address as verified.
|
|
*
|
|
* @param string $uuid the UUID of the user whose email address has been verified
|
|
* @return void
|
|
*/
|
|
public function set_email_verified(string $uuid): void
|
|
{
|
|
$stmt = $this->conn->prepare("UPDATE users SET email_verification_token=null WHERE uuid=:uuid;");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->execute();
|
|
}
|
|
|
|
/**
|
|
* Sets whether email notifications are enabled for the given user.
|
|
*
|
|
* @param string $uuid the UUID of the user whose notifications should be enabled or disabled
|
|
* @param bool $enabled `true` if notifications should be enabled, `false` if notifications should be disabled
|
|
* @return void
|
|
*/
|
|
public function set_notifications_enabled(string $uuid, bool $enabled): void
|
|
{
|
|
$stmt = $this->conn->prepare("UPDATE users SET email_notifications_enabled=:enabled WHERE uuid=:uuid;");
|
|
$stmt->bindValue(":enabled", $enabled);
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->execute();
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the indicated user's password.
|
|
*
|
|
* @param string $uuid the UUID of the user whose password should be changed
|
|
* @param string $password the new password
|
|
* @return void
|
|
*/
|
|
public function set_password(string $uuid, string $password): void
|
|
{
|
|
$stmt = $this->conn->prepare("UPDATE users
|
|
SET password=:password, password_last_change=unixepoch(),
|
|
password_reset_token=null
|
|
WHERE uuid=:uuid;");
|
|
$stmt->bindValue(":uuid", $uuid);
|
|
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
|
$stmt->execute();
|
|
}
|
|
|
|
/**
|
|
* Generates and registers a password reset token for the user with the given email address.
|
|
*
|
|
* @param string $email the address for which a password reset token should be generated
|
|
* @return string the generate password reset token
|
|
*/
|
|
public function register_password_reset(string $email): string
|
|
{
|
|
$stmt = $this->conn->prepare("UPDATE users
|
|
SET password_reset_token=lower(hex(randomblob(16))),
|
|
password_reset_token_timestamp=unixepoch()
|
|
WHERE email=:email
|
|
RETURNING password_reset_token;");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["password_reset_token"];
|
|
}
|
|
}
|