From 17b03414e1bdde3c0ba01614e7bc0fbcec606297 Mon Sep 17 00:00:00 2001 From: Albert Chen Date: Thu, 10 Aug 2023 00:32:01 +0800 Subject: [PATCH] feat: add JWT alias and jwt/auth config --- .env.example | 4 +- composer.json | 2 + composer.lock | 245 ++++++++++++++- config/autoload/app.php | 2 + config/autoload/auth.php | 26 ++ config/autoload/jwt.php | 284 ++++++++++++++++++ .../Commands/Migrations/BaseCommand.php | 5 +- 7 files changed, 563 insertions(+), 5 deletions(-) create mode 100644 config/autoload/auth.php create mode 100644 config/autoload/jwt.php diff --git a/.env.example b/.env.example index 09019f1..5280a2c 100644 --- a/.env.example +++ b/.env.example @@ -21,4 +21,6 @@ DB_PREFIX= REDIS_HOST=localhost REDIS_AUTH=(null) REDIS_PORT=6379 -REDIS_DB=0 \ No newline at end of file +REDIS_DB=0 + +JWT_SECRET= \ No newline at end of file diff --git a/composer.json b/composer.json index 733a927..a47d2e3 100644 --- a/composer.json +++ b/composer.json @@ -32,11 +32,13 @@ "hyperf/translation": "^3.1", "hyperf/validation": "^3.1", "psr/http-message": "^1.0", + "swooletw/hyperf-auth": "@dev", "swooletw/hyperf-cache": "@dev", "swooletw/hyperf-container": "@dev", "swooletw/hyperf-encryption": "@dev", "swooletw/hyperf-foundation": "@dev", "swooletw/hyperf-hashing": "@dev", + "swooletw/hyperf-jwt": "@dev", "swooletw/hyperf-log": "@dev", "swooletw/hyperf-router": "@dev", "swooletw/hyperf-sqlite": "@dev", diff --git a/composer.lock b/composer.lock index 98b4415..f059257 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "27a1c4ec8052848e64eb1083011362ea", + "content-hash": "9c09d456b3a0e23a06a810ee76782241", "packages": [ { "name": "doctrine/deprecations", @@ -3473,6 +3473,81 @@ ], "time": "2023-03-20T13:51:37+00:00" }, + { + "name": "lcobucci/jwt", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", + "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.26.19", + "lcobucci/clock": "^3.0", + "lcobucci/coding-standard": "^9.0", + "phpbench/phpbench": "^1.2.8", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.3", + "phpstan/phpstan-deprecation-rules": "^1.1.2", + "phpstan/phpstan-phpunit": "^1.3.8", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.0.12" + }, + "suggest": { + "lcobucci/clock": ">= 3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2023-02-25T21:35:16+00:00" + }, { "name": "monolog/monolog", "version": "3.4.0", @@ -3899,6 +3974,54 @@ ], "time": "2023-02-25T19:38:58+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -4496,6 +4619,74 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "swooletw/hyperf-auth", + "version": "dev-master", + "dist": { + "type": "path", + "url": "../framework/src/auth", + "reference": "3ee65cc67559c439cc29a07a1ee46634632f86d1" + }, + "require": { + "hyperf/config": "^3.1", + "hyperf/context": "^3.1", + "hyperf/contract": "^3.1", + "hyperf/database": "^3.1", + "hyperf/http-server": "^3.1", + "hyperf/macroable": "^3.1", + "hyperf/stringable": "^3.1", + "nesbot/carbon": "^2.16", + "php": "^8.1", + "swooletw/hyperf-hashing": "^3.1|dev-master", + "swooletw/hyperf-jwt": "^3.1|dev-master" + }, + "suggest": { + "hyperf/session": "Required to use session guard." + }, + "type": "library", + "extra": { + "hyperf": { + "config": "SwooleTW\\Hyperf\\Auth\\ConfigProvider" + }, + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "SwooleTW\\Hyperf\\Auth\\": "src/" + }, + "files": [ + "src/Functions.php" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Albert Chen", + "email": "albert.cht@pm.me", + "homepage": "https://albert-chen.com" + } + ], + "description": "The Support package for Hyperf.", + "keywords": [ + "auth", + "hyperf", + "larvel", + "php", + "swoole" + ], + "support": { + "issues": "https://github.com/swooletw/hyperf-packages/issues", + "source": "https://github.com/swooletw/hyperf-packages" + }, + "transport-options": { + "symlink": true, + "relative": true + } + }, { "name": "swooletw/hyperf-cache", "version": "dev-master", @@ -4780,6 +4971,56 @@ "relative": true } }, + { + "name": "swooletw/hyperf-jwt", + "version": "dev-master", + "dist": { + "type": "path", + "url": "../framework/src/jwt", + "reference": "a3ed07f5f2ca91b50c6deb94fa7aef20e7eaa233" + }, + "require": { + "hyperf/collection": "^3.1", + "hyperf/stringable": "^3.1", + "lcobucci/jwt": "^5.0", + "nesbot/carbon": "^2.16", + "php": "^8.1", + "psr/simple-cache": "^3.0" + }, + "type": "library", + "extra": { + "hyperf": { + "config": "SwooleTW\\Hyperf\\JWT\\ConfigProvider" + }, + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "SwooleTW\\Hyperf\\JWT\\": "src/" + } + }, + "license": [ + "MIT" + ], + "description": "The Support package for Hyperf.", + "keywords": [ + "hyperf", + "jwt", + "larvel", + "php", + "swoole" + ], + "support": { + "issues": "https://github.com/swooletw/hyperf-packages/issues", + "source": "https://github.com/swooletw/hyperf-packages" + }, + "transport-options": { + "symlink": true, + "relative": true + } + }, { "name": "swooletw/hyperf-log", "version": "dev-master", @@ -9492,11 +9733,13 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { + "swooletw/hyperf-auth": 20, "swooletw/hyperf-cache": 20, "swooletw/hyperf-container": 20, "swooletw/hyperf-encryption": 20, "swooletw/hyperf-foundation": 20, "swooletw/hyperf-hashing": 20, + "swooletw/hyperf-jwt": 20, "swooletw/hyperf-log": 20, "swooletw/hyperf-router": 20, "swooletw/hyperf-sqlite": 20, diff --git a/config/autoload/app.php b/config/autoload/app.php index 9d483a2..5971961 100644 --- a/config/autoload/app.php +++ b/config/autoload/app.php @@ -100,7 +100,9 @@ 'Response' => SwooleTW\Hyperf\Support\Facades\Response::class, 'Translator' => SwooleTW\Hyperf\Support\Facades\Translator::class, 'Validator' => SwooleTW\Hyperf\Support\Facades\Validator::class, + 'JWT' => SwooleTW\Hyperf\Support\Facades\JWT::class, 'Auth' => SwooleTW\Hyperf\Support\Facades\Auth::class, 'Hash' => SwooleTW\Hyperf\Support\Facades\Hash::class, + 'Environment' => SwooleTW\Hyperf\Support\Facades\Environment::class, ], ]; diff --git a/config/autoload/auth.php b/config/autoload/auth.php new file mode 100644 index 0000000..76e47fb --- /dev/null +++ b/config/autoload/auth.php @@ -0,0 +1,26 @@ + [ + 'guard' => 'jwt', + 'provider' => 'users', + ], + 'guards' => [ + 'session' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + 'jwt' => [ + 'driver' => 'jwt', + 'provider' => 'users', + ] + ], + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], + ], +]; diff --git a/config/autoload/jwt.php b/config/autoload/jwt.php new file mode 100644 index 0000000..21846c9 --- /dev/null +++ b/config/autoload/jwt.php @@ -0,0 +1,284 @@ + env('JWT_DRIVER', 'lcobucci'), + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Secret + |-------------------------------------------------------------------------- + | + | Don't forget to set this in your .env file, as it will be used to sign + | your tokens. A helper command is provided for this: + | `php artisan jwt:secret` + | + | Note: This will be used for Symmetric algorithms only (HMAC), + | since RSA and ECDSA use a private/public key combo (See below). + | + */ + + 'secret' => env('JWT_SECRET'), + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Keys + |-------------------------------------------------------------------------- + | + | The algorithm you are using, will determine whether your tokens are + | signed with a random string (defined in `JWT_SECRET`) or using the + | following public & private keys. + | + | Symmetric Algorithms: + | HS256, HS384 & HS512 will use `JWT_SECRET`. + | + | Asymmetric Algorithms: + | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. + | + */ + + 'keys' => [ + + /* + |-------------------------------------------------------------------------- + | Public Key + |-------------------------------------------------------------------------- + | + | A path or resource to your public key. + | + | E.g. 'file://path/to/public/key' + | + */ + + 'public' => env('JWT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Private Key + |-------------------------------------------------------------------------- + | + | A path or resource to your private key. + | + | E.g. 'file://path/to/private/key' + | + */ + + 'private' => env('JWT_PRIVATE_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passphrase + |-------------------------------------------------------------------------- + | + | The passphrase for your private key. Can be null if none set. + | + */ + + 'passphrase' => env('JWT_PASSPHRASE'), + + ], + + /* + |-------------------------------------------------------------------------- + | JWT time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token will be valid for. + | Defaults to 1 hour. + | + | You can also set this to null, to yield a never expiring token. + | Some people may want this behaviour for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. + | + */ + + 'ttl' => env('JWT_TTL', 120), + + /* + |-------------------------------------------------------------------------- + | Refresh time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token can be refreshed + | within. I.E. The user can refresh their token within a 2 week window of + | the original token being created until they must re-authenticate. + | Defaults to 2 weeks. + | + | You can also set this to null, to yield an infinite refresh time. + | Some may want this instead of never expiring tokens for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | + */ + + 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), + + /* + |-------------------------------------------------------------------------- + | JWT hashing algorithm + |-------------------------------------------------------------------------- + | + | Specify the hashing algorithm that will be used to sign the token. + | + */ + + 'algo' => env('JWT_ALGO', SwooleTW\Hyperf\JWT\Providers\Provider::ALGO_HS256), + + /* + |-------------------------------------------------------------------------- + | Validations + |-------------------------------------------------------------------------- + | + | Sepcify default validations that jwt tokens. + | + */ + 'validations' => [ + \SwooleTW\Hyperf\JWT\Validations\RequiredClaims::class, + // \SwooleTW\Hyperf\JWT\Validations\ExpiredCliam::class, + // \SwooleTW\Hyperf\JWT\Validations\IssuedAtClaim::class, + // \SwooleTW\Hyperf\JWT\Validations\NotBeforeCliam::class, + ], + + /* + |-------------------------------------------------------------------------- + | Required Claims + |-------------------------------------------------------------------------- + | + | Specify the required claims that must exist in any token. + | A TokenInvalidException will be thrown if any of these claims are not + | present in the payload. + | + */ + + 'required_claims' => [ + // 'iss', + 'iat', + // 'exp', + // 'nbf', + 'sub', + // 'jti', + ], + + /* + |-------------------------------------------------------------------------- + | Persistent Claims + |-------------------------------------------------------------------------- + | + | Specify the claim keys to be persisted when refreshing a token. + | `sub` and `iat` will automatically be persisted, in + | addition to the these claims. + | + | Note: If a claim does not exist then it will be ignored. + | + */ + + 'persistent_claims' => [ + // 'foo', + // 'bar', + ], + + /* + |-------------------------------------------------------------------------- + | Leeway + |-------------------------------------------------------------------------- + | + | This property gives the jwt timestamp claims some "leeway". + | Meaning that if you have any unavoidable slight clock skew on + | any of your servers then this will afford you some level of cushioning. + | + | This applies to the claims `iat`, `nbf` and `exp`. + | + | Specify in seconds - only if you know you need it. + | + */ + + 'leeway' => env('JWT_LEEWAY', 0), + + /* + |-------------------------------------------------------------------------- + | Blacklist Enabled + |-------------------------------------------------------------------------- + | + | In order to invalidate tokens, you must have the blacklist enabled. + | If you do not want or need this functionality, then set this to false. + | + */ + + 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', false), + + /* + | ------------------------------------------------------------------------- + | Blacklist Grace Period + | ------------------------------------------------------------------------- + | + | When multiple concurrent requests are made with the same JWT, + | it is possible that some of them fail, due to token regeneration + | on every request. + | + | Set grace period in seconds to prevent parallel request failure. + | + */ + + 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), + + /* + | ------------------------------------------------------------------------- + | Refresh time to live of blacklist + | ------------------------------------------------------------------------- + | + | Number of minutes from issue date in which a JWT can be refreshed. + | + */ + + 'blacklist_refresh_ttl' => env('JWT_BLACKLIST_REFRESH_TTL', 20160), + + /* + |-------------------------------------------------------------------------- + | Providers + |-------------------------------------------------------------------------- + | + | Specify the various providers used throughout the package. + | + */ + + 'providers' => [ + + /* + |-------------------------------------------------------------------------- + | JWT Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to create and decode the tokens. + | + */ + + 'jwt' => SwooleTW\Hyperf\JWT\Providers\Lcobucci::class, + + /* + |-------------------------------------------------------------------------- + | Storage Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to store tokens in the blacklist. + | + */ + + 'storage' => SwooleTW\Hyperf\JWT\Storage\HyperfCache::class, + ], +]; diff --git a/overrides/Hyperf/Database/Commands/Migrations/BaseCommand.php b/overrides/Hyperf/Database/Commands/Migrations/BaseCommand.php index be90808..bfdb41b 100644 --- a/overrides/Hyperf/Database/Commands/Migrations/BaseCommand.php +++ b/overrides/Hyperf/Database/Commands/Migrations/BaseCommand.php @@ -11,10 +11,9 @@ */ namespace Hyperf\Database\Commands\Migrations; +use Hyperf\Collection\Collection; use Hyperf\Command\Command; -use function Hyperf\Collection\collect; - abstract class BaseCommand extends Command { /** @@ -26,7 +25,7 @@ protected function getMigrationPaths(): array // use the path relative to the root of the installation folder so our database // migrations may be run for any customized path from within the application. if ($this->input->hasOption('path') && $this->input->getOption('path')) { - return collect($this->input->getOption('path'))->map(function ($path) { + return Collection::make($this->input->getOption('path'))->map(function ($path) { return ! $this->usingRealPath() ? BASE_PATH . DIRECTORY_SEPARATOR . $path : $path;