1313
1414namespace CodeIgniter \Commands \Encryption ;
1515
16- use CodeIgniter \CLI \BaseCommand ;
16+ use CodeIgniter \CLI \AbstractCommand ;
17+ use CodeIgniter \CLI \Attributes \Command ;
1718use CodeIgniter \CLI \CLI ;
19+ use CodeIgniter \CLI \Input \Option ;
1820use CodeIgniter \Config \DotEnv ;
1921use CodeIgniter \Encryption \Encryption ;
2022use Config \Paths ;
2123
2224/**
23- * Generates a new encryption key.
25+ * Generates a new encryption key and writes it in an `.env` file .
2426 */
25- class GenerateKey extends BaseCommand
27+ #[Command(name: 'key:generate ' , description: 'Generates a new encryption key and writes it in an `.env` file. ' , group: 'Encryption ' )]
28+ class GenerateKey extends AbstractCommand
2629{
2730 /**
28- * The Command's group.
29- *
30- * @var string
31+ * @var list<string>
3132 */
32- protected $ group = ' Encryption ' ;
33+ private const VALID_PREFIXES = [ ' hex2bin ' , ' base64 ' ] ;
3334
34- /**
35- * The Command's name.
36- *
37- * @var string
38- */
39- protected $ name = 'key:generate ' ;
35+ protected function configure (): void
36+ {
37+ $ this
38+ ->addOption (new Option (
39+ name: 'force ' ,
40+ shortcut: 'f ' ,
41+ description: 'Force overwrite existing key in `.env` file. ' ,
42+ ))
43+ ->addOption (new Option (
44+ name: 'length ' ,
45+ description: 'The length of the random string that should be returned in bytes. ' ,
46+ requiresValue: true ,
47+ default: '32 ' ,
48+ ))
49+ ->addOption (new Option (
50+ name: 'prefix ' ,
51+ description: 'Prefix to prepend to encoded key (either hex2bin or base64). ' ,
52+ requiresValue: true ,
53+ default: 'hex2bin ' ,
54+ ))
55+ ->addOption (new Option (
56+ name: 'show ' ,
57+ description: 'Shows the generated key in the terminal instead of storing in the `.env` file. ' ,
58+ ));
59+ }
4060
41- /**
42- * The Command's usage.
43- *
44- * @var string
45- */
46- protected $ usage = 'key:generate [options] ' ;
61+ protected function interact (array &$ arguments , array &$ options ): void
62+ {
63+ $ prefix = $ this ->getUnboundOption ('prefix ' , $ options );
4764
48- /**
49- * The Command's short description.
50- *
51- * @var string
52- */
53- protected $ description = 'Generates a new encryption key and writes it in an `.env` file. ' ;
65+ if (is_string ($ prefix ) && ! in_array ($ prefix , self ::VALID_PREFIXES , true )) {
66+ $ options ['prefix ' ] = CLI ::prompt ('Please provide a valid prefix to use. ' , self ::VALID_PREFIXES , 'required ' );
67+ }
5468
55- /**
56- * The command's options
57- *
58- * @var array<string, string>
59- */
60- protected $ options = [
61- '--force ' => 'Force overwrite existing key in `.env` file. ' ,
62- '--length ' => 'The length of the random string that should be returned in bytes. Defaults to 32. ' ,
63- '--prefix ' => 'Prefix to prepend to encoded key (either hex2bin or base64). Defaults to hex2bin. ' ,
64- '--show ' => 'Shows the generated key in the terminal instead of storing in the `.env` file. ' ,
65- ];
69+ if ($ this ->hasUnboundOption ('show ' , $ options )) {
70+ return ;
71+ }
6672
67- /**
68- * Actually execute the command.
69- */
70- public function run (array $ params )
71- {
72- $ prefix = $ params ['prefix ' ] ?? CLI ::getOption ('prefix ' );
73+ if ($ this ->hasUnboundOption ('force ' , $ options )) {
74+ return ;
75+ }
7376
74- if (in_array ($ prefix , [null , true ], true )) {
75- $ prefix = 'hex2bin ' ;
76- } elseif (! in_array ($ prefix , ['hex2bin ' , 'base64 ' ], true )) {
77- $ prefix = CLI ::prompt ('Please provide a valid prefix to use. ' , ['hex2bin ' , 'base64 ' ], 'required ' ); // @codeCoverageIgnore
77+ if (env ('encryption.key ' , '' ) === '' ) {
78+ return ;
7879 }
7980
80- $ length = $ params ['length ' ] ?? CLI ::getOption ('length ' );
81+ if (CLI ::prompt ('Overwrite existing key? ' , ['n ' , 'y ' ]) === 'y ' ) {
82+ $ options ['force ' ] = null ; // simulate the presence of the --force option
83+ }
84+ }
85+
86+ protected function execute (array $ arguments , array $ options ): int
87+ {
88+ $ prefix = $ options ['prefix ' ];
8189
82- if (in_array ($ length , [null , true ], true )) {
83- $ length = 32 ;
90+ if (! in_array ($ prefix , self ::VALID_PREFIXES , true )) {
91+ CLI ::error (sprintf ('Invalid prefix "%s". Use either "hex2bin" or "base64". ' , $ prefix ));
92+
93+ return EXIT_ERROR ;
8494 }
8595
86- $ encodedKey = $ this ->generateRandomKey ($ prefix , $ length );
96+ $ encodedKey = $ this ->generateRandomKey ($ prefix , ( int ) $ options [ ' length ' ] );
8797
88- if (array_key_exists ( 'show ' , $ params ) || ( bool ) CLI :: getOption ( ' show ' ) ) {
98+ if ($ options [ 'show ' ] === true ) {
8999 CLI ::write ($ encodedKey , 'yellow ' );
90- CLI ::newLine ();
91100
92101 return EXIT_SUCCESS ;
93102 }
94103
95- if (! $ this ->setNewEncryptionKey ($ encodedKey , $ params )) {
96- CLI ::write ('Error in setting new encryption key to .env file. ' , 'light_gray ' , 'red ' );
97- CLI ::newLine ();
104+ $ currentKey = env ('encryption.key ' , '' );
105+
106+ if ($ currentKey !== '' && $ options ['force ' ] === false ) {
107+ CLI ::error ('Setting new encryption key aborted. ' );
108+
109+ if (! $ this ->isInteractive ()) {
110+ CLI ::error ('If you want, use the "--force" option to force overwrite the existing key. ' );
111+ }
112+
113+ return EXIT_ERROR ;
114+ }
115+
116+ if (! $ this ->writeNewEncryptionKeyToFile ($ currentKey , $ encodedKey )) {
117+ CLI ::write ('Error in setting new encryption key to .env file. ' );
98118
99119 return EXIT_ERROR ;
100120 }
@@ -114,7 +134,7 @@ public function run(array $params)
114134 /**
115135 * Generates a key and encodes it.
116136 */
117- protected function generateRandomKey (string $ prefix , int $ length ): string
137+ private function generateRandomKey (string $ prefix , int $ length ): string
118138 {
119139 $ key = Encryption::createKey ($ length );
120140
@@ -125,37 +145,10 @@ protected function generateRandomKey(string $prefix, int $length): string
125145 return 'base64: ' . base64_encode ($ key );
126146 }
127147
128- /**
129- * Sets the new encryption key in your .env file.
130- *
131- * @param array<int|string, string|null> $params
132- */
133- protected function setNewEncryptionKey (string $ key , array $ params ): bool
134- {
135- $ currentKey = env ('encryption.key ' , '' );
136-
137- if ($ currentKey !== '' && ! $ this ->confirmOverwrite ($ params )) {
138- // Not yet testable since it requires keyboard input
139- return false ; // @codeCoverageIgnore
140- }
141-
142- return $ this ->writeNewEncryptionKeyToFile ($ currentKey , $ key );
143- }
144-
145- /**
146- * Checks whether to overwrite existing encryption key.
147- *
148- * @param array<int|string, string|null> $params
149- */
150- protected function confirmOverwrite (array $ params ): bool
151- {
152- return (array_key_exists ('force ' , $ params ) || CLI ::getOption ('force ' )) || CLI ::prompt ('Overwrite existing key? ' , ['n ' , 'y ' ]) === 'y ' ;
153- }
154-
155148 /**
156149 * Writes the new encryption key to .env file.
157150 */
158- protected function writeNewEncryptionKeyToFile (string $ oldKey , string $ newKey ): bool
151+ private function writeNewEncryptionKeyToFile (string $ oldKey , string $ newKey ): bool
159152 {
160153 $ baseEnv = ROOTPATH . 'env ' ;
161154 $ envFile = ((new Paths ())->envDirectory ?? ROOTPATH ) . '.env ' ; // @phpstan-ignore nullCoalesce.property
@@ -164,7 +157,6 @@ protected function writeNewEncryptionKeyToFile(string $oldKey, string $newKey):
164157 if (! is_file ($ baseEnv )) {
165158 CLI ::write ('Both default shipped `env` file and custom `.env` are missing. ' , 'yellow ' );
166159 CLI ::write ('Here \'s your new key instead: ' . CLI ::color ($ newKey , 'yellow ' ));
167- CLI ::newLine ();
168160
169161 return false ;
170162 }
@@ -195,7 +187,7 @@ protected function writeNewEncryptionKeyToFile(string $oldKey, string $newKey):
195187 /**
196188 * Get the regex of the current encryption key.
197189 */
198- protected function keyPattern (string $ oldKey ): string
190+ private function keyPattern (string $ oldKey ): string
199191 {
200192 $ escaped = preg_quote ($ oldKey , '/ ' );
201193
0 commit comments