diff --git a/src/Illuminate/Foundation/Console/KeyGenerateCommand.php b/src/Illuminate/Foundation/Console/KeyGenerateCommand.php index d51fce79f45e..b392eef8d2e2 100644 --- a/src/Illuminate/Foundation/Console/KeyGenerateCommand.php +++ b/src/Illuminate/Foundation/Console/KeyGenerateCommand.php @@ -35,10 +35,18 @@ class KeyGenerateCommand extends Command */ public function handle() { + if ($this->shouldSaveExistingKey()) { + $this->call('key:rotate'); + + return; + } + $key = $this->generateRandomKey(); if ($this->option('show')) { - return $this->line(''.$key.''); + $this->line(''.$key.''); + + return; } // Next, we will replace the application key in the environment file so it is @@ -53,6 +61,23 @@ public function handle() $this->components->info('Application key set successfully.'); } + /** + * Ask the user if they want to save the existing key. + */ + protected function shouldSaveExistingKey(): bool + { + $currentKey = $this->laravel['config']['app.key']; + + if (! empty($currentKey) && $this->confirm( + 'There is already an app key. Do you want to store the old key before generating a new one?', + true + )) { + return true; + } + + return false; + } + /** * Generate a random key for the application. * diff --git a/src/Illuminate/Foundation/Console/RotateKeyCommand.php b/src/Illuminate/Foundation/Console/RotateKeyCommand.php new file mode 100644 index 000000000000..0e36df468aa8 --- /dev/null +++ b/src/Illuminate/Foundation/Console/RotateKeyCommand.php @@ -0,0 +1,93 @@ +laravel['config']['app.key']; + + // 2. Get current APP_PREVIOUS_KEYS as array and prepend the current key + $previousKeys = Arr::prepend($this->laravel['config']['app.previous_keys'] ?? [], $currentKey); + + // 3. Update .env file with the new APP_PREVIOUS_KEYS and clear APP_KEY + if (! $this->updateEnvFile($currentKey, $previousKeys)) { + $this->components->error('Failed to update the environment file.'); + + return; + } + + // 4. Notify the user and generate a new key + $this->components->info('Current application key has been saved successfully. Running php artisan key:generate --force to generate a new key.'); + $this->call('key:generate', ['--force' => true]); + $this->components->info('Application key has been rotated successfully.'); + } + + /** + * Update the .env file with the new APP_PREVIOUS_KEYS and clear APP_KEY. + */ + protected function updateEnvFile(string $currentKey, array $previousKeys): bool + { + $envPath = $this->laravel->environmentFilePath(); + $contents = file_get_contents($envPath); + + // Convert array to comma-separated string and wrap in quotes + $quotedPreviousKeys = '"'.implode(',', $previousKeys).'"'; + + // Update APP_PREVIOUS_KEYS + if (str_contains($contents, 'APP_PREVIOUS_KEYS=')) { + // If APP_PREVIOUS_KEYS already exists, update its value + $contents = preg_replace($this->previousKeysPattern(), 'APP_PREVIOUS_KEYS='.$quotedPreviousKeys, $contents); + } else { + // If APP_PREVIOUS_KEYS does not exist, insert it after APP_KEY + $contents = preg_replace($this->keyPattern(), "APP_KEY=\nAPP_PREVIOUS_KEYS=".$quotedPreviousKeys, $contents); + } + + // Clear APP_KEY + $contents = preg_replace($this->keyPattern(), 'APP_KEY=', $contents); + $this->laravel['config']['app.key'] = null; + + return file_put_contents($envPath, $contents) !== false; + } + + /** + * Generate a regex pattern to match the current APP_KEY line. + */ + protected function keyPattern(): string + { + return '/^APP_KEY='.preg_quote($this->laravel['config']['app.key'], '/').'/m'; + } + + /** + * Generate a regex pattern to match the current APP_PREVIOUS_KEYS line. + */ + protected function previousKeysPattern(): string + { + return '/^APP_PREVIOUS_KEYS="'.preg_quote(implode(',', (array) $this->laravel['config']['app.previous_keys']), '/').'"/m'; + } +}