Compare commits

...

4 Commits
v1.1.1 ... main

Author SHA1 Message Date
Florine W. Dekker d7c2658ba6
Resolve error in default ini 2023-09-04 15:04:08 +02:00
Florine W. Dekker 2daa74b319
Make user agent customisable
And fix some instructions in the README, and put config settings between quotes where applicable.
2023-09-04 14:54:45 +02:00
Florine W. Dekker 6a1be5ac2f
Use Argon2 instead of bcrypt
Passwords currently stored under bcrypt are not automatically converted. Argon2 is only used for new passwords from this moment on.
2023-08-30 22:35:35 +02:00
Florine W. Dekker 7b86673590
Update dependencies and ensure Hitler is dead
Hitler's category was changed from "1945 deaths" to "1945 suicides", resulting in Death Notifier not detecting that Hitler was dead (or even a person). Death Notifier now recognises the "<year> suicides" category.
2023-08-30 22:31:05 +02:00
20 changed files with 92 additions and 62 deletions

View File

@ -1,7 +1,7 @@
const path = require("path");
module.exports = grunt => {
const testTarget = grunt.option("test-target") || ".";
const testFilter = grunt.option("test-filter") || "'' .";
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
@ -71,7 +71,7 @@ module.exports = grunt => {
exec: "composer.phar install --no-dev"
},
phpunit: {
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --testdox ${testTarget}`
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --filter ${testFilter}`
},
stan: {
exec: "vendor/bin/phpstan analyse -l 8 src/main src/test"

View File

@ -9,9 +9,10 @@ This tool regularly checks if people are still alive according to Wikipedia, and
## Development
### Requirements
* PHP 8.1+ (i.e. `apt install php php-cgi`)
* PHP 8.1+ (i.e. `apt install php php-cgi`) (compiled with Argon2 support)
* [PHP cURL](https://www.php.net/manual/en/book.curl.php) (i.e. `apt install php-curl`)
* [PHP DOM](https://www.php.net/manual/en/book.dom.php) (i.e. `apt install php-dom`)
* [PHP mbstring](https://www.php.net/manual/en/book.mbstring.php) (i.e. `apt install php-mbstring`)
* [PHP SQLite 3](https://www.php.net/manual/en/book.sqlite3.php) (i.e. `apt install php-sqlite3`)
* [composer](https://getcomposer.org/) (make sure `composer.phar` is on your path)
* [npm](https://www.npmjs.com/)
@ -45,7 +46,10 @@ composer.phar update
npm install
```
### Static analysis
### Static analysis and tests
Note that PHPUnit suppresses output from `print`.
Instead, you can use `fwrite(STDERR, print_r("my message", TRUE));`.
* Run static analysis
```shell
npm run analyze
@ -54,10 +58,24 @@ npm install
```shell
npm run test
```
* Run all tests in package
* Run only select tests
```shell
npm run test -- --test-target=com/fwdekker/deathnotifier/wikipedia/
npm run test -- --test-filter="test-name file-name"
```
Note that a `test-name` of `''` and a `file-name` of `.` matches all tests.
* Run all tests in package
```shell
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/"
```
* Run all tests in class
```shell
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php"
```
* Run only specific test
```shell
npm run test -- --test-filter="test_query_detects_dead_person ."
```
* Run static analysis and tests
```shell
npm run check
@ -77,12 +95,12 @@ Inside the installation directory, create `config.ini.php` and use it to overrid
Make sure only the user that runs PHP can read/write `config.ini.php`.
### Cron jobs
You should run the `process-email-queue` and `update-all-trackings` actions regularly;
You should run the `process-email-queue` and `update-trackings` actions regularly;
recommended is every minute and every five minutes, respectively.
For example, you can add the following lines to your crontab (e.g. using `sudo -u www crontab -e`):
```
* * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=process-email-queue password=secret_password
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-all-trackings password=secret_password
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-trackings password=secret_password
```
Replace `secret_password` with the password you configured in `config.ini.php`.

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "1.1.1", "_comment_version": "Also update version in `package.json`!",
"version": "1.3.0", "_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",
@ -19,12 +19,12 @@
"composer/semver": "^3.3",
"ext-curl": "*",
"ext-pdo": "*",
"monolog/monolog": "^3.3",
"monolog/monolog": "^3.4",
"phpmailer/phpmailer": "^6.8"
},
"require-dev": {
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^9.5"
"phpstan/phpstan": "^1.10.32",
"phpunit/phpunit": "^10.3.2"
},
"autoload": {
"psr-4": {

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "1.1.1", "_comment_version": "Also update version in `composer.json`!",
"version": "1.3.0", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -1,39 +1,37 @@
;<?php exit(); ?>
[admin]
# bcrypt hash of password to use the CLI of `api.php`. If set to its default value, or if empty, the CLI is disabled.
cli_password = REPLACE THIS WITH A SECRET VALUE
# PHC-formatted hash of password for the CLI of `api.php`. You can create one using PHP's `password_hash`. Escaping
# dollar symbols is optional. If set to its default value, or if empty, the CLI is disabled.
cli_password = "REPLACE THIS WITH A SECRET VALUE"
[database]
# File to store SQLite database in. The default value assumes that you have configured your server to hide files
# starting with a `.`.
filename = .death-notifier.db
# File to store SQLite database in.
filename = ".death-notifier.db"
[logger]
# File to store general logs in. The default value assumes that you have configured your server to hide files starting
# with a `.`.
filename = .death-notifier.log
# File to store general logs in.
filename = ".death-notifier.log"
# Log level for general log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
level = 250
[logger_db]
# File to store database logs in. The default value assumes that you have configured your server to hide files starting
# with a `.`.
filename = .death-notifier.db.log
# File to store database logs in.
filename = ".death-notifier.db.log"
# Log level for database log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
level = 250
[mail]
# Host name of SMTP server to send mail through.
host = TODO
host = "TODO"
# Port of SMTP server to send mail through.
port = TODO
# Username to authenticate with at SMTP server.
username = TODO
username = "TODO"
# Password to authenticate with at SMTP server.
password = TODO
password = "TODO"
# Name to show to recipient.
from_name = TODO
from_name = "TODO"
[security]
# `true` if and only if insecure file permissions for config files should be tolerated.
@ -41,8 +39,13 @@ allow_config_insecure_permissions = false
[server]
# The path to the directory containing the site's main page.
base_path = https://example.com/death-notifier/
base_path = "https://example.com/death-notifier/"
# The path at which users can report bugs, or an empty string if there is no such path.
issue_path =
issue_path = ""
# The message to display at the top of all pages. A blank string hides the message.
global_message =
global_message = ""
[wikipedia]
# Contact information to include in requests to Wikipedia's API. Typically a URL and an email address separated by a
# semicolon. See also https://www.mediawiki.org/wiki/API:Etiquette.
user_agent_contact = "https://example.com/death-notifier; name@example.com"

View File

@ -89,7 +89,7 @@ class ProcessEmailQueueAction extends Action
$mailer->SMTPKeepAlive = true;
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mailer->Host = $config["host"];
$mailer->Port = $config["port"];
$mailer->Port = intval($config["port"]);
$mailer->Username = $config["username"];
$mailer->Password = $config["password"];
try {

View File

@ -104,7 +104,7 @@ class UserList
VALUES (:email, :password)
RETURNING email_verification_token;");
$stmt->bindValue(":email", $email);
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
$stmt->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
}
@ -265,11 +265,12 @@ class UserList
public function set_password(string $uuid, string $password): void
{
$stmt = $this->database->conn->prepare("UPDATE users
SET password=:password, password_last_change=unixepoch(),
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->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
$stmt->execute();
}

View File

@ -2,7 +2,7 @@
namespace com\fwdekker\deathnotifier\wikipedia;
use com\fwdekker\deathnotifier\LoggerUtil;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\Util;
use JsonException;
@ -19,9 +19,7 @@ class Wikipedia
/**
* The user agent used to represent the death notifier to Wikipedia.
*/
private const USER_AGENT =
"death-notifier/%%VERSION_NUMBER%% " .
"(https://git.fwdekker.com/tools/death-notifier; florine@fwdekker.com)";
private const USER_AGENT_FORMAT = "death-notifier/%%VERSION_NUMBER%% (%1\$s) %2\$s";
/**
* Number of articles to query per query.
*/
@ -48,10 +46,13 @@ class Wikipedia
*/
private function api_fetch(array $params): mixed
{
$curl_version = curl_version()["version"] ?? "unknown";
$user_agent = sprintf(self::USER_AGENT_FORMAT, Config::get("wikipedia")["user_agent_contact"], $curl_version);
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, self::API_URL . http_build_query($params));
curl_setopt($ch, CURLOPT_USERAGENT, self::USER_AGENT);
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
$output = curl_exec($ch);
curl_close($ch);
@ -262,7 +263,7 @@ class Wikipedia
return null;
$category_titles = array_column($article["categories"], "title");
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?deaths|Year of death (missing|unknown))$/";
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?(deaths|suicides)|Year of death (missing|unknown))$/";
if (!empty(array_filter($category_titles, fn($it) => preg_match($dead_regex, $it))))
return PersonStatus::Dead;

View File

@ -44,9 +44,9 @@ class EqualsCliPasswordRuleTest extends TestCase
* Returns the test cases.
*
* @return array<string, array{string|null, string|null, class-string<Throwable>|null, string|null}> the test cases
* @see RuleTest::test_check()
* @see RuleTestTemplate::test_check()
*/
public function check_provider(): array
public static function check_provider(): array
{
$hash = "\$2y\$04\$fwXTw7Rjzw0EpU094u4agOBaBNqtCHGc4TMoxfbPrxuqO5tpYyRka"; # Hash of "password"

View File

@ -8,9 +8,9 @@ use InvalidArgumentException;
/**
* Unit tests for {@see EqualsRule}.
*/
class EqualsRuleTest extends RuleTest
class EqualsRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;
$value = InvalidValueException::class;

View File

@ -8,9 +8,9 @@ use InvalidArgumentException;
/**
* Unit tests for {@see HasStringLengthRule}.
*/
class HasStringLengthRuleTest extends RuleTest
class HasStringLengthRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;
$value = InvalidValueException::class;

View File

@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
/**
* Unit tests for {@see IsBooleanRule}.
*/
class IsBooleanRuleTest extends RuleTest
class IsBooleanRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;

View File

@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
/**
* Unit tests for {@see IsEmailRule}.
*/
class IsEmailRuleTest extends RuleTest
class IsEmailRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;
$value = InvalidValueException::class;

View File

@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
/**
* Unit tests for {@see IsNotBlankRule}.
*/
class IsNotBlankRuleTest extends RuleTest
class IsNotBlankRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;
$value = InvalidValueException::class;

View File

@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
/**
* Unit tests for {@see IsStringRule}.
*/
class IsStringRuleTest extends RuleTest
class IsStringRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;

View File

@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
/**
* Unit tests for {@see IsValidCsrfTokenRule}.
*/
class IsValidCsrfTokenRuleTest extends RuleTest
class IsValidCsrfTokenRuleTest extends RuleTestTemplate
{
public function check_provider(): array
public static function check_provider(): array
{
$type = InvalidTypeException::class;

View File

@ -9,7 +9,7 @@ use Throwable;
/**
* Unit tests for {@see Rule} implementations.
*/
abstract class RuleTest extends TestCase
abstract class RuleTestTemplate extends TestCase
{
/**
* Tests the output of {@see Rule::check()}.
@ -40,7 +40,7 @@ abstract class RuleTest extends TestCase
* Returns the test cases.
*
* @return array<string, array{Rule, mixed|null, class-string<Throwable>|null, string|null}> the test cases
* @see RuleTest::test_check()
* @see RuleTestTemplate::test_check()
*/
abstract public function check_provider(): array;
abstract public static function check_provider(): array;
}

View File

@ -135,6 +135,13 @@ class WikipediaTest extends TestCase
}
public function test_query_detects_dead_person(): void
{
$output = $this->wikipedia->query_people_info(["Sophie (musician)"], resolve_moves: false);
self::assertEquals(PersonStatus::Dead, $output->results["Sophie (musician)"]["status"]);
}
public function test_query_detects_suicide_person(): void
{
$output = $this->wikipedia->query_people_info(["Adolf Hitler"], resolve_moves: false);
@ -150,8 +157,8 @@ class WikipediaTest extends TestCase
public function test_query_detects_possibly_living_person(): void
{
$output = $this->wikipedia->query_people_info(["Judge Edward Aaron"], resolve_moves: false);
$output = $this->wikipedia->query_people_info(["Angela Anderes"], resolve_moves: false);
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Judge Edward Aaron"]["status"]);
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Angela Anderes"]["status"]);
}
}