diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Controllers/LoginController.php | 73 | ||||
-rw-r--r-- | src/DB.php | 22 | ||||
-rw-r--r-- | src/Database.php | 36 | ||||
-rw-r--r-- | src/Errors/AppException.php | 11 | ||||
-rw-r--r-- | src/Models/Device.php | 150 | ||||
-rw-r--r-- | src/Models/Tokens.php | 41 | ||||
-rw-r--r-- | src/Models/User.php | 88 | ||||
-rw-r--r-- | src/Router/Router.php | 2 | ||||
-rw-r--r-- | src/Router/routes_client_server.php | 4 | ||||
-rw-r--r-- | src/Support/ConnectsToDatabase.php | 24 | ||||
-rw-r--r-- | src/Types/UserRegistrationKind.php | 9 |
11 files changed, 424 insertions, 36 deletions
diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index d48628b..1ff234c 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -2,10 +2,15 @@ namespace App\Controllers; +use App\Database; +use App\Errors\AppException; +use App\Errors\ErrorCode; use App\Errors\UnknownError; -use App\Support\Parser; +use App\Models\Device; +use App\Models\User; use App\Types\LoginFlow; use App\Types\LoginType; +use App\Types\UserRegistrationKind; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; @@ -14,7 +19,6 @@ class LoginController { /** * GET /_matrix/client/r0/login - * GET /_matrix/client/v3/login */ public function supportedLoginTypes(): Response { @@ -31,28 +35,77 @@ class LoginController public function login(): Response { $request = Request::createFromGlobals(); - $content = json_decode($request->getContent(), true); + $body = json_decode($request->getContent(), true); // validate login type $loginType = null; try { - $loginType = LoginType::from($content["type"]); + $loginType = LoginType::from($body["type"]); } catch (\ValueError $error) { throw new UnknownError("Bad login type.", Response::HTTP_BAD_REQUEST); } - // get user name - $user = Parser::parseUser($content["identifier"]["user"]); + // get user id + $userId = $body["identifier"]["user"]; #if ($loginType == LoginType::PASSWORD) {} + $user = User::fetchWithPassword($userId, $body["password"]); + + if (! $user) { + throw new AppException(ErrorCode::FORBIDDEN, "Invalid credentials.", Response::HTTP_FORBIDDEN); + } + + $deviceId = $body["device_id"] ?? ""; + $device = $user->fetchDevice($deviceId); + + if (! $device) { + $device = Device::new( + $user->getId(), + initialDisplayName: $body["initial_device_display_name"] ?? "", + ); + } + return new JsonResponse([ - "access_token" => "abc123", - "device_id" => "ABC", + "access_token" => $device->getAccessToken(), + "device_id" => $device->getId(), "expires_in_ms" => 60000, - "refresh_token" => "def456", - "user_id" => "@{$user["username"]}:{$_ENV["DOMAIN"]}", + "refresh_token" => $device->getRefreshToken(), + "user_id" => $user->getId(), #"well_known" => [], ]); } + + /** + * POST /_matrix/client/v3/register + */ + public function register(): Response + { + $request = Request::createFromGlobals(); + $body = json_decode($request->getContent(), true); + + $kind = UserRegistrationKind::from($request->query->get("kind") ?? "user"); + + $username = $body["username"]; + $userId = "@$username:$_ENV[DOMAIN]"; + + Database::getInstance()->query("insert into users (id, password) values (:id, :password)", [ + "id" => $userId, + "password" => $body["password"], + ]); + + $device_id = $body["device_id"] ?? ""; + $initialDeviceDisplayName = $body["initialDeviceDisplayName"] ?? ""; + + $device = Device::new($userId, $device_id, $initialDeviceDisplayName); + $device->insert(); + + return new JsonResponse([ + "access_token" => $device->getAccessToken(), + "device_id" => $device->getId(), + "expires_in_ms" => 60000, + "refresh_token" => $device->getRefreshToken(), + "user_id" => $userId, + ]); + } } diff --git a/src/DB.php b/src/DB.php deleted file mode 100644 index 53d078b..0000000 --- a/src/DB.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -namespace App; - -class DB -{ - use Singleton; - - private \PDO $connection; - - public function __construct() - { - $driver = $_ENV['DB_DRIVER'] ?? 'pgsql'; - $host = $_ENV['DB_HOST'] ?? 'localhost'; - $port = $_ENV['DB_PORT'] ?? 5432; - $dbname = $_ENV['DB_NAME']; - $user = $_ENV['DB_USER']; - $password = $_ENV['DB_PASSWORD']; - - $this->connection = new \PDO("$driver:host=$host;port=$port;dbname=$dbname", $user, $password); - } -} diff --git a/src/Database.php b/src/Database.php new file mode 100644 index 0000000..3fff863 --- /dev/null +++ b/src/Database.php @@ -0,0 +1,36 @@ +<?php + +namespace App; + +use PDO; +use PDOStatement; + +class Database +{ + use Singleton; + + private \PDO $connection; + + public function __construct() + { + #$driver = $_ENV['DB_DRIVER'] ?? 'pgsql'; + $host = $_ENV['DB_HOST'] ?? 'localhost'; + $port = $_ENV['DB_PORT'] ?? 5432; + $dbname = $_ENV['DB_NAME']; + $user = $_ENV['DB_USER']; + $password = $_ENV['DB_PASSWORD']; + + $this->connection = new \PDO("pgsql:host=$host;port=$port;dbname=$dbname", $user, $password); + } + + /** + * @param array<mixed> $parameters + */ + public function query(string $query, array $parameters = []): \PDOStatement|false + { + $statement = $this->connection->prepare($query); + $statement->execute($parameters); + + return $statement; + } +} diff --git a/src/Errors/AppException.php b/src/Errors/AppException.php new file mode 100644 index 0000000..e4359e6 --- /dev/null +++ b/src/Errors/AppException.php @@ -0,0 +1,11 @@ +<?php + +namespace App\Errors; + +class AppException extends Exception +{ + public function getAdditionalData(): array + { + return []; + } +} diff --git a/src/Models/Device.php b/src/Models/Device.php new file mode 100644 index 0000000..2b09c6b --- /dev/null +++ b/src/Models/Device.php @@ -0,0 +1,150 @@ +<?php + +namespace App\Models; + +use App\Database; +use App\Support\ConnectsToDatabase; + +class Device implements ConnectsToDatabase +{ + public function __construct( + private string $id, + private string $userId, + private string $name + ) + {} + + /** + * @param array<string,mixed> $row The row from the database + */ + public static function fromDatabase(array $row): self + { + return new self( + $row["id"], + $row["user_id"], + $row["name"] + ); + } + + public static function fetch(string $id = "", string $userId = ""): ?self + { + if (! empty($id) && empty($userId)) { + throw new \InvalidArgumentException("Can't fetch device without user id."); + } + + $row = []; + + if (! empty($userId)) { + if (! empty($id)) { + $row = Database::getInstance()->query( + <<<SQL + select * from devices where id=:id and user_id=:user_id + SQL, + [ + "id" => $id, + "user_id" => $userId, + ] + )->fetch(); + } else { + $row = Database::getInstance()->query( + <<<SQL + select * from devices where user_id=:user_id + SQL, + [ + "user_id" => $userId, + ] + )->fetch(); + } + } + + if (empty($row)) { + return null; + } + + return self::fromDatabase($row); + } + + public static function fetchAll(string $userId = ""): array + { + if (empty($userId)) { + throw new \InvalidArgumentException("missing user id"); + } + + $devices = []; + $rows = Database::getInstance()->query( + <<<SQL + select * from devices + where user_id=:user_id + SQL, + [ + "user_id" => $userId, + ] + )->fetchAll(); + + foreach ($rows as $row) { + $devices[] = Device::fromDatabase($row); + } + + return $devices; + } + + public static function new(string $userId, string $deviceId = "", string $initialDisplayName = ""): self + { + $deviceId = $deviceId ?: md5($userId . random_bytes(512)); + $initialDisplayName = $initialDisplayName ?: "capybara"; + + $accessToken = md5($userId . random_bytes(512)); + $refreshToken = md5($userId . random_bytes(512)); + + return new self( + $deviceId, + $userId, + $initialDisplayName, + $accessToken, + $refreshToken, + ); + } + + public function insert(): bool + { + return Database::getInstance()->query( + <<<SQL + insert into devices (id, user_id, name, access_token, refresh_token) + values (:id, :user_id, :name, :access_token, :refresh_token) + SQL, + [ + "id" => $this->id, + "user_id" => $this->userId, + "name" => $this->name, + "access_token" => $this->accessToken, + "refresh_token" => $this->refreshToken, + ] + ); + } + + public function update(): bool + {} + + public function delete(): bool + {} + + public function getId(): string + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function getAccessToken(): string + { + return $this->accessToken; + } + + public function getRefreshToken(): string + { + return $this->refreshToken; + } +} diff --git a/src/Models/Tokens.php b/src/Models/Tokens.php new file mode 100644 index 0000000..a94c876 --- /dev/null +++ b/src/Models/Tokens.php @@ -0,0 +1,41 @@ +<?php + +namespace App\Models; + +use App\Support\ConnectsToDatabase; + +class Tokens implements ConnectsToDatabase +{ + public function __construct( + private string $accessToken, + private string $refreshToken, + private string $userId, + private string $deviceId = "", + ) + {} + + public static function fromDatabase(array $row): self + { + return new self( + $row["access_token"], + $row["refresh_token"], + $row["user_id"], + $row["device_id"], + ); + } + + public static function fetch(): ?self + {} + + public static function fetchAll(): array + {} + + public function insert(): bool + {} + + public function update(): bool + {} + + public function delete(): bool + {} +} diff --git a/src/Models/User.php b/src/Models/User.php new file mode 100644 index 0000000..5d198e7 --- /dev/null +++ b/src/Models/User.php @@ -0,0 +1,88 @@ +<?php + +namespace App\Models; + +use App\Database; +use App\Support\ConnectsToDatabase; + +class User implements ConnectsToDatabase +{ + public function __construct(private string $id) + {} + + public static function fromDatabase(array $row): self + { + return new self( + $row["id"], + ); + } + + public static function fetch(string $id = ""): ?self + { + if (empty($id)) { + throw new \InvalidArgumentException("missing user id"); + } + + return Database::getInstance()->query( + <<<SQL + select * from users where id=:id + SQL, + [ + "id" => $id, + ] + ); + } + + public static function fetchAll(): array + {} + + public static function fetchWithPassword(string $id, string $password): ?self + { + $row = Database::getInstance()->query("select * from users where id=:id and password=:password", [ + "id" => $id, + "password" => $password, + ])->fetch(); + + if (empty($row)) { + return null; + } + + return self::fromDatabase($row); + } + + public function insert(): bool + { + return Database::getInstance()->query( + <<<SQL + insert into users (id) + values (:id) + SQL, + [ + "id" => $this->id, + ] + ); + } + + public function update(): bool + {} + + public function delete(): bool + {} + + public function getId(): string + { + return $this->id; + } + + public function fetchDevice(string $id): ?Device + { + return Device::fetch($id, $this->id); + } + /** + * @return Device[] + */ + public function fetchDevices(): array + { + return Device::fetchAll($this->id); + } +} diff --git a/src/Router/Router.php b/src/Router/Router.php index 1739e7c..61abed1 100644 --- a/src/Router/Router.php +++ b/src/Router/Router.php @@ -66,6 +66,8 @@ class Router return new ErrorResponse(ErrorCode::NOT_FOUND, "404", Response::HTTP_NOT_FOUND); } catch (MethodNotAllowedException $exception) { return new ErrorResponse(ErrorCode::FORBIDDEN, "403", Response::HTTP_FORBIDDEN); + } catch (\LogicException $exception) { // display logic exceptions normally + throw $exception; } catch (\Exception $exception) { return new ErrorResponse( ErrorCode::UNKNOWN, diff --git a/src/Router/routes_client_server.php b/src/Router/routes_client_server.php index 78e2e48..a0b117b 100644 --- a/src/Router/routes_client_server.php +++ b/src/Router/routes_client_server.php @@ -31,10 +31,6 @@ 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_v3_login", "/_matrix/client/v3/login") diff --git a/src/Support/ConnectsToDatabase.php b/src/Support/ConnectsToDatabase.php new file mode 100644 index 0000000..29f566c --- /dev/null +++ b/src/Support/ConnectsToDatabase.php @@ -0,0 +1,24 @@ +<?php + +namespace App\Support; + +interface ConnectsToDatabase +{ + /** + * @param array<string,mixed> $row + */ + public static function fromDatabase(array $row): self; + + public static function fetch(): ?self; + + /** + * @return array<self> + */ + public static function fetchAll(): array; + + public function insert(): bool; + + public function update(): bool; + + public function delete(): bool; +} diff --git a/src/Types/UserRegistrationKind.php b/src/Types/UserRegistrationKind.php new file mode 100644 index 0000000..23dee2f --- /dev/null +++ b/src/Types/UserRegistrationKind.php @@ -0,0 +1,9 @@ +<?php + +namespace App\Types; + +enum UserRegistrationKind: string +{ + case USER = "user"; + case GUEST = "guest"; +} |