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|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|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"]; } }