From 875b9ece501de5937993c41a83c44f8ea59897d0 Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Fri, 10 Apr 2026 22:38:59 +0200 Subject: register and login to Cinny client --- Dockerfile | 29 ++++++ bin/create-user | 24 +++++ matrix-specification/Data/PushCondition.php | 56 +++++++++++ matrix-specification/Data/PushRule.php | 32 ++++++ matrix-specification/Data/Ruleset.php | 33 +++++++ matrix-specification/Enums/PushConditionKind.php | 17 ++++ .../Responses/ClientRegisterPostResponse.php | 8 +- src/Controllers/Client/ClientController.php | 108 ++++++++++++++++++++- src/Models/User.php | 2 +- src/Router.php | 2 +- 10 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 Dockerfile create mode 100755 bin/create-user create mode 100644 matrix-specification/Data/PushCondition.php create mode 100644 matrix-specification/Data/PushRule.php create mode 100644 matrix-specification/Data/Ruleset.php create mode 100644 matrix-specification/Enums/PushConditionKind.php diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d481b5a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM composer/composer:latest-bin AS composer + +WORKDIR /app + +COPY composer.* /app + +RUN composer install \ + --no-interaction \ + --no-plugins \ + --no-scripts \ + --no-dev \ + && composer dump-autoload -o + + +FROM php:fpm-alpine + +RUN : \ + && apk add libpq-dev icu-dev icu-data-full \ + && docker-php-ext-install pdo_pgsql intl + +WORKDIR /usr/src/app + +COPY --from=composer /app/vendor /usr/src/app/vendor + +COPY src /usr/src/app/src +COPY public /usr/src/app/public +COPY migrations /usr/src/app/migrations +COPY bin/db-migrate /usr/src/app/bin/db-migrate +COPY matrix-specification /usr/src/app/matrix-specification diff --git a/bin/create-user b/bin/create-user new file mode 100755 index 0000000..02cb7da --- /dev/null +++ b/bin/create-user @@ -0,0 +1,24 @@ +#!/usr/bin/env php + + [ + "header" => ["Content-Type: application/json"], + "method" => "POST", + "content" => json_encode([ + "device_id" => "matrix-php-cli", + "inhibit_login" => true, + "password" => $options["password"], + "username" => $options["username"], + ]), + ] +])); diff --git a/matrix-specification/Data/PushCondition.php b/matrix-specification/Data/PushCondition.php new file mode 100644 index 0000000..ca94de9 --- /dev/null +++ b/matrix-specification/Data/PushCondition.php @@ -0,0 +1,56 @@ + $this->is, + "key" => $this->key, + "kind" => $this->kind, + "pattern" => $this->pattern, + "value" => $this->value, + ]; + } +} diff --git a/matrix-specification/Data/PushRule.php b/matrix-specification/Data/PushRule.php new file mode 100644 index 0000000..2217a78 --- /dev/null +++ b/matrix-specification/Data/PushRule.php @@ -0,0 +1,32 @@ + $actions + * @param PushCondition[] $conditions + */ + public function __construct( + private array $actions, + private bool $default, + private bool $enabled, + private string $ruleId, + private ?array $conditions = null, + private ?string $pattern = null, + ) + {} + + public function jsonSerialize(): array + { + return [ + "actions" => $this->actions, + "conditions" => $this->conditions, + "default" => $this->default, + "enabled" => $this->enabled, + "pattern" => $this->pattern, + "rule_id" => $this->ruleId, + ]; + } +} diff --git a/matrix-specification/Data/Ruleset.php b/matrix-specification/Data/Ruleset.php new file mode 100644 index 0000000..af75579 --- /dev/null +++ b/matrix-specification/Data/Ruleset.php @@ -0,0 +1,33 @@ + $this->content, + "override" => $this->override, + "room" => $this->room, + "sender" => $this->sender, + "underride" => $this->underride, + ]; + } +} diff --git a/matrix-specification/Enums/PushConditionKind.php b/matrix-specification/Enums/PushConditionKind.php new file mode 100644 index 0000000..fe61c16 --- /dev/null +++ b/matrix-specification/Enums/PushConditionKind.php @@ -0,0 +1,17 @@ +value; + } +} diff --git a/matrix-specification/Responses/ClientRegisterPostResponse.php b/matrix-specification/Responses/ClientRegisterPostResponse.php index 6ed65ce..2b6dabe 100644 --- a/matrix-specification/Responses/ClientRegisterPostResponse.php +++ b/matrix-specification/Responses/ClientRegisterPostResponse.php @@ -17,12 +17,16 @@ class ClientRegisterPostResponse extends Response ) {} - public function validateRequired(ClientRegisterPostRequest $request): void + public function validateRequired(/*?ClientRegisterPostRequest $request*/): void { + /* if (is_null($request)) { + return; + } + $requestBody = $request->getBody(); if ($requestBody["inhibit_login"] === false) { # TODO: validate - } + } */ } public function getBody(): array diff --git a/src/Controllers/Client/ClientController.php b/src/Controllers/Client/ClientController.php index 053d288..c9eb1fa 100644 --- a/src/Controllers/Client/ClientController.php +++ b/src/Controllers/Client/ClientController.php @@ -4,11 +4,14 @@ namespace App\Controllers\Client; use App\Database; use App\Errors\AppException; +use App\Errors\ErrorResponse; +use App\Errors\UnauthorizedError; use App\Errors\UnknownError; use App\Models\Device; use App\Models\RoomEvent; use App\Models\Tokens; use App\Models\User; +use App\Support\Id; use App\Support\Logger; use App\Support\Parser; use App\Support\RequestValidator; @@ -16,6 +19,8 @@ use Matrix\Data\AccountData; use Matrix\Data\DeviceLists; use Matrix\Data\LoginFlow; use Matrix\Data\Presence; +use Matrix\Data\PushCondition; +use Matrix\Data\PushRule; use Matrix\Data\Room\Ephemeral; use Matrix\Data\Room\JoinedRoom; use Matrix\Data\Room\RoomSummary; @@ -23,11 +28,14 @@ use Matrix\Data\Room\Rooms; use Matrix\Data\Room\State; use Matrix\Data\Room\Timeline; use Matrix\Data\Room\UnreadNotificationCounts; +use Matrix\Data\Ruleset; use Matrix\Data\ToDevice; +use Matrix\Enums\AuthenticationType; use Matrix\Enums\ErrorCode; use Matrix\Enums\LoginType; use Matrix\Enums\MembershipState; use Matrix\Enums\PresenceState; +use Matrix\Enums\PushConditionKind; use Matrix\Enums\UserRegistrationKind; use Matrix\Events\PresenceEvent; use Matrix\Responses\ClientLoginGetResponse; @@ -131,6 +139,21 @@ class ClientController $body = json_decode($request->getContent(), true); RequestValidator::validateJson(); + if (empty($body)) { + return new JsonResponse([ + "completed" => [], + "flows" => [ + [ + "stages" => [ + AuthenticationType::DUMMY, + ], + ], + ], + "params" => [], + "session" => Id::generate(), + ], Response::HTTP_UNAUTHORIZED); + } + // validate kind $kind = null; try { @@ -139,11 +162,24 @@ class ClientController throw new UnknownError("Bad registration kind.", Response::HTTP_BAD_REQUEST); } - $username = $body["username"]; + $username = $body["username"] ?? ""; + if (empty($username)) { + $username = crc32(time() . random_bytes(64)); + } + $userId = "@$username:$_ENV[DOMAIN]"; - Database::getInstance()->query("insert into users (id, password) values (:id, :password)", [ + if (empty($body["password"])) { + throw new AppException(ErrorCode::MISSING_PARAM, "missing password", Response::HTTP_BAD_REQUEST); + } + + if (Database::getInstance()->query("select id from users where id=:id", ["id" => $userId])->fetchColumn()) { + throw new AppException(ErrorCode::USER_IN_USE, "The desired user ID is already taken.", Response::HTTP_BAD_REQUEST); + } + + Database::getInstance()->query("insert into users (id, name, password) values (:id, :name, :password)", [ "id" => $userId, + "name" => $username, "password" => $body["password"], ]); @@ -289,4 +325,72 @@ class ClientController refreshToken: $newTokens->getRefreshToken(), )); } + + #[Route(path: "/_matrix/client/v3/pushrules", methods: ["GET"])] + public function pushRules(Request $request): Response + { + $user = User::authenticateWithRequest($request); + + return new JsonResponse([ + "global" => new Ruleset( + content: [ + new PushRule( + actions: [ + "notify", + [ + "set_tweak" => "sound", + "value" => "default", + ], + [ + "set_tweak" => "highlight", + ], + ], + default: true, + enabled: true, + pattern: "alice", + ruleId: ".m.rule.contains_user_name", + ), + ], + + override: [ + new PushRule( + actions: [], + conditions: [], + default: true, + enabled: false, + ruleId: ".m.rule.master", + ), + ], + + room: [], + sender: [], + + underride: [ + new PushRule( + actions: [ + "notify", + [ + "set_tweak" => "sound", + "value" => "ring", + ], + [ + "set_tweak" => "highlight", + "value" => false, + ], + ], + conditions: [ + new PushCondition( + kind: PushConditionKind::EVENT_MATCH, + key: "type", + pattern: "m.call.invite", + ) + ], + default: true, + enabled: true, + ruleId: ".m.rule.master", + ), + ], + ), + ]); + } } diff --git a/src/Models/User.php b/src/Models/User.php index b24afaf..4c016ad 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -22,7 +22,7 @@ class User implements ConnectsToDatabase { return new self( $row["id"], - $row["name"] ?? "", + $row["name"], ); } diff --git a/src/Router.php b/src/Router.php index cda3006..ab0c37c 100644 --- a/src/Router.php +++ b/src/Router.php @@ -64,7 +64,7 @@ class Router try { $matcher = new UrlMatcher($this->routes, $context); - $match = $matcher->matchRequest($request); + $match = $matcher->match(rtrim($request->getPathInfo(), "/")); $class = $match["_controller"][0]; $method = $match["_controller"][1]; -- cgit v1.2.3