diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f222fa9f1..f60b3bf6ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: php: [8.1, 8.2, 8.3, 8.4] - laravel: [10.*, 11.*] + laravel: [10.*, 11.*, 12.*] stability: [prefer-lowest, prefer-stable] os: [ubuntu-latest] include: @@ -32,6 +32,8 @@ jobs: exclude: - php: 8.1 laravel: 11.* + - php: 8.1 + laravel: 12.* - php: 8.4 laravel: 10.* diff --git a/composer.json b/composer.json index 5fdafcae10..a2934dd6a1 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "composer/semver": "^3.4", "guzzlehttp/guzzle": "^6.3 || ^7.0", "james-heinrich/getid3": "^1.9.21", - "laravel/framework": "^10.40 || ^11.34", + "laravel/framework": "^10.40 || ^11.34 || ^12.0", "laravel/prompts": "^0.1.16 || ^0.2.0 || ^0.3.0", "league/commonmark": "^2.2", "league/csv": "^9.0", @@ -23,12 +23,12 @@ "michelf/php-smartypants": "^1.8.1", "nesbot/carbon": "^2.62.1 || ^3.0", "pixelfear/composer-dist-plugin": "^0.1.4", - "rebing/graphql-laravel": "^9.7", + "rebing/graphql-laravel": "^9.8", "rhukster/dom-sanitizer": "^1.0.6", "spatie/blink": "^1.3", - "spatie/ignition": "^1.15", + "spatie/ignition": "^1.15.1", "statamic/stringy": "^3.1.2", - "stillat/blade-parser": "^1.10.1", + "stillat/blade-parser": "^1.10.1 || ^2.0", "symfony/lock": "^6.4", "symfony/var-exporter": "^6.0", "symfony/yaml": "^6.0 || ^7.0", @@ -42,8 +42,8 @@ "google/cloud-translate": "^1.6", "laravel/pint": "1.16.0", "mockery/mockery": "^1.6.10", - "orchestra/testbench": "^8.14 || ^9.2", - "phpunit/phpunit": "^10.5.35", + "orchestra/testbench": "^8.14 || ^9.2 || ^10.0", + "phpunit/phpunit": "^10.5.35 || ^11.5.3", "spatie/laravel-ray": "^1.37" }, "config": { diff --git a/src/Auth/Passwords/LaravelTwelveTokenRepository.php b/src/Auth/Passwords/LaravelTwelveTokenRepository.php new file mode 100644 index 0000000000..17b2891cc2 --- /dev/null +++ b/src/Auth/Passwords/LaravelTwelveTokenRepository.php @@ -0,0 +1,98 @@ +path = storage_path("statamic/password_resets/$table.yaml"); + } + + public function create(CanResetPasswordContract $user) + { + $email = $user->getEmailForPasswordReset(); + + $token = $this->createNewToken(); + + $this->insert($this->getPayload($email, $token)); + + return $token; + } + + protected function insert($payload) + { + $resets = $this->getResets(); + + $resets[$payload['email']] = [ + 'token' => $payload['token'], + 'created_at' => $payload['created_at']->timestamp, + ]; + + $this->putResets($resets); + } + + public function delete(CanResetPasswordContract $user) + { + $this->putResets( + $this->getResets()->forget($user->email()) + ); + } + + public function deleteExpired() + { + $this->putResets($this->getResets()->reject(function ($item, $email) { + return $this->tokenExpired($item['created_at']); + })); + } + + public function exists(CanResetPasswordContract $user, $token) + { + $record = $this->getResets()->get($user->email()); + + return $record && + ! $this->tokenExpired(Carbon::createFromTimestamp($record['created_at'], config('app.timezone'))) + && $this->hasher->check($token, $record['token']); + } + + public function recentlyCreatedToken(CanResetPasswordContract $user) + { + $record = $this->getResets()->get($user->email()); + + return $record && parent::tokenRecentlyCreated($record['created_at']); + } + + protected function getResets() + { + if (! $this->files->exists($this->path)) { + return collect(); + } + + return collect(YAML::parse($this->files->get($this->path))); + } + + protected function putResets($resets) + { + if (! $this->files->isDirectory($dir = dirname($this->path))) { + $this->files->makeDirectory($dir); + } + + $this->files->put($this->path, YAML::dump($resets->all())); + } +} diff --git a/src/Auth/Passwords/PasswordBrokerManager.php b/src/Auth/Passwords/PasswordBrokerManager.php index 3e70c7cd14..0267a1e2ac 100644 --- a/src/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Auth/Passwords/PasswordBrokerManager.php @@ -15,12 +15,23 @@ protected function createTokenRepository(array $config) $key = base64_decode(substr($key, 7)); } - return new TokenRepository( + if (version_compare(app()->version(), '12', '<')) { + return new TokenRepository( + $this->app['files'], + $this->app['hash'], + $config['table'], + $key, + $config['expire'], + $config['throttle'] ?? 0 + ); + } + + return new LaravelTwelveTokenRepository( $this->app['files'], $this->app['hash'], $config['table'], $key, - $config['expire'], + ($config['expire'] ?? 60) * 60, $config['throttle'] ?? 0 ); } diff --git a/tests/Auth/Protect/PasswordEntryTest.php b/tests/Auth/Protect/PasswordEntryTest.php index cd8a89e1dd..371912b833 100644 --- a/tests/Auth/Protect/PasswordEntryTest.php +++ b/tests/Auth/Protect/PasswordEntryTest.php @@ -110,16 +110,16 @@ public static function localPasswordProvider() { return [ 'string' => [ - 'value' => 'the-local-password', - 'submitted' => 'the-local-password', + 'the-local-password', + 'the-local-password', ], 'array with single value' => [ - 'value' => ['the-local-password'], - 'submitted' => 'the-local-password', + ['the-local-password'], + 'the-local-password', ], 'array with multiple values' => [ - 'value' => ['first-local-password', 'second-local-password'], - 'submitted' => 'second-local-password', + ['first-local-password', 'second-local-password'], + 'second-local-password', ], ]; }