commit adb24dae9f65d114230823c0155584abfd578b4d Author: flashwave Date: Sun Aug 25 01:50:52 2024 +0000 Initial import. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb3a0bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +[Tt]humbs.db +[Dd]esktop.ini +.DS_Store +/.debug +/vendor +/aleister.cfg diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..78866df --- /dev/null +++ b/LICENCE @@ -0,0 +1,30 @@ +Copyright (c) 2024, flashwave +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ed708c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Aleister + +Aleister is the API gateway server for Flashii. diff --git a/aleister.php b/aleister.php new file mode 100644 index 0000000..786641a --- /dev/null +++ b/aleister.php @@ -0,0 +1,32 @@ +hasValues('sentry:dsn')) + (function($cfg) { + \Sentry\init([ + 'dsn' => $cfg->getString('dsn'), + 'traces_sample_rate' => $cfg->getFloat('tracesRate', 0.2), + 'profiles_sample_rate' => $cfg->getFloat('profilesRate', 0.2), + ]); + + set_exception_handler(function(\Throwable $ex) { + \Sentry\captureException($ex); + }); + })($cfg->scopeTo('sentry')); + +$ctx = new AleisterContext($cfg); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..721c6e9 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "autoload": { + "psr-4": { + "Aleister\\": "src" + } + }, + "require": { + "flashwave/index": "^0.2408.40014", + "flashwave/syokuhou": "^1.2", + "flashwave/aiwass": "^1.0", + "sentry/sdk": "^4.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d57ab95 --- /dev/null +++ b/composer.lock @@ -0,0 +1,852 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f16579c0b9bf3a7b6091a2eec61e2354", + "packages": [ + { + "name": "flashwave/aiwass", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://patchii.net/flashii/aiwass.git", + "reference": "de27da54b603f0fdc06dab89341908e73ea7a880" + }, + "require": { + "ext-msgpack": ">=2.2", + "flashwave/index": "^0.2408.40014", + "php": ">=8.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Aiwass\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "bsd-3-clause-clear" + ], + "authors": [ + { + "name": "flashwave", + "email": "packagist@flash.moe", + "homepage": "https://flash.moe", + "role": "mom" + } + ], + "description": "Shared HTTP RPC client/server library.", + "homepage": "https://railgun.sh/aiwass", + "time": "2024-08-16T15:59:19+00:00" + }, + { + "name": "flashwave/index", + "version": "v0.2408.182001", + "source": { + "type": "git", + "url": "https://patchii.net/flash/index.git", + "reference": "5a1fdcccedf818897a3468d5457875fabfb2ce28" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.2" + }, + "suggest": { + "ext-mysqli": "Support for the Index\\Data\\MariaDB namespace (both mysqlnd and libmysql are supported).", + "ext-sqlite3": "Support for the Index\\Data\\SQLite namespace." + }, + "type": "library", + "autoload": { + "psr-4": { + "Index\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "bsd-3-clause-clear" + ], + "authors": [ + { + "name": "flashwave", + "email": "packagist@flash.moe", + "homepage": "https://flash.moe", + "role": "mom" + } + ], + "description": "Composer package for the common library for my projects.", + "homepage": "https://railgun.sh/index", + "time": "2024-08-18T20:01:21+00:00" + }, + { + "name": "flashwave/syokuhou", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://patchii.net/flash/syokuhou.git", + "reference": "129a46c0d917382f9bc195cce278be51984eb87d" + }, + "require": { + "flashwave/index": "^0.2408.40014", + "php": ">=8.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Syokuhou\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "bsd-3-clause-clear" + ], + "authors": [ + { + "name": "flashwave", + "email": "packagist@flash.moe", + "homepage": "https://flash.moe", + "role": "mom" + } + ], + "description": "Configuration library for PHP.", + "homepage": "https://railgun.sh/syokuhou", + "time": "2024-08-04T01:07:23+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:15:46+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + }, + "time": "2024-03-08T09:58:59+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sentry/sdk", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php-sdk.git", + "reference": "fcbca864e8d1dc712f3ecfaa95ea89d024fb2e53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/fcbca864e8d1dc712f3ecfaa95ea89d024fb2e53", + "reference": "fcbca864e8d1dc712f3ecfaa95ea89d024fb2e53", + "shasum": "" + }, + "require": { + "sentry/sentry": "^4.0" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "This is a meta package of sentry/sentry. We recommend using sentry/sentry directly.", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "sentry" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php-sdk/issues", + "source": "https://github.com/getsentry/sentry-php-sdk/tree/4.0.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2023-11-06T10:23:19+00:00" + }, + { + "name": "sentry/sentry", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/788ec170f51ebb22f2809a1e3f78b19ccd39b70d", + "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "jean85/pretty-package-versions": "^1.5|^2.0.4", + "php": "^7.2|^8.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" + }, + "conflict": { + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "guzzlehttp/promises": "^1.0|^2.0", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "monolog/monolog": "^1.6|^2.0|^3.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^8.5.14|^9.4", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", + "vimeo/psalm": "^4.17" + }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "profiling", + "sentry", + "tracing" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/4.9.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2024-08-08T14:40:50+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.11.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-08-08T09:02:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..d1e70dc --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: 5 + paths: + - src + bootstrapFiles: + - aleister.php + dynamicConstantNames: + - CRW_CLI + - CRW_DEBUG diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..4738ca1 --- /dev/null +++ b/public/index.php @@ -0,0 +1,21 @@ +getRouter()->dispatch(); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/src/AleisterContentHandler.php b/src/AleisterContentHandler.php new file mode 100644 index 0000000..555b850 --- /dev/null +++ b/src/AleisterContentHandler.php @@ -0,0 +1,58 @@ +accept->getMostPreferred([ + 'application/json', + 'text/html', // pretty printed JSON + ]) !== null; + } + + public function match(mixed $content): bool { + return true; + } + + public function handle(HttpResponseBuilder $response, mixed $content): void { + // mostly for debug and info pages, so we'll ignore the accept header for strings + if(is_string($content)) { + if(stripos($content, 'setTypeHTML('utf-8'); + else + $response->setTypePlain('utf-8'); + + $response->setContent($content); + return; + } + + if($content instanceof JsonSerializable) + $content = $content->jsonSerialize(); + elseif($content instanceof IBencodeSerialisable) + $content = $content->bencodeSerialise(); + + // default to JSON + if(!$response->hasContentType()) + $response->setTypeJson(); + + $flags = JSON_UNESCAPED_UNICODE + | JSON_UNESCAPED_SLASHES + | JSON_THROW_ON_ERROR + | JSON_PRESERVE_ZERO_FRACTION + | JSON_INVALID_UTF8_SUBSTITUTE; + + // pretty print if text/html is acceptable, someone is likely poking using a browser + if($this->accept->isAcceptable('text/html')) + $flags |= JSON_PRETTY_PRINT; + + $response->setContent(json_encode($content, $flags)); + } +} diff --git a/src/AleisterContext.php b/src/AleisterContext.php new file mode 100644 index 0000000..0f23ebe --- /dev/null +++ b/src/AleisterContext.php @@ -0,0 +1,92 @@ +authInfo = new OAuth2AuthInfo(['error' => 'none']); + + $this->rpcWrapper = new RpcClientWrapper; + $this->rpcWrapper->createHmacConfig($config, 'hanyuu'); + + $this->router = new HttpRouter( + errorHandler: 'plain', + registerDefaultContentHandlers: false + ); + $this->router->use('/', fn($resp) => $resp->setPoweredBy('Flashii')); + $this->router->use('/', $this->handleAcceptHeader(...)); + $this->router->use('/', $this->handleAuthzHeader(...)); + $this->router->get('/', fn() => 'Hello! Someday this page will probably redirect to documentation, but none exists yet.'); + + $this->router->scopeTo('/oauth2')->register(new OAuth2\OAuth2Routes($this)); + $this->router->scopeTo('/v1')->register(new V1\V1Routes(new V1\V1Context($this))); + } + + public function getRpcClient(): RpcClientWrapper { + return $this->rpcWrapper; + } + + public function getRouter(): IRouter { + return $this->router; + } + + public function getAuthInfo(): OAuth2AuthInfo { + return $this->authInfo; + } + + public function handleAcceptHeader($response, $request) { + $accept = HttpAcceptHeader::parse($request->getHeaderLine('Accept')); + $contentHandler = new AleisterContentHandler($accept); + + if(!$contentHandler->hasAcceptableType()) + return 406; + + $this->router->setErrorHandler(new AleisterErrorHandler($contentHandler)); + $this->router->registerContentHandler($contentHandler); + } + + public function handleAuthzHeader($response, $request): void { + $authInfo = null; + + $authzHeader = explode(' ', (string)$request->getHeaderLine('Authorization'), 2); + if(count($authzHeader) > 1) { + $authzMethod = array_shift($authzHeader); + $authzInfo = array_shift($authzHeader); + + if(strcasecmp($authzMethod, 'basic') === 0) { + $authzInfo = base64_decode($authzInfo); + if($authzInfo !== false) { + $authzInfo = explode(':', $authzInfo, 2); + if(count($authzInfo) > 0) + try { + $authInfo = $this->rpcWrapper->procedure( + 'hanyuu:oauth2:attemptAppAuth', + ['clientId' => array_shift($authzInfo), 'clientSecret' => array_shift($authzInfo) ?? ''] + ); + } catch(RuntimeException $ex) {} + } + } elseif(strcasecmp($authzMethod, 'bearer') === 0) { + try { + $authInfo = $this->rpcWrapper->procedure( + 'hanyuu:oauth2:getTokenInfo', + ['type' => 'Bearer', 'token' => $authzInfo] + ); + } catch(RuntimeException $ex) {} + } + } + + if($authInfo !== null) + $this->authInfo = new OAuth2AuthInfo($authInfo); + } +} diff --git a/src/AleisterErrorHandler.php b/src/AleisterErrorHandler.php new file mode 100644 index 0000000..06d56fc --- /dev/null +++ b/src/AleisterErrorHandler.php @@ -0,0 +1,18 @@ +contentHandler->handle($response, [ + 'code' => sprintf('http:%s', $code), + 'message' => $message, + ]); + } +} diff --git a/src/HttpAcceptHeader.php b/src/HttpAcceptHeader.php new file mode 100644 index 0000000..3144adf --- /dev/null +++ b/src/HttpAcceptHeader.php @@ -0,0 +1,59 @@ +accept, fn($a, $b) => $b->getQuality() <=> $a->getQuality()); + } + + public function isAcceptable(string $option, bool $strict = false): bool { + if(empty($this->accept)) + return true; + + foreach($this->accept as $type) + if($type->equals($option) && $type->getQuality() > 0 + && (!$strict || ($type->getCategory() !== '*' && $type->getKind() !== '*'))) + return true; + + return false; + } + + public function getMostPreferred(array $options = []): ?string { + if(empty($options)) + return null; + if(empty($this->accept)) + return $options[array_key_first($options)]; + + $prefer = null; + $weight = 0; + + foreach($options as $option) + foreach($this->accept as $type) { + $quality = $type->getQuality(); + if($quality <= 0 || $quality <= $weight || !$type->equals($option)) + continue; + + $prefer = $option; + $weight = $quality; + } + + return $prefer; + } + + public static function parse(string $header): HttpAcceptHeader { + $accept = []; + $types = explode(',', $header); + + foreach($types as $type) { + $type = trim($type); + if($type !== '') + $accept[] = MediaType::parse($type); + } + + return new HttpAcceptHeader($accept); + } +} diff --git a/src/OAuth2/OAuth2Routes.php b/src/OAuth2/OAuth2Routes.php new file mode 100644 index 0000000..2df027b --- /dev/null +++ b/src/OAuth2/OAuth2Routes.php @@ -0,0 +1,190 @@ + $value) + $wwwAuth .= sprintf(', %s="%s"', $name, rawurlencode($value)); + + $response->setStatusCode(401); + $response->setHeader('WWW-Authenticate', $wwwAuth); + } else + $response->setStatusCode(400); + } + + return $result; + } + + #[HttpPost('/request-authorise')] + public function postRequestAuthorise($response, $request) { + $response->setHeader('Cache-Control', 'no-store'); + + $authInfo = $this->ctx->getAuthInfo(); + if($authInfo->getError() === 'secret') + return self::filter($response, [ + 'error' => 'invalid_client', + 'error_description' => 'Provided client secret is not correct for this application.', + ], authzHeader: true); + + if(!$request->isFormContent()) + return self::filter($response, [ + 'error' => 'invalid_request', + 'error_description' => 'Your request must use content type application/x-www-form-urlencoded.', + ]); + + $rpc = $this->ctx->getRpcClient(); + $content = $request->getContent(); + + if($authInfo->getMethod() === '') + try { + $authInfo = new OAuth2AuthInfo($rpc->procedure('hanyuu:oauth2:attemptAppAuth', [ + 'clientId' => (string)$content->getParam('client_id'), + ])); + } catch(RuntimeException $ex) {} + + if(!$authInfo->isAppUser()) + return self::filter($response, [ + 'error' => 'invalid_client', + 'error_description' => 'App authentication failed.', + ]); + + try { + $reqInfo = new OAuth2RfcModel($rpc->procedure('hanyuu:oauth2:createAuthoriseRequest', [ + 'appId' => $authInfo->getAppId(), + 'scope' => (string)$content->getParam('scope'), + ])); + } catch(RuntimeException $ex) {} + + if($reqInfo === null || ($reqInfo->hasError() && !in_array($reqInfo->getError(), self::REQ_AUTH_ERRORS))) + return self::filter($response, [ + 'error' => 'server_error', + 'error_description' => 'Authorisation server gave unexpected response.', + ]); + + return self::filter($response, $reqInfo->getRaw()); + } + + #[HttpOptions('/token')] + #[HttpPost('/token')] + public function postToken($response, $request) { + $response->setHeader('Cache-Control', 'no-store'); + + $originHeaders = ['Origin', 'X-Origin', 'Referer']; + $origins = []; + foreach($originHeaders as $originHeader) { + $originHeader = $request->getHeaderFirstLine($originHeader); + if($originHeader !== '' && !in_array($originHeader, $origins)) + $origins[] = $originHeader; + } + + if(!empty($origins)) { + // TODO: check if none of the provided origins is on a blocklist or something + // different origins being specified for each header should probably also be considered suspect... + + $response->setHeader('Access-Control-Allow-Origin', $origins[0]); + $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST'); + $response->setHeader('Access-Control-Allow-Headers', 'Authorization'); + $response->setHeader('Access-Control-Expose-Headers', 'Vary'); + foreach($originHeaders as $originHeader) + $response->setHeader('Vary', $originHeader); + } + + if($request->getMethod() === 'OPTIONS') + return 204; + + if(!$request->isFormContent()) + return self::filter($response, [ + 'error' => 'invalid_request', + 'error_description' => 'Your request must use content type application/x-www-form-urlencoded.', + ]); + + $rpc = $this->ctx->getRpcClient(); + $content = $request->getContent(); + + $authInfo = $this->ctx->getAuthInfo(); + if($authInfo->getMethod() === '') + try { + $authInfo = new OAuth2AuthInfo($rpc->procedure('hanyuu:oauth2:attemptAppAuth', [ + 'clientId' => (string)$content->getParam('client_id'), + 'clientSecret' => (string)$content->getParam('client_secret') + ])); + } catch(RuntimeException $ex) { + $authInfo = null; + } + + if($authInfo === null || !$authInfo->isAppUser()) + return self::filter($response, [ + 'error' => 'invalid_client', + 'error_description' => 'App authentication failed.', + ]); + if($authInfo->getError() === 'secret') + return self::filter($response, [ + 'error' => 'invalid_client', + 'error_description' => 'Provided client secret is not correct for this application.' + ], authzHeader: true); + + try { + $name = ''; + $args = ['appId' => $authInfo->getAppId(), 'isAuthed' => $authInfo->isAuthed()]; + + switch($content->getParam('grant_type')) { + case 'authorization_code': + $name = 'authorisationCode'; + $args['code'] = (string)$content->getParam('code'); + $args['codeVerifier'] = (string)$content->getParam('code_verifier'); + break; + + case 'refresh_token': + $name = 'refreshToken'; + $args['refreshToken'] = (string)$content->getParam('refresh_token'); + + if($content->hasParam('scope')) + $args['scope'] = (string)$content->getParam('scope'); + break; + + case 'client_credentials': + $name = 'clientCredentials'; + + if($content->hasParam('scope')) + $args['scope'] = (string)$content->getParam('scope'); + break; + + case 'urn:ietf:params:oauth:grant-type:device_code': + case 'device_code': + $name = 'deviceCode'; + $args['deviceCode'] = (string)$content->getParam('device_code'); + break; + } + + if($name === '') + return self::filter($response, [ + 'error' => 'unsupported_grant_type', + 'error_description' => 'Requested grant type is not supported by this server.', + ]); + + return self::filter($response, $rpc->procedure('hanyuu:oauth2:createBearerToken:' . $name, $args)); + } catch(RuntimeException $ex) { + return self::filter($response, [ + 'error' => 'server_error', + 'error_description' => 'Authorisation server gave unexpected response.', + ]); + } + } +} diff --git a/src/RpcClientWrapper.php b/src/RpcClientWrapper.php new file mode 100644 index 0000000..c1ce97d --- /dev/null +++ b/src/RpcClientWrapper.php @@ -0,0 +1,47 @@ +scopeTo($prefix); + $this->createHmac( + $prefix . ':', + $config->getString('endpoint'), + fn() => $config->getString('secret') + ); + } + + public function createHmac(string $prefix, string $url, $getSecretKey): void { + $this->register($prefix, RpcClient::createHmac($url, $getSecretKey)); + } + + public function register(string $prefix, IRpcClient $client): void { + $this->clients[$prefix] = $client; + } + + public function scopeTo(string $prefix): IRpcClient { + return new RpcClientScoped($this, $prefix); + } + + public function query(string $action, array $params): mixed { + foreach($this->clients as $prefix => $client) + if(str_starts_with($action, $prefix)) + return $client->query($action, $params); + + throw new RuntimeException(sprintf('Unable to handle this query: %s', $action)); + } + + public function procedure(string $action, array $params): mixed { + foreach($this->clients as $prefix => $client) + if(str_starts_with($action, $prefix)) + return $client->procedure($action, $params); + + throw new RuntimeException(sprintf('Unable to handle this procedure: %s', $action)); + } +} diff --git a/src/RpcModels/Hanyuu/OAuth2AuthInfo.php b/src/RpcModels/Hanyuu/OAuth2AuthInfo.php new file mode 100644 index 0000000..a7df553 --- /dev/null +++ b/src/RpcModels/Hanyuu/OAuth2AuthInfo.php @@ -0,0 +1,38 @@ +getString('method'); + } + + public function isAuthed(): bool { + return $this->getBoolean('authed'); + } + + public function getAppId(): string { + return $this->getString('app_id'); + } + + public function hasUserId(): bool { + return $this->hasValue('user_id'); + } + + public function isAppUser(): bool { + return $this->getMethod() === 'basic' || $this->getUserId() === '0'; + } + + public function getUserId(): string { + return $this->getString('user_id'); + } + + public function getScope(): string { + return $this->getString('scope'); + } + + public function getExpiresIn(): int { + return $this->getInteger('expires_in'); + } +} diff --git a/src/RpcModels/Hanyuu/OAuth2RfcModel.php b/src/RpcModels/Hanyuu/OAuth2RfcModel.php new file mode 100644 index 0000000..f9b342d --- /dev/null +++ b/src/RpcModels/Hanyuu/OAuth2RfcModel.php @@ -0,0 +1,22 @@ +hasValue('error_description'); + } + + public function getErrorDescription(): string { + return $this->getString('error_description'); + } + + public function hasErrorUri(): bool { + return $this->hasValue('error_uri'); + } + + public function getErrorUri(): string { + return $this->getString('error_uri'); + } +} diff --git a/src/RpcModels/RpcModel.php b/src/RpcModels/RpcModel.php new file mode 100644 index 0000000..1903942 --- /dev/null +++ b/src/RpcModels/RpcModel.php @@ -0,0 +1,55 @@ +info = $info; + } + + public function hasError(): bool { + return $this->hasValue('error'); + } + + public function getError(): string { + return $this->getString('error'); + } + + public function getRaw(): array { + return $this->info; + } + + protected function hasValue(string $name): bool { + return array_key_exists($name, $this->info); + } + + protected function getValue(string $name, mixed $fallback = null): mixed { + return array_key_exists($name, $this->info) ? $this->info[$name] : $fallback; + } + + protected function getArray(string $name): array { + return array_key_exists($name, $this->info) && is_array($this->info[$name]) + ? $this->info[$name] : []; + } + + protected function getBoolean(string $name): bool { + return array_key_exists($name, $this->info) && is_bool($this->info[$name]) + && $this->info[$name]; + } + + protected function getInteger(string $name): int { + return array_key_exists($name, $this->info) && is_int($this->info[$name]) + ? $this->info[$name] : 0; + } + + protected function getFloat(string $name): float { + return array_key_exists($name, $this->info) && is_float($this->info[$name]) + ? $this->info[$name] : 0.0; + } + + protected function getString(string $name): string { + return array_key_exists($name, $this->info) && is_string($this->info[$name]) + ? $this->info[$name] : ''; + } +} diff --git a/src/V1/V1Context.php b/src/V1/V1Context.php new file mode 100644 index 0000000..faa4caf --- /dev/null +++ b/src/V1/V1Context.php @@ -0,0 +1,20 @@ +ctx->getRpcClient(); + } + + public function getAuthInfo(): OAuth2AuthInfo { + return $this->ctx->getAuthInfo(); + } +} diff --git a/src/V1/V1Routes.php b/src/V1/V1Routes.php new file mode 100644 index 0000000..ec8db88 --- /dev/null +++ b/src/V1/V1Routes.php @@ -0,0 +1,16 @@ +get('/', fn() => ['status' => 'operational']); + } +}