summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Controllers/LoginController.php12
-rwxr-xr-xsrc/Controllers/RoomController.php154
-rw-r--r--src/Controllers/ServerDiscoveryController.php2
-rwxr-xr-xsrc/Controllers/SyncController.php168
-rwxr-xr-xsrc/Controllers/UserController.php27
-rw-r--r--src/Database.php8
-rw-r--r--src/Events/Event.php20
-rw-r--r--src/Events/PresenceEvent.php40
-rw-r--r--src/Events/RoomMemberEvent.php36
-rw-r--r--src/Events/RoomMessageEvent.php86
-rw-r--r--src/Events/RoomNameEvent.php33
-rw-r--r--src/Models/RoomEvent.php64
-rw-r--r--src/Models/Tokens.php29
-rw-r--r--src/Models/User.php35
-rw-r--r--src/Router/Router.php37
-rw-r--r--src/Router/routes_client_server.php19
-rw-r--r--src/Support/ArrayTransformable.php11
-rw-r--r--src/Support/Id.php21
-rw-r--r--src/Support/Logger.php1
-rw-r--r--src/Types/AuthenticationType.php14
-rw-r--r--src/Types/EventType.php11
-rw-r--r--src/Types/IdentifierType.php10
-rw-r--r--src/Types/LoginFlow.php27
-rw-r--r--src/Types/LoginType.php9
-rw-r--r--src/Types/MembershipState.php12
-rw-r--r--src/Types/MessageType.php8
-rw-r--r--src/Types/PresenceState.php10
-rw-r--r--src/Types/UserRegistrationKind.php9
28 files changed, 466 insertions, 447 deletions
diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php
index c520e25..9ae3a48 100644
--- a/src/Controllers/LoginController.php
+++ b/src/Controllers/LoginController.php
@@ -9,10 +9,12 @@ use App\Errors\UnknownError;
use App\Models\Device;
use App\Models\Tokens;
use App\Models\User;
+use App\Support\Logger;
+use App\Support\Parser;
use App\Support\RequestValidator;
-use App\Types\UserRegistrationKind;
use Matrix\Data\LoginFlow;
use Matrix\Enums\LoginType;
+use Matrix\Enums\UserRegistrationKind;
use Matrix\Responses\ClientLoginGetResponse;
use Matrix\Responses\ClientLoginPostResponse;
use Matrix\Responses\ClientRegisterPostResponse;
@@ -37,6 +39,8 @@ class LoginController
*/
public function login(Request $request): Response
{
+ Logger::logRequestToFile($request);
+
$body = json_decode($request->getContent(), true);
RequestValidator::validateJson();
@@ -49,7 +53,11 @@ class LoginController
}
// get user id
- $userId = $body["identifier"]["user"];
+ $userId = Parser::parseUser($body["identifier"]["user"]);
+ if (empty($userId["server"])) {
+ #$userId = "@$userId[username]:$_ENV[DOMAIN]";
+ $userId = "@$userId[username]:localhost";
+ }
#if ($loginType == LoginType::PASSWORD) {}
diff --git a/src/Controllers/RoomController.php b/src/Controllers/RoomController.php
index c02d5ce..dcc0415 100755
--- a/src/Controllers/RoomController.php
+++ b/src/Controllers/RoomController.php
@@ -7,18 +7,155 @@ use App\Errors\AppException;
use App\Errors\ErrorCode;
use App\Errors\UnauthorizedError;
use App\Events\RoomMessageEvent;
+use App\Models\RoomEvent;
use App\Models\User;
+use App\Support\Id;
use App\Support\Parser;
use App\Support\RequestValidator;
-use App\Types\EventType;
-use App\Types\MembershipState;
-use App\Types\MessageType;
+use Matrix\Enums\EventType;
+use Matrix\Enums\MembershipState;
+use Matrix\Enums\MessageType;
+use Matrix\Enums\RoomGuestAccess;
+use Matrix\Enums\RoomHistoryVisibility;
+use Matrix\Enums\RoomJoinRule;
+use Matrix\Enums\RoomVisibility;
+use Matrix\Events\Room\CreateEvent;
+use Matrix\Events\Room\GuestAccessEvent;
+use Matrix\Events\Room\HistoryVisibilityEvent;
+use Matrix\Events\Room\JoinRulesEvent;
+use Matrix\Events\Room\MemberEvent;
+use Matrix\Events\Room\NameEvent;
+use Matrix\Events\Room\PowerLevelsEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class RoomController
{
+ public function createRoom(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+ $body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
+
+ $creationContent = $body["creation_content"] ?? [];
+ $initialState = $body["initial_state"] ?? [];
+ $invite = $body["invite"] ?? [];
+ $body["invite_3pid"] ?? [];
+ $body["is_direct"] ?? false;
+ $name = $body["name"] ?? "";
+ $body["power_level_content_override"] ?? [];
+ $preset = $body["preset"] ?? "";
+ $roomAliasName = $body["room_alias_name"] ?? "";
+ $roomVersion = $body["room_version"] ?? "DEFAULT_ROOM_VERSION";
+ $topic = $body["topic"] ?? "";
+ $visibility = RoomVisibility::tryFrom($body["visibility"] ?? "private");
+
+ if (! $preset) {
+ $preset = $visibility->value . "_chat";
+ }
+
+ # TODO: get events for preset
+ # TODO: override preset events with initial state
+ # TODO: override events with name and topic if applicable
+ $state = [];
+
+ if ($name) {
+ $state[EventType::ROOM_NAME->value] = $name;
+ }
+
+ if ($topic) {
+ $state[EventType::ROOM_TOPIC->value] = $topic;
+ }
+
+ // create room
+ $roomId = Id::generateRoomId();
+ Database::getInstance()->query(<<<SQL
+ insert into rooms (id, name) values (:id, :name)
+ SQL, [
+ "id" => $roomId,
+ "name" => $roomAliasName, # "#$roomAliasName:$_ENV[DOMAIN]",
+ ]);
+
+ $roomCreateEvent = new RoomEvent(new CreateEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ roomVersion: "12",
+ ));
+ $roomCreateEvent->insert();
+
+ $roomMemberEvent = new RoomEvent(new MemberEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ stateKey: $user->getId(),
+ isDirect: false,
+ membership: MembershipState::JOIN,
+ displayName: $user->getName(),
+ ));
+ $roomMemberEvent->insert();
+
+ $roomPowerLevelsEvent = new RoomEvent(new PowerLevelsEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ ));
+ $roomPowerLevelsEvent->insert();
+
+ $roomJoinRulesEvent = new RoomEvent(new JoinRulesEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ joinRule: RoomJoinRule::INVITE,
+ ));
+ $roomJoinRulesEvent->insert();
+
+ $roomHistoryVisibilityEvent = new RoomEvent(new HistoryVisibilityEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ historyVisibility: RoomHistoryVisibility::SHARED,
+ ));
+ $roomHistoryVisibilityEvent->insert();
+
+ $roomGuestAccessEvent = new RoomEvent(new GuestAccessEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ guestAccess: RoomGuestAccess::CAN_JOIN,
+ ));
+ $roomGuestAccessEvent->insert();
+
+ $roomNameEvent = new RoomEvent(new NameEvent(
+ eventId: Id::generateEventId(),
+ originServerTimestamp: time(),
+ roomId: $roomId,
+ sender: $user->getId(),
+ name: $roomAliasName,
+ ));
+ $roomNameEvent->insert();
+
+ Database::getInstance()->query(<<<SQL
+ insert into room_memberships (room_id, user_id, state)
+ values (:room_id, :user_id, :state)
+ SQL, [
+ "room_id" => $roomId,
+ "user_id" => $user->getId(),
+ "state" => MembershipState::JOIN->value,
+ ]);
+
+ return new JsonResponse([
+ "room_id" => $roomId,
+ ]);
+ }
+
/**
* GET /_matrix/client/v3/directory/room/{roomAlias}
*
@@ -59,6 +196,17 @@ class RoomController
]);
}
+ public function readMarkers(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+ $body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
+
+ $roomId = $request->attributes->get("roomId");
+
+ return new JsonResponse();
+ }
+
/**
* PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}
*
diff --git a/src/Controllers/ServerDiscoveryController.php b/src/Controllers/ServerDiscoveryController.php
index f3b96b2..917df14 100644
--- a/src/Controllers/ServerDiscoveryController.php
+++ b/src/Controllers/ServerDiscoveryController.php
@@ -19,7 +19,7 @@ class ServerDiscoveryController
{
return new JsonResponse([
"m.homeserver" => [
- "base_url" => "https://$_ENV[DOMAIN]",
+ "base_url" => "http://$_ENV[DOMAIN]",
],
]);
}
diff --git a/src/Controllers/SyncController.php b/src/Controllers/SyncController.php
index 243fab5..acebb11 100755
--- a/src/Controllers/SyncController.php
+++ b/src/Controllers/SyncController.php
@@ -3,13 +3,23 @@
namespace App\Controllers;
use App\Database;
-use App\Errors\UnauthorizedError;
-use App\Events\PresenceEvent;
-use App\Events\RoomMemberEvent;
-use App\Events\RoomMessageEvent;
+use App\Models\RoomEvent;
use App\Models\User;
-use App\Types\MembershipState;
-use App\Types\PresenceState;
+use Matrix\Data\AccountData;
+use Matrix\Data\DeviceLists;
+use Matrix\Data\Presence;
+use Matrix\Data\Room\Ephemeral;
+use Matrix\Data\Room\JoinedRoom;
+use Matrix\Data\Room\RoomSummary;
+use Matrix\Data\Room\Rooms;
+use Matrix\Data\Room\State;
+use Matrix\Data\Room\Timeline;
+use Matrix\Data\Room\UnreadNotificationCounts;
+use Matrix\Data\ToDevice;
+use Matrix\Enums\MembershipState;
+use Matrix\Enums\PresenceState;
+use Matrix\Events\PresenceEvent;
+use Matrix\Responses\ClientSyncGetResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -24,99 +34,93 @@ class SyncController
*/
public function sync(Request $request): Response
{
- $accessToken = str_replace("Bearer ", "", $request->headers->get("authorization") ?: "");
- $user = User::fetchWithAccessToken($accessToken);
-
- if (empty($user)) {
- throw new UnauthorizedError();
- }
+ $user = User::authenticateWithRequest($request);
$filter = $request->query->get("filter", "");
$syncFullState = $request->query->get("full_state", false);
$setPresence = PresenceState::tryFrom($request->query->get("set_presence") ?? "") ?? PresenceState::ONLINE;
$since = $request->query->get("since", "");
$timeout = $request->query->get("timeout", 0);
+ $useStateAfter = $request->query->get("use_state_after", false);
+
+ if (! empty($filter)) {
+ if (str_starts_with($filter, "{")) {
+ $filter = json_decode($filter, true);
+ } else {
+ $filter = Database::getInstance()->query("select * from filters where id=:id", ["id" => $filter])->fetch();
+ }
+ }
$rooms = Database::getInstance()->query(<<<SQL
select * from rooms
- SQL)->fetchAll();
-
- $joinedRooms = new \stdClass();
- if (! empty($rooms)) {
- $joinedRooms = [];
- foreach ($rooms as $room) {
- $joinedRooms[$room["id"]] = [
- "account_data" => [
- "events" => [],
- ],
-
- "ephemeral" => [
- "events" => [],
- ],
-
- "state" => [
- "events" => [],
- ],
-
- "summary" => [
- "m.heroes" => [],
- "m.invited_member_count" => 0,
- "m.joined_member_count" => 1,
- ],
-
- "timeline" => [
- "events" => [
- (new RoomMemberEvent($user->getId(), $user->getId(), MembershipState::JOIN))->toJsonEncodeable(),
- (new RoomMessageEvent($user->getId(), "Hallo Test Nachricht"))->toJsonEncodeable(),
- ],
- "limited" => false,
- "prev_batch" => "",
- ],
-
- "unread_notifications" => [
- "highlight_count" => random_int(0, 1),
- "notification_count" => random_int(0, 10),
- ],
-
- "unread_thread_notifications" => new \stdClass(),
- ];
+ left join room_memberships
+ on rooms.id = room_memberships.room_id
+ where room_memberships.user_id = :user_id
+ SQL, [
+ "user_id" => $user->getId(),
+ ])->fetchAll();
+
+ $invitedRooms = [];
+ $joinedRooms = [];
+ $knockedRooms = [];
+ $leftRooms = [];
+
+ foreach ($rooms as $room) {
+ $events = Database::getInstance()->query(<<<SQL
+ select * from room_events
+ where room_id = :room_id
+ SQL, [
+ "room_id" => $room["room_id"],
+ #"limit" => ($filter["room"]["timeline"]["limit"] ?? false) ? "limit " . $filter["room"]["timeline"]["limit"] : "",
+ ])->fetchAll();
+
+ if ($since === "" && MembershipState::tryFrom($room["state"]) === MembershipState::JOIN) {
+ $joinedRooms[$room["room_id"]] = new JoinedRoom(
+ accountData: new AccountData([]),
+ ephemeral: new Ephemeral([]),
+ state: new State([]),
+ summary: new RoomSummary(
+ heroes: [],
+ invitedMemberCount: 0,
+ joinedMemberCount: 1,
+ ),
+ timeline: new Timeline(
+ events: array_map([RoomEvent::class, "transformEvent"], $events),
+ limited: false,# $filter["room"]["timeline"]["limit"] ?? false,
+ previousBatch: null,
+ ),
+ unreadNotifications: new UnreadNotificationCounts(0, 0),
+ unreadThreadNotifications: [],
+ );
}
}
- return new JsonResponse([
- "account_data" => [
- "events" => [
+ return new JsonResponse(new ClientSyncGetResponse(
+ nextBatch: "1",
- ],
- ],
+ accountData: new AccountData([]),
- "device_lists" => [
- "changed" => [],
- "left" => [],
- ],
+ deviceLists: new DeviceLists([], []),
- "device_one_time_keys_count" => [
+ deviceOneTimeKeysCount: [
"signed_curve25519" => 10,
],
- "next_batch" => "next_batch_id",
-
- "presence" => [
- "events" => [
- (new PresenceEvent(sender: $user->getId()))->toJsonEncodeable(),
- ],
- ],
-
- "rooms" => [
- "invite" => new \stdClass(),
- "join" => $joinedRooms,
- "knock" => new \stdClass(),
- "leave" => new \stdClass(),
- ],
-
- "to_device" => [
- "events" => [],
- ],
- ]);
+ presence: new Presence([
+ new PresenceEvent(
+ sender: $user->getId(),
+ presence: $setPresence,
+ ),
+ ]),
+
+ rooms: new Rooms(
+ $invitedRooms,
+ $joinedRooms,
+ $knockedRooms,
+ $leftRooms,
+ ),
+
+ toDevice: new ToDevice([]),
+ ));
}
}
diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php
index a056e1d..d102160 100755
--- a/src/Controllers/UserController.php
+++ b/src/Controllers/UserController.php
@@ -2,8 +2,10 @@
namespace App\Controllers;
+use App\Database;
use App\Errors\UnauthorizedError;
use App\Models\User;
+use App\Support\RequestValidator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -25,8 +27,31 @@ class UserController
throw new UnauthorizedError();
}
+ $userId = $request->get("userId");
+ if ($user->getId() !== $userId) {
+ throw new UnauthorizedError();
+ }
+
+ $body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
+
+ $filterId = md5($userId . random_bytes(512));
+
+ Database::getInstance()->query(<<<SQL
+ insert into filters (id, account_data, event_fields, event_format, presence, room, user_id)
+ values (:id, :account_data, :event_fields, :event_format, :presence, :room, :user_id)
+ SQL, [
+ "id" => $filterId,
+ "account_data" => isset($body["account_data"]) ? json_encode($body["account_data"]) : null,
+ "event_fields" => isset($body["event_fields"]) ? json_encode($body["event_fields"]) : null,
+ "event_format" => isset($body["event_format"]) ? json_encode($body["event_format"]) : null,
+ "presence" => isset($body["presence"]) ? json_encode($body["presence"]) : null,
+ "room" => isset($body["room"]) ? json_encode($body["room"]) : null,
+ "user_id" => $userId,
+ ]);
+
return new JsonResponse([
- "filter_id" => "1234",
+ "filter_id" => $filterId,
]);
}
}
diff --git a/src/Database.php b/src/Database.php
index e84abc1..4c26a93 100644
--- a/src/Database.php
+++ b/src/Database.php
@@ -2,9 +2,6 @@
namespace App;
-use PDO;
-use PDOStatement;
-
class Database
{
use Singleton;
@@ -40,4 +37,9 @@ class Database
return $statement;
}
+
+ public function getLastInsertId(string $name = ""): string|false
+ {
+ return $this->connection->lastInsertId($name);
+ }
}
diff --git a/src/Events/Event.php b/src/Events/Event.php
deleted file mode 100644
index 7dbf242..0000000
--- a/src/Events/Event.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Types\EventType;
-
-abstract class Event
-{
- public function __construct(
- protected EventType $type,
- )
- {}
-
- abstract public function fromJson(string $json): self;
-
- /**
- * @return array<string, mixed>
- */
- abstract public function toJsonEncodeable(): array;
-}
diff --git a/src/Events/PresenceEvent.php b/src/Events/PresenceEvent.php
deleted file mode 100644
index cd230c5..0000000
--- a/src/Events/PresenceEvent.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Types\EventType;
-use App\Types\PresenceState;
-
-class PresenceEvent extends Event
-{
- public function __construct(
- private string $sender,
- private string $avatarUrl = "mxc://localhost/wefuiwegh8742w",
- private int $lastActiveAgo = 1234,
- private bool $currentlyActive = false,
- private PresenceState $presence = PresenceState::ONLINE,
- private string $statusMessage = "",
- )
- {
- parent::__construct(EventType::PRESENCE);
- }
-
- public function fromJson(string $json): self
- {
- }
-
- public function toJsonEncodeable(): array
- {
- return [
- "type" => $this->type,
- "sender" => $this->sender,
- "content" => [
- "avatar_url" => $this->avatarUrl,
- "currently_active" => $this->currentlyActive,
- "last_active_ago" => $this->lastActiveAgo,
- "presence" => $this->presence,
- "status_msg" => $this->statusMessage,
- ],
- ];
- }
-}
diff --git a/src/Events/RoomMemberEvent.php b/src/Events/RoomMemberEvent.php
deleted file mode 100644
index 79ba0cc..0000000
--- a/src/Events/RoomMemberEvent.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Types\EventType;
-use App\Types\MembershipState;
-
-class RoomMemberEvent extends Event
-{
- public function __construct(
- private string $sender,
- private string $stateKey,
- private MembershipState $membership,
- )
- {
- parent::__construct(EventType::ROOM_MEMBER);
- }
-
- public function fromJson(string $json): self
- {
- }
-
- public function toJsonEncodeable(): array
- {
- return [
- "type" => $this->type,
- "sender" => $this->sender,
- "state_key" => $this->stateKey,
- "event_id" => "\$0",
- "origin_server_ts" => time(),
- "content" => [
- "membership" => $this->membership,
- ],
- ];
- }
-}
diff --git a/src/Events/RoomMessageEvent.php b/src/Events/RoomMessageEvent.php
deleted file mode 100644
index 3ccc408..0000000
--- a/src/Events/RoomMessageEvent.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Database;
-use App\Support\ConnectsToDatabase;
-use App\Types\EventType;
-use App\Types\MembershipState;
-use App\Types\MessageType;
-
-class RoomMessageEvent extends Event implements ConnectsToDatabase
-{
- public function __construct(
- private string $id = "",
- private string $sender = "",
- private \DateTime $originServerTimestamp = new \DateTime("now"),
- private array $content = [],
- private array $unsigned = [],
- private string $roomId = "",
- )
- {
- parent::__construct(EventType::ROOM_MESSAGE);
- }
-
- public function fromJson(string $json): self
- {
- }
-
- public function toJsonEncodeable(): array
- {
- return [
- "type" => $this->type,
- "sender" => $this->sender,
- "event_id" => $this->id,
- "origin_server_ts" => $this->originServerTimestamp->format("U.v"),
- "content" => [
- "body" => $this->body,
- "msgtype" => MessageType::TEXT,
- ],
- "unsigned" => [
- "age" => 1234,
- "membership" => MembershipState::JOIN,
- ],
- "room_id" => $this->roomId,
- ];
- }
-
- public static function fromDatabase(array $row): self
- {
- }
-
- public static function fetch(): ?self
- {
- }
-
- public static function fetchAll(): array
- {
- }
-
- public function insert(): bool
- {
- return !! Database::getInstance()->query(
- <<<SQL
- insert into room_events (id, type, sender, origin_server_timestamp, content, unsigned, room_id)
- values (:id, :type, :sender, to_timestamp(:origin_server_timestamp), :content, :unsigned, :room_id)
- SQL,
- [
- "id" => $this->id,
- "type" => $this->type->value,
- "sender" => $this->sender,
- "origin_server_timestamp" => (new \DateTime("now"))->format("U.v"),
- "content" => json_encode($this->content),
- "unsigned" => json_encode($this->unsigned),
- "room_id" => $this->roomId,
- ]
- );
- }
-
- public function update(): bool
- {
- }
-
- public function delete(): bool
- {
- }
-}
diff --git a/src/Events/RoomNameEvent.php b/src/Events/RoomNameEvent.php
deleted file mode 100644
index 279e6c1..0000000
--- a/src/Events/RoomNameEvent.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Types\EventType;
-
-class RoomNameEvent extends Event
-{
- public function __construct(
- private string $sender,
- private string $stateKey,
- private string $name,
- )
- {
- parent::__construct(EventType::ROOM_NAME);
- }
-
- public function fromJson(string $json): self
- {
- }
-
- public function toJsonEncodeable(): array
- {
- return [
- "type" => $this->type,
- "sender" => $this->sender,
- "state_key" => $this->stateKey,
- "content" => [
- "name" => $this->name,
- ],
- ];
- }
-}
diff --git a/src/Models/RoomEvent.php b/src/Models/RoomEvent.php
new file mode 100644
index 0000000..96dc204
--- /dev/null
+++ b/src/Models/RoomEvent.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Models;
+
+use App\Database;
+use Matrix\Enums\EventType;
+use Matrix\Events\ClientEvent;
+use Matrix\Events\StateEvent;
+
+class RoomEvent
+{
+ public function __construct(
+ private ClientEvent $event,
+ )
+ {}
+
+ /**
+ * @param array<string, mixed> $row
+ */
+ public static function transformEvent(array $row): ClientEvent
+ {
+ return new ClientEvent(
+ content: json_decode($row["content"], true),
+ eventId: $row["id"],
+ originServerTimestamp: new \DateTime($row["origin_server_timestamp"])->getTimestamp(),
+ roomId: $row["room_id"],
+ sender: $row["sender"],
+ type: EventType::from($row["type"]),
+ unsigned: json_decode($row["unsigned"], true),
+ );
+ }
+
+ public function insert(): bool
+ {
+ if ($this->event instanceof StateEvent) {
+ return !! Database::getInstance()->query(<<<SQL
+ insert into room_events (id, content, type, sender, origin_server_timestamp, room_id, unsigned, state_key)
+ values (:id, :content, :type, :sender, to_timestamp(:origin_server_timestamp), :room_id, :unsigned, :state_key)
+ SQL, [
+ "id" => $this->event->getId(),
+ "content" => json_encode($this->event->getContent()),
+ "type" => $this->event->getType()->value,
+ "sender" => $this->event->getSender(),
+ "origin_server_timestamp" => \DateTime::createFromTimestamp($this->event->getOriginServerTimestamp())->format("U.v"),
+ "room_id" => $this->event->getRoomId(),
+ "unsigned" => json_encode($this->event->getUnsigned()),
+ "state_key" => $this->event->getStateKey(),
+ ]);
+ }
+
+ return !! Database::getInstance()->query(<<<SQL
+ insert into room_events (id, content, type, sender, origin_server_timestamp, room_id, unsigned)
+ values (:id, :content, :type, :sender, to_timestamp(:origin_server_timestamp), :room_id, :unsigned)
+ SQL, [
+ "id" => $this->event->getId(),
+ "content" => json_encode($this->event->getContent()),
+ "type" => $this->event->getType()->value,
+ "sender" => $this->event->getSender(),
+ "origin_server_timestamp" => \DateTime::createFromTimestamp($this->event->getOriginServerTimestamp())->format("U.v"),
+ "room_id" => $this->event->getRoomId(),
+ "unsigned" => json_encode($this->event->getUnsigned()),
+ ]);
+ }
+}
diff --git a/src/Models/Tokens.php b/src/Models/Tokens.php
index 4ad8e1d..ee912fb 100644
--- a/src/Models/Tokens.php
+++ b/src/Models/Tokens.php
@@ -37,9 +37,9 @@ class Tokens implements ConnectsToDatabase
$isExpiredSql = "";
if ($isExpired) {
- $isExpiredSql = "and expires_at <= current_timestamp";
+ #$isExpiredSql = "and expires_at <= current_timestamp";
} else {
- $isExpiredSql = "and expires_at > current_timestamp";
+ #$isExpiredSql = "and expires_at > current_timestamp";
}
$row = [];
@@ -78,6 +78,26 @@ class Tokens implements ConnectsToDatabase
public static function fetchAll(): array
{}
+ public static function fetchWithAccessToken(string $accessToken): ?self
+ {
+ $row = Database::getInstance()->query(
+ <<<SQL
+ select * from tokens
+ where access_token=:access_token
+ order by created_at desc
+ SQL,
+ [
+ "access_token" => $accessToken,
+ ]
+ )->fetch();
+
+ if (empty($row)) {
+ return null;
+ }
+
+ return self::fromDatabase($row);
+ }
+
public static function fetchWithRefreshToken(string $refreshToken): ?self
{
$row = Database::getInstance()->query(
@@ -140,6 +160,11 @@ class Tokens implements ConnectsToDatabase
);
}
+ public function isExpired(): bool
+ {
+ return $this->expiresAt->format("U.v") <= time();
+ }
+
public function getAccessToken(): string
{
return $this->accessToken;
diff --git a/src/Models/User.php b/src/Models/User.php
index c0c73f8..c92f5b3 100644
--- a/src/Models/User.php
+++ b/src/Models/User.php
@@ -3,19 +3,26 @@
namespace App\Models;
use App\Database;
+use App\Errors\AppException;
+use App\Errors\ErrorCode;
use App\Errors\UnauthorizedError;
use App\Support\ConnectsToDatabase;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
class User implements ConnectsToDatabase
{
- public function __construct(private string $id)
+ public function __construct(
+ private string $id,
+ private string $name,
+ )
{}
public static function fromDatabase(array $row): self
{
return new self(
$row["id"],
+ $row["name"],
);
}
@@ -67,9 +74,9 @@ class User implements ConnectsToDatabase
return self::fromDatabase($row);
}
- public static function new(string $id): self
+ public static function new(string $id, string $name): self
{
- return new self($id);
+ return new self($id, $name);
}
public static function authenticateWithRequest(Request $request): self
@@ -81,6 +88,17 @@ class User implements ConnectsToDatabase
throw new UnauthorizedError();
}
+ $tokens = Tokens::fetchWithAccessToken($accessToken);
+
+ if (empty($tokens) /*|| $tokens->isExpired()*/) {
+ throw new AppException(
+ ErrorCode::UNKNOWN_TOKEN,
+ "Soft logged out",
+ Response::HTTP_UNAUTHORIZED,
+ ["soft_logout" => true],
+ );
+ }
+
return $user;
}
@@ -88,11 +106,12 @@ class User implements ConnectsToDatabase
{
return !! Database::getInstance()->query(
<<<SQL
- insert into users (id)
- values (:id)
+ insert into users (id, name)
+ values (:id, :name)
SQL,
[
"id" => $this->id,
+ "name" => $this->name,
]
);
}
@@ -108,10 +127,16 @@ class User implements ConnectsToDatabase
return $this->id;
}
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
public function fetchDevice(string $id): ?Device
{
return Device::fetch($id, $this->id);
}
+
/**
* @return Device[]
*/
diff --git a/src/Router/Router.php b/src/Router/Router.php
index 534b7f7..377821f 100644
--- a/src/Router/Router.php
+++ b/src/Router/Router.php
@@ -6,6 +6,7 @@ use App\Errors\ErrorCode;
use App\Errors\ErrorResponse;
use App\Errors\Exception;
use App\Singleton;
+use App\Support\Logger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
@@ -38,14 +39,14 @@ class Router
{
$request = Request::createFromGlobals();
- if ($request->isMethod("OPTIONS")) {
- $response = new Response();
- $response->headers->add([
- "Access-Control-Allow-Origin" => "*",
- "Access-Control-Allow-Methods" => "GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD",
- "Access-Control-Allow-Headers" => "X-Requested-With, Content-Type, Authorization",
- ]);
+ $response = new Response();
+ $response->headers->add([
+ "Access-Control-Allow-Origin" => "*",
+ "Access-Control-Allow-Methods" => "GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD",
+ "Access-Control-Allow-Headers" => "X-Requested-With, Content-Type, Authorization",
+ ]);
+ if ($request->isMethod("OPTIONS")) {
return $response;
}
@@ -64,22 +65,34 @@ class Router
array_flip(["_controller", "_route"])
));
- return (new $class)->$method($request);
+ Logger::logRequestToFile($request);
+
+ $response = (new $class)->$method($request);
} catch (Exception $exception) {
- return ErrorResponse::fromException($exception);
+ $response = ErrorResponse::fromException($exception);
} catch (ResourceNotFoundException $exception) {
- return new ErrorResponse(ErrorCode::NOT_FOUND, "404", Response::HTTP_NOT_FOUND);
+ $response = new ErrorResponse(ErrorCode::NOT_FOUND, "404", Response::HTTP_NOT_FOUND);
} catch (MethodNotAllowedException $exception) {
- return new ErrorResponse(ErrorCode::FORBIDDEN, "403", Response::HTTP_FORBIDDEN);
+ $response = new ErrorResponse(ErrorCode::FORBIDDEN, "403", Response::HTTP_FORBIDDEN);
} catch (\LogicException $exception) { // display logic exceptions normally
throw $exception;
} catch (\Exception $exception) {
- return new ErrorResponse(
+ $response = new ErrorResponse(
ErrorCode::UNKNOWN,
$exception->getMessage() ?: "Unknown error occured",
Response::HTTP_INTERNAL_SERVER_ERROR
);
+ } catch (\Error $error) {
+ error_log($error->getMessage() ?: "Unknown error occured");
+
+ $response = new ErrorResponse(
+ ErrorCode::UNKNOWN,
+ $error->getMessage() ?: "Unknown error occured",
+ Response::HTTP_INTERNAL_SERVER_ERROR
+ );
}
+
+ return $response;
}
/**
diff --git a/src/Router/routes_client_server.php b/src/Router/routes_client_server.php
index fd39d08..590520b 100644
--- a/src/Router/routes_client_server.php
+++ b/src/Router/routes_client_server.php
@@ -34,6 +34,10 @@ return function (RouteConfigurator $routes): void
->add("matrix_client_r0_login_types", "/_matrix/client/r0/login")
->controller($supportedLoginTypes)
->methods(["GET"]);
+ $routes
+ ->add("matrix_client_v3_login_types", "/_matrix/client/v3/login")
+ ->controller($supportedLoginTypes)
+ ->methods(["GET"]);
$routes
->add("matrix_client_r0_login", "/_matrix/client/r0/login")
@@ -89,4 +93,19 @@ return function (RouteConfigurator $routes): void
->add("matrix_client_r0_user_id_filter", "/_matrix/client/r0/user/{userId}/filter")
->controller([UserController::class, "uploadFilter"])
->methods(["POST"]);
+
+ $routes
+ ->add("matrix_client_v3_user_id_filter", "/_matrix/client/v3/user/{userId}/filter")
+ ->controller([UserController::class, "uploadFilter"])
+ ->methods(["POST"]);
+
+ $routes
+ ->add("matrix_client_v3_room_create", "/_matrix/client/v3/createRoom")
+ ->controller([RoomController::class, "createRoom"])
+ ->methods(["POST"]);
+
+ $routes
+ ->add("matrix_client_v3_rooms_id_read_markers", "/_matrix/client/v3/rooms/{roomId}/read_markers")
+ ->controller([RoomController::class, "readMarkers"])
+ ->methods(["POST"]);
};
diff --git a/src/Support/ArrayTransformable.php b/src/Support/ArrayTransformable.php
deleted file mode 100644
index f1adf81..0000000
--- a/src/Support/ArrayTransformable.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace App\Support;
-
-interface ArrayTransformable
-{
- /**
- * @return array
- */
- public function toArray(): array;
-}
diff --git a/src/Support/Id.php b/src/Support/Id.php
new file mode 100644
index 0000000..dc23631
--- /dev/null
+++ b/src/Support/Id.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Support;
+
+class Id
+{
+ public static function generate(string $prefix = "", string $suffix = "", string $salt = ""): string
+ {
+ return $prefix . md5(time() . random_bytes(512)) . $suffix;
+ }
+
+ public static function generateRoomId(): string
+ {
+ return self::generate(prefix: "!", suffix: ":" . $_ENV["DOMAIN"]);
+ }
+
+ public static function generateEventId(): string
+ {
+ return self::generate(prefix: "$", suffix: ":" . $_ENV["DOMAIN"]);
+ }
+}
diff --git a/src/Support/Logger.php b/src/Support/Logger.php
index c806157..b01af11 100644
--- a/src/Support/Logger.php
+++ b/src/Support/Logger.php
@@ -9,6 +9,7 @@ class Logger implements LoggerInterface
{
public static function logRequestToFile(Request $request): void
{
+ @mkdir(dirname(dirname(__DIR__)) . "/.cache/log", recursive: true);
$basePath = dirname(dirname(__DIR__)) . "/.cache/log/" . str_replace("/", "_", $request->getPathInfo());
file_put_contents(
diff --git a/src/Types/AuthenticationType.php b/src/Types/AuthenticationType.php
deleted file mode 100644
index dc768d0..0000000
--- a/src/Types/AuthenticationType.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum AuthenticationType: string
-{
- case PASSWORD = "m.login.password";
- case RECAPTCHA = "m.login.recaptcha";
- case SSO = "m.login.sso";
- case EMAIL_IDENTITY = "m.login.email.identity";
- case MSISDN = "m.login.msisdn";
- case DUMMY = "m.login.dummy";
- case REGISTRATION_TOKEN = "m.login.registration_token";
-}
diff --git a/src/Types/EventType.php b/src/Types/EventType.php
deleted file mode 100644
index d599c6f..0000000
--- a/src/Types/EventType.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum EventType: string
-{
- case PRESENCE = "m.presence";
- case ROOM_NAME = "m.room.name";
- case ROOM_MEMBER = "m.room.member";
- case ROOM_MESSAGE = "m.room.message";
-}
diff --git a/src/Types/IdentifierType.php b/src/Types/IdentifierType.php
deleted file mode 100644
index 93396fa..0000000
--- a/src/Types/IdentifierType.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum IdentifierType: string
-{
- case USER = "m.id.user";
- case THIRDPARTY = "m.id.thirdparty";
- case PHONE = "m.id.phone";
-}
diff --git a/src/Types/LoginFlow.php b/src/Types/LoginFlow.php
deleted file mode 100644
index bd948a8..0000000
--- a/src/Types/LoginFlow.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace App\Types;
-
-use App\Support\ArrayTransformable;
-
-class LoginFlow implements ArrayTransformable
-{
- public function __construct(
- private LoginType $type,
- private bool $get_login_token = false,
- )
- {}
-
- public function toArray(): array
- {
- $flow = [
- "type" => $this->type,
- ];
-
- if ($this->type == LoginType::TOKEN) {
- $flow["get_login_token"] = $this->get_login_token;
- }
-
- return $flow;
- }
-}
diff --git a/src/Types/LoginType.php b/src/Types/LoginType.php
deleted file mode 100644
index a5e3d8b..0000000
--- a/src/Types/LoginType.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum LoginType: string
-{
- case PASSWORD = "m.login.password";
- case TOKEN = "m.login.token";
-}
diff --git a/src/Types/MembershipState.php b/src/Types/MembershipState.php
deleted file mode 100644
index a4b2a1d..0000000
--- a/src/Types/MembershipState.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum MembershipState: string
-{
- case INVITE = "invite";
- case JOIN = "join";
- case LEAVE = "leave";
- case BAN = "ban";
- case KNOCK = "knock";
-}
diff --git a/src/Types/MessageType.php b/src/Types/MessageType.php
deleted file mode 100644
index ded17a0..0000000
--- a/src/Types/MessageType.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum MessageType: string
-{
- case TEXT = "m.text";
-}
diff --git a/src/Types/PresenceState.php b/src/Types/PresenceState.php
deleted file mode 100644
index 26eeac6..0000000
--- a/src/Types/PresenceState.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum PresenceState: string
-{
- case ONLINE = "online";
- case OFFLINE = "offline";
- case UNAVAILABLE = "unavailable";
-}
diff --git a/src/Types/UserRegistrationKind.php b/src/Types/UserRegistrationKind.php
deleted file mode 100644
index 23dee2f..0000000
--- a/src/Types/UserRegistrationKind.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-namespace App\Types;
-
-enum UserRegistrationKind: string
-{
- case USER = "user";
- case GUEST = "guest";
-}