From c984d0bb458f605942f57f0a6263e58eb794e7f3 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 27 Apr 2023 13:30:45 +0300 Subject: [PATCH 001/401] Add translation for Bulgarian language --- src/Language/bg/Auth.php | 105 ++++++++++++++++++ .../Language/AbstractTranslationTestCase.php | 1 + tests/Language/BulgarianTranslationTest.php | 21 ++++ 3 files changed, 127 insertions(+) create mode 100644 src/Language/bg/Auth.php create mode 100644 tests/Language/BulgarianTranslationTest.php diff --git a/src/Language/bg/Auth.php b/src/Language/bg/Auth.php new file mode 100644 index 000000000..751b68146 --- /dev/null +++ b/src/Language/bg/Auth.php @@ -0,0 +1,105 @@ + '{0} не е валиден аутентикатор.', + 'unknownUserProvider' => 'Не може да се определи използваният потребителски доставчик.', + 'invalidUser' => 'Не може да се намери посоченият потребител.', + 'bannedUser' => 'Не може да влезете в профила си, тъй като сте баннати.', + 'logOutBannedUser' => 'Изведен сте от профила ви, защото сте баннати.', + 'badAttempt' => 'Не може да влезете в профила си. Моля, проверете вашите потребителски данни.', + 'noPassword' => 'Не може да се потвърди потребителски профил без парола.', + 'invalidPassword' => 'Не може да влезете в профила си. Моля, проверете вашата парола.', + 'noToken' => 'Всяка заявка трябва да съдържа носител на токен в {0} заглавната си част.', + 'badToken' => 'Токенът за достъп не е валиден.', + 'oldToken' => 'Токенът за достъп е изтекъл.', + 'noUserEntity' => 'Потребителското съдържание трябва да бъде предоставено за потвърждение на паролата.', + 'invalidEmail' => 'Не може да се потвърди, че имейл адресът съвпада с имейл адреса от записа.', + 'unableSendEmailToUser' => 'Съжаляваме, имаше проблем с изпращането на имейла. Не можем да изпратим имейл до "{0}".', + 'throttled' => 'Твърде много заявки са направени от този IP адрес. Може да опитате отново след {0} секунди.', + 'notEnoughPrivilege' => 'Нямате необходимите права за изпълнение на желаната операция.', + // JWT Изключения + 'invalidJWT' => 'Токенът е невалиден.', + 'expiredJWT' => 'Токенът е изтекъл.', + 'beforeValidJWT' => 'Токенът все още не е наличен.', + + 'email' => 'Адрес на електронна поща', + 'username' => 'Потребителско име', + 'password' => 'Парола', + 'passwordConfirm' => 'Парола (отново)', + 'haveAccount' => 'Вече имате акаунт?', + + // Бутони + 'confirm' => 'Потвърди', + 'send' => 'Изпрати', + + // Регистрация + 'register' => 'Регистрация', + 'registerDisabled' => 'Регистрацията в момента не е позволена.', + 'registerSuccess' => 'Добре дошли!', + + // Вход + 'login' => 'Вход', + 'needAccount' => 'Нуждаете се от акаунт?', + 'rememberMe' => 'Запомни ме?', + 'forgotPassword' => 'Забравена парола?', + 'useMagicLink' => 'Използвайте линк за вход', + 'magicLinkSubject' => 'Вашият линк за вход', + 'magicTokenNotFound' => 'Не може да се потвърди линка.', + 'magicLinkExpired' => 'Съжаляваме, линкът е изтекъл.', + 'checkYourEmail' => 'Проверете вашия имейл!', + 'magicLinkDetails' => 'Току що ви изпратихме имейл с линк за вход. Линкът ще бъде валиден само {0} минути.', + 'successLogout' => 'Успешно излязохте от системата.', + + // Пароли + 'errorPasswordLength' => 'Паролите трябва да са поне {0, number} символа дълги.', + 'suggestPasswordLength' => 'Паролите с дължина до 255 символа, наричани "паролни изречения", правят паролите по-сигурни и лесни за запомняне.', + 'errorPasswordCommon' => 'Паролата не трябва да е общоизвестна.', + 'suggestPasswordCommon' => 'Проверихме паролата срещу над 65 000 общоизвестни пароли или пароли, които са били изложени след хакерски атаки.', + 'errorPasswordPersonal' => 'Паролите не могат да съдържат лична информация.', + 'suggestPasswordPersonal' => 'Вариации на имейл адреса или потребителското име не трябва да се използват за пароли.', + 'errorPasswordTooSimilar' => 'Паролата е твърде подобна на потребителското име.', + 'suggestPasswordTooSimilar' => 'Не използвайте части от потребителското си име в паролата си.', + 'errorPasswordPwned' => 'Паролата {0} е била компрометирана в следствие на нарушения в сигурността на данните и е била видяна {1, number} пъти в {2} от компрометираните пароли.', + 'suggestPasswordPwned' => '{0} никога не трябва да се използва като парола. Ако я използвате някъде, трябва да я сменете веднага.', + 'errorPasswordEmpty' => 'Изисква се парола.', + 'errorPasswordTooLongBytes' => 'Паролата не може да бъде по-дълга от {param} байта.', + 'passwordChangeSuccess' => 'Паролата беше успешно променена.', + 'userDoesNotExist' => 'Паролата не беше променена. Потребителят не съществува.', + 'resetTokenExpired' => 'Съжаляваме. Вашият токен за нулиране на паролата е изтекъл.', + + // Глобални променливи за електронна поща + 'emailInfo' => 'Някаква информации за потребителя:', + 'emailIpAddress' => 'IP Адрес:', + 'emailDevice' => 'Устройство:', + 'emailDate' => 'Дата:', + + // Двуфакторна автентикация (2FA) + 'email2FATitle' => 'Двуфакторна автентикация', + 'confirmEmailAddress' => 'Потвърдете Вашата електронна поща.', + 'emailEnterCode' => 'Потвърдете Вашата електронна поща', + 'emailConfirmCode' => 'Въведете 6-цифрен код, който изпратихме на Вашата електронна поща.', + 'email2FASubject' => 'Вашият код за автентикация', + 'email2FAMailBody' => 'Вашият код за автентикация е:', + 'invalid2FAToken' => 'Грешен код.', + 'need2FA' => 'Трябва да завършите двуфакторна верификация.', + 'needVerification' => 'Проверете Вашата електронна поща, за да завършите активацията на профила.', + + // Активация + 'emailActivateTitle' => 'Активиране по имейл', + 'emailActivateBody' => 'Изпратихме ви имейл с код за потвърждение на вашия имейл адрес. Копирайте този код и го поставете по-долу.', + 'emailActivateSubject' => 'Вашият код за активация', + 'emailActivateMailBody' => 'Моля, използвайте по-долу посочения код за активиране на акаунта си и започнете да използвате сайта.', + 'invalidActivateToken' => 'Кода е невалиден.', + 'needActivate' => 'Трябва да завършите регистрацията си, като потвърдите кода, изпратен на вашия имейл адрес.', + 'activationBlocked' => 'Трябва да активирате акаунта си, преди да влезете.', + + // Групи + 'unknownGroup' => '{0} не е валидна група.', + 'missingTitle' => 'Групите трябва да имат заглавие.', + + // Разрешения + 'unknownPermission' => '{0} не е валидно разрешение.', +]; diff --git a/tests/Language/AbstractTranslationTestCase.php b/tests/Language/AbstractTranslationTestCase.php index 05097897c..13387af8c 100644 --- a/tests/Language/AbstractTranslationTestCase.php +++ b/tests/Language/AbstractTranslationTestCase.php @@ -48,6 +48,7 @@ abstract class AbstractTranslationTestCase extends TestCase */ public static array $locales = [ // ArabicTranslationTest::class => 'ar', + BulgarianTranslationTest::class => 'bg', // BosnianTranslationTest::class => 'bs', // CzechTranslationTest::class => 'cs', GermanTranslationTest::class => 'de', diff --git a/tests/Language/BulgarianTranslationTest.php b/tests/Language/BulgarianTranslationTest.php new file mode 100644 index 000000000..1125d144e --- /dev/null +++ b/tests/Language/BulgarianTranslationTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Language; + +/** + * @internal + */ +final class BulgarianTranslationTest extends AbstractTranslationTestCase +{ +} From 21888bfbd7e22de2382e9c03176d29a95ecea48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20=C3=96zkartal?= Date: Wed, 26 Apr 2023 23:58:26 +0300 Subject: [PATCH 002/401] lang: tr - added translations --- src/Language/tr/Auth.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 57f515e31..7cefce0aa 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -7,8 +7,8 @@ 'unknownAuthenticator' => '{0} geçerli bir kimlik doğrulayıcı değil.', 'unknownUserProvider' => 'Kullanılacak Kullanıcı Sağlayıcı belirlenemiyor.', 'invalidUser' => 'Belirtilen kullanıcı bulunamadı.', - 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', - 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', + 'bannedUser' => 'Bu hesap yasaklandı. Şu anda giriş yapamazsınız.', + 'logOutBannedUser' => 'Bu hesap yasaklandığından dolayı oturumunuz kapatıldı.', 'badAttempt' => 'Oturumunuz açılamıyor. Lütfen kimlik bilgilerinizi kontrol edin.', 'noPassword' => 'Parola olmadan bir kullanıcı doğrulanamaz.', 'invalidPassword' => 'Oturumunuz açılamıyor. Lütfen şifrenizi kontrol edin.', @@ -21,9 +21,9 @@ 'throttled' => 'Bu IP adresinden çok fazla istek yapıldı. {0} saniye sonra tekrar deneyebilirsiniz.', 'notEnoughPrivilege' => 'İstediğiniz işlemi gerçekleştirmek için gerekli izne sahip değilsiniz.', // JWT Exceptions - 'invalidJWT' => '(To be translated) The token is invalid.', - 'expiredJWT' => '(To be translated) The token has expired.', - 'beforeValidJWT' => '(To be translated) The token is not yet available.', + 'invalidJWT' => 'Token geçersiz.', + 'expiredJWT' => 'Tokenin süresi dolmuş.', + 'beforeValidJWT' => 'Token henüz geçerli değil.', 'email' => 'E-posta Adresi', 'username' => 'Kullanıcı Adı', @@ -65,7 +65,7 @@ 'errorPasswordPwned' => '{0} şifresi, bir veri ihlali nedeniyle açığa çıktı ve güvenliği ihlal edilmiş şifrelerin {2} tanesinde {1, sayı} kez görüldü.', 'suggestPasswordPwned' => '{0} asla şifre olarak kullanılmamalıdır. Herhangi bir yerde kullanıyorsanız hemen değiştirin.', 'errorPasswordEmpty' => 'Şifre gerekli.', - 'errorPasswordTooLongBytes' => '(To be translated) Password cannot exceed {param} bytes in length.', + 'errorPasswordTooLongBytes' => 'Şifre uzunluğu {param} baytı geçemez.', 'passwordChangeSuccess' => 'Şifre başarıyla değiştirildi.', 'userDoesNotExist' => 'Şifre değiştirilmedi. Kullanıcı yok.', 'resetTokenExpired' => 'Üzgünüz. Sıfırlama anahtarınızın süresi doldu.', @@ -94,7 +94,7 @@ 'emailActivateMailBody' => 'Hesabınızı etkinleştirmek ve siteyi kullanmaya başlamak için lütfen aşağıdaki kodu kullanın.', 'invalidActivateToken' => 'Kod yanlıştı.', 'needActivate' => 'E-posta adresinize gönderilen kodu onaylayarak kaydınızı tamamlamanız gerekmektedir.', - 'activationBlocked' => '(to be translated) You must activate your account before logging in.', + 'activationBlocked' => 'Giriş yapmadan önce hesabınızı etkinleştirmeniz gerekmektedir.', // Groups 'unknownGroup' => '{0} geçerli bir grup değil.', From 6a463a0737f56ebd9a9f0b5a45170d680d92bb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20=C3=96zkartal?= Date: Wed, 26 Apr 2023 23:59:15 +0300 Subject: [PATCH 003/401] lang: tr - misunderstood string corrected --- src/Language/tr/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 7cefce0aa..7a2b76710 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -38,7 +38,7 @@ // Registration 'register' => 'Kayıt Ol', 'registerDisabled' => 'Kayıt işlemine şu anda izin verilmiyor.', - 'registerSuccess' => 'Gemiye Hoşgeldiniz!', + 'registerSuccess' => 'Aramıza Hoşgeldiniz!', // Login 'login' => 'Giriş', From 604eac1f20bb8f938adddcd38cf491ce21b234d2 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 27 Apr 2023 23:36:40 +0330 Subject: [PATCH 004/401] add `random_string` to ignoreErrors because shield uses type `nozero` --- phpstan.neon.dist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5a406f846..75b50e42f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -12,6 +12,10 @@ parameters: ignoreErrors: - '#Call to an undefined method CodeIgniter\\Database\\ConnectionInterface::[A-Za-z].+\(\)#' - '#Cannot access property [\$a-z_]+ on (array|object)#' + - + message: '#Call to deprecated function random_string\(\):#' + count: 5 + path: * universalObjectCratesClasses: - CodeIgniter\Entity - CodeIgniter\Entity\Entity From 82f983a38bee63d40ef411ef8e9a1adc7d83434c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 27 Apr 2023 23:38:17 +0330 Subject: [PATCH 005/401] fix: `$credentials` *NEVER* given --- src/Controllers/LoginController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index 8c5bc445d..dc78426d7 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -51,7 +51,8 @@ public function loginAction(): RedirectResponse return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } - $credentials = $this->request->getPost(setting('Auth.validFields')); + /** @var array $credentials */ + $credentials = $this->request->getPost(setting('Auth.validFields')) ?? []; $credentials = array_filter($credentials); $credentials['password'] = $this->request->getPost('password'); $remember = (bool) $this->request->getPost('remember'); From 397e519c2ba36fcd854bed570576c80eb0fa3e4e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Fri, 28 Apr 2023 06:31:02 +0330 Subject: [PATCH 006/401] add all paths for prevent accidental suppression in the project --- phpstan.neon.dist | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 75b50e42f..8c77114cc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -14,8 +14,12 @@ parameters: - '#Cannot access property [\$a-z_]+ on (array|object)#' - message: '#Call to deprecated function random_string\(\):#' - count: 5 - path: * + paths: + - src/Authentication/Actions/Email2FA.php + - src/Authentication/Actions/EmailActivator.php + - src/Controllers/MagicLinkController.php + - src/Models/TokenLoginModel.php + - src/Models/UserIdentityModel.php universalObjectCratesClasses: - CodeIgniter\Entity - CodeIgniter\Entity\Entity From 4a01eec2ec6150a0dcc4ca6ff308f3fc5e8e924b Mon Sep 17 00:00:00 2001 From: sarog Date: Sun, 30 Apr 2023 22:15:09 +0300 Subject: [PATCH 007/401] fix nothing personal validator error with bad email value --- .../Passwords/NothingPersonalValidator.php | 13 ++++-- tests/Controllers/RegisterTest.php | 18 +++++++++ tests/Unit/NothingPersonalValidatorTest.php | 40 +++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/Authentication/Passwords/NothingPersonalValidator.php b/src/Authentication/Passwords/NothingPersonalValidator.php index 6a4102e6b..ef11ffbae 100644 --- a/src/Authentication/Passwords/NothingPersonalValidator.php +++ b/src/Authentication/Passwords/NothingPersonalValidator.php @@ -72,10 +72,15 @@ protected function isNotPersonal(string $password, ?User $user): bool $needles = $this->strip_explode($userName); // extract local-part and domain parts from email as separate needles - [ - $localPart, - $domain, - ] = explode('@', $email); + if (str_contains($email, '@')) { + [ + $localPart, + $domain, + ] = explode('@', $email); + } else { + $localPart = $email; + $domain = null; + } // might be john.doe@example.com and we want all the needles we can get $emailParts = $this->strip_explode($localPart); if (! empty($domain)) { diff --git a/tests/Controllers/RegisterTest.php b/tests/Controllers/RegisterTest.php index 320db297f..3b8e73afe 100644 --- a/tests/Controllers/RegisterTest.php +++ b/tests/Controllers/RegisterTest.php @@ -294,6 +294,24 @@ public function testRegisterActionRedirectsIfLoggedIn(): void $result->assertRedirectTo(config('Auth')->registerRedirect()); } + public function testRegisterActionWithBadEmailValue(): void + { + $result = $this->withSession()->post('/register', [ + 'username' => 'JohnDoe', + 'email' => 'john.doe', + 'password' => '123456789aa', + 'password_confirm' => '123456789aa', + ]); + + $result->assertStatus(302); + $result->assertRedirect(); + $result->assertSessionMissing('error'); + $result->assertSessionHas( + 'errors', + ['email' => 'The Email Address field must contain a valid email address.'] + ); + } + protected function setupConfig(): void { $config = config('Validation'); diff --git a/tests/Unit/NothingPersonalValidatorTest.php b/tests/Unit/NothingPersonalValidatorTest.php index c1bc93c1f..50453f5e9 100644 --- a/tests/Unit/NothingPersonalValidatorTest.php +++ b/tests/Unit/NothingPersonalValidatorTest.php @@ -287,4 +287,44 @@ public static function maxSimilarityProvider() ], ]; } + + /** + * @dataProvider badEmailsProvider + * + * @param string $email + * @param bool $expected + */ + public function testCheckPasswordWithBadEmail(string $email, bool $expected): void + { + $config = new Auth(); + $this->validator = new NothingPersonalValidator($config); + + $user = new User([ + 'username' => 'CaptainJoe', + 'email' => $email, + ]); + + $password = '123456789a'; + + $result = $this->validator->check($password, $user); + + $this->assertSame($expected, $result->isOK()); + } + + public static function badEmailsProvider() + { + return [ + [ + 'test', + true, + ], [ + 'test@example', + true, + ], + [ + 'test@example.com', + true, + ], + ]; + } } From 57f35b98af19d4dd0d888bf76c56216c3bbb7a72 Mon Sep 17 00:00:00 2001 From: sarog Date: Mon, 1 May 2023 00:51:34 +0300 Subject: [PATCH 008/401] fix after phpcs check --- tests/Unit/NothingPersonalValidatorTest.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/Unit/NothingPersonalValidatorTest.php b/tests/Unit/NothingPersonalValidatorTest.php index 50453f5e9..a0d3848d5 100644 --- a/tests/Unit/NothingPersonalValidatorTest.php +++ b/tests/Unit/NothingPersonalValidatorTest.php @@ -290,14 +290,11 @@ public static function maxSimilarityProvider() /** * @dataProvider badEmailsProvider - * - * @param string $email - * @param bool $expected */ public function testCheckPasswordWithBadEmail(string $email, bool $expected): void { - $config = new Auth(); - $this->validator = new NothingPersonalValidator($config); + $config = new Auth(); + $this->validator = new NothingPersonalValidator($config); $user = new User([ 'username' => 'CaptainJoe', From de02bd7465aaad64ebc67fcd010ae723f1cda330 Mon Sep 17 00:00:00 2001 From: Donatas Glodenis Date: Mon, 1 May 2023 10:34:16 +0300 Subject: [PATCH 009/401] lang: [lt] add Lithuanian localization --- src/Language/lt/Auth.php | 105 ++++++++++++++++++ .../Language/AbstractTranslationTestCase.php | 2 +- tests/Language/LithuanianTranslationTest.php | 21 ++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/Language/lt/Auth.php create mode 100644 tests/Language/LithuanianTranslationTest.php diff --git a/src/Language/lt/Auth.php b/src/Language/lt/Auth.php new file mode 100644 index 000000000..eed03fb6f --- /dev/null +++ b/src/Language/lt/Auth.php @@ -0,0 +1,105 @@ + '{0} nėra teisingas autentifikatorius.', + 'unknownUserProvider' => 'Nepavyksta nustatyti, kokį reikėtų naudoti vartotojų šaltinį.', + 'invalidUser' => 'Nepavyksta rasti nurodyto vartotojo.', + 'bannedUser' => 'Jūsų vartotojas uždraustas, todėl prisijungti nepavyks.', + 'logOutBannedUser' => 'Sistema jus išregistravo, nes Jūsų vartotojas uždraustas.', + 'badAttempt' => 'Nepavyksta Jūsų prijungti. Patikrinkite prisijungimo duomenis.', + 'noPassword' => 'Negalima patvirtinti vartotojo be slaptažodžio.', + 'invalidPassword' => 'Nepavyksta Jūsų prijungti. Patikrinkite slaptažodį.', + 'noToken' => 'Kiekviena užklausa turi turėti prieigos raštą antraštėje {0}.', + 'badToken' => 'Prieigos raktas neteisingas.', + 'oldToken' => 'Prieigos raktas nebegalioja.', + 'noUserEntity' => 'Slaptažodžio patikrinimui turi būti pateiktas vartotojo subjektas.', + 'invalidEmail' => 'Neišeina patvirtinti, kad pateiktas el. pašto adresas atitinka turimą el. pašto įrašą.', + 'unableSendEmailToUser' => 'Deja, nepavyko išsiųsti el. laiško. Nepavyko išsiųsti laiško adresu "{0}".', + 'throttled' => 'Per daug užklausų iš šio IP adreso. Galite pamėginti iš naujo po {0} sekundžių.', + 'notEnoughPrivilege' => 'Neturite operacijai atlikti užtektinų leidimų.', + // JWT Exceptions + 'invalidJWT' => 'Raktas neteisingai suformuotas.', + 'expiredJWT' => 'Rakto galiojimas pasibaigęs.', + 'beforeValidJWT' => 'Rakto kol kas dar nėra.', + + 'email' => 'El. pašto adresas', + 'username' => 'Vartotojo vardas', + 'password' => 'Slaptažodis', + 'passwordConfirm' => 'Slaptažodis (pakartoti)', + 'haveAccount' => 'Jau turite paskyrą?', + + // Buttons + 'confirm' => 'Patvirtinti', + 'send' => 'Siųsti', + + // Registration + 'register' => 'Registruotis', + 'registerDisabled' => 'Šiuo metu registracija neleidžiama.', + 'registerSuccess' => 'Sveiki prisijungę!', + + // Login + 'login' => 'Prisijungimas', + 'needAccount' => 'Reikia paskyros?', + 'rememberMe' => 'Atsiminti mane?', + 'forgotPassword' => 'Pamiršote slaptažodį?', + 'useMagicLink' => 'Naudoti prisijungimo nuorodą', + 'magicLinkSubject' => 'Jūsų prisijungimo nuoroda', + 'magicTokenNotFound' => 'Nepavyksta patvirtinti nuorodos.', + 'magicLinkExpired' => 'Deja, nuorodos galiojimas baigėsi.', + 'checkYourEmail' => 'Patikrinkite savo el. paštą!', + 'magicLinkDetails' => 'Mes ką tik išsiuntėme Jums el. laišką su prisijungimo nuoroda. Ji galios tiki {0} minučių(-es).', + 'successLogout' => 'Jūs sėkmingai atsijungėte.', + + // Passwords + 'errorPasswordLength' => 'Slaptažodis turi būti bent {0, number} ženklų ilgio.', + 'suggestPasswordLength' => 'Prisijungimo frazės - iki 255 ženklų ilgio - yra kur kas saugesni slaptažodžiai kuriuos lengva įsiminti.', + 'errorPasswordCommon' => 'Slaptažodis neturi būti paprastas žodis.', + 'suggestPasswordCommon' => 'Slaptažodis buvo patikrintas lyginant jį su daugiau nei 65 tūkst. įprastai naudojamų slaptažodžių ir slaptažodžių, kurie buvo išviešinti nulaužus sistemas.', + 'errorPasswordPersonal' => 'Slaptažodyje neturi būti įterpta asmeninės informacijos.', + 'suggestPasswordPersonal' => 'Slaptažodyje neturi būti naudojami menkai pakeisti el. pašto adreso arba vartotojo vardo variantai.', + 'errorPasswordTooSimilar' => 'Slaptažodis pernelyg panašus į vartotojo vardą.', + 'suggestPasswordTooSimilar' => 'Nenaudokite vartotojo vardo dalių slaptažodyje.', + 'errorPasswordPwned' => 'Slaptažodis {0} buvo išviešintas po internetinės sistemos nulaužimo ir buvo paskelbtas {1, number} kartus {2} nulaužtų slaptažodžių sąrašuose.', + 'suggestPasswordPwned' => '{0} neturi būti naudojamas kaip slaptažodis. Jei jį naudojate bet kur, tuoj pat pakeiskite.', + 'errorPasswordEmpty' => 'Reikia slaptažodžio.', + 'errorPasswordTooLongBytes' => 'Slaptažodis neturi būti ilgesnis nei {param} baitų(-ai).', + 'passwordChangeSuccess' => 'Slaptažodis sėkmingai pakeistas', + 'userDoesNotExist' => 'Slaptažodis nepakeistas. Tokio vartotojo nėra', + 'resetTokenExpired' => 'Deja, Jūsų slaptažodžio atkūrimo raktas nebegalioja.', + + // Email Globals + 'emailInfo' => 'Šiek tiek informacijos apie asmenį:', + 'emailIpAddress' => 'IP adresas:', + 'emailDevice' => 'Įrenginys:', + 'emailDate' => 'Data:', + + // 2FA + 'email2FATitle' => 'Dviejų faktorių autentifikacija', + 'confirmEmailAddress' => 'Patvirtinkite savo el. pašto adresą.', + 'emailEnterCode' => 'Patvirtinkite savo el. paštą', + 'emailConfirmCode' => 'Įrašykite 6 ženklų kodą, kurį ką tik išsiuntėme Jums el. paštu.', + 'email2FASubject' => 'Jūsų autentifikacijos kodas', + 'email2FAMailBody' => 'Jūsų autentifikacijos kodas yra:', + 'invalid2FAToken' => 'Kodas buvo neteisingas.', + 'need2FA' => 'Turite užbaigti dviejų faktorių autentifikaciją.', + 'needVerification' => 'Norėdami užbaigti paskyros aktyvavimą, patikrinkite savo el. pašto dėžutę.', + + // Activate + 'emailActivateTitle' => 'Aktyvavimas el. paštu', + 'emailActivateBody' => 'Mes ką tik išsiuntėme Jums el. laišką su kodu el. pašto adreso patvirtinimui. Nukopijuokite tą kodą ir įterpkite žemiau.', + 'emailActivateSubject' => 'Jūsų aktyvavimo kodas', + 'emailActivateMailBody' => 'Prašome naudoti žemiau esantį kodą paskyros aktyvavimui. Tuomet galėsite pradėti naudoti mūsų svetainę.', + 'invalidActivateToken' => 'Kodas buvo neteisingas.', + 'needActivate' => 'Turite baigti registraciją panaudodami kodą, išsiųstą Jums el. pašto adresu.', + 'activationBlocked' => 'Prieš prisijungdami turite aktyvuoti paskyrą.', + + // Groups + 'unknownGroup' => '{0} nėra egzistuojanti grupė.', + 'missingTitle' => 'Grupė turi turėti pavadinimą.', + + // Permissions + 'unknownPermission' => '{0} nėra žinomas leidimo tipas.', +]; diff --git a/tests/Language/AbstractTranslationTestCase.php b/tests/Language/AbstractTranslationTestCase.php index 13387af8c..75feb3a5c 100644 --- a/tests/Language/AbstractTranslationTestCase.php +++ b/tests/Language/AbstractTranslationTestCase.php @@ -60,7 +60,7 @@ abstract class AbstractTranslationTestCase extends TestCase ItalianTranslationTest::class => 'it', JapaneseTranslationTest::class => 'ja', // KoreanTranslationTest::class => 'ko', - // LithuanianTranslationTest::class => 'lt', + LithuanianTranslationTest::class => 'lt', // LatvianTranslationTest::class => 'lv', // MalayalamTranslationTest::class => 'ml', // DutchTranslationTest::class => 'nl', diff --git a/tests/Language/LithuanianTranslationTest.php b/tests/Language/LithuanianTranslationTest.php new file mode 100644 index 000000000..b9a5372f0 --- /dev/null +++ b/tests/Language/LithuanianTranslationTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Language; + +/** + * @internal + */ +final class LithuanianTranslationTest extends AbstractTranslationTestCase +{ +} From b704ca5eaac704710d104875eec332c8646dc3be Mon Sep 17 00:00:00 2001 From: Bernhard Enders Date: Tue, 2 May 2023 12:27:54 -0300 Subject: [PATCH 010/401] Fixed typo in getMaxLengthRule method Changed method name from getMaxLenghtRule to getMaxLengthRule --- src/Authentication/Passwords.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Passwords.php b/src/Authentication/Passwords.php index 3eca6c71f..3a5b7b041 100644 --- a/src/Authentication/Passwords.php +++ b/src/Authentication/Passwords.php @@ -143,7 +143,7 @@ public function check(string $password, ?User $user = null): Result /** * Returns the validation rule for max length. */ - public static function getMaxLenghtRule(): string + public static function getMaxLengthRule(): string { if (config('Auth')->hashAlgorithm === PASSWORD_BCRYPT) { return 'max_byte[72]'; From c14a141b722679658f2033634addb612db7a3eae Mon Sep 17 00:00:00 2001 From: Bernhard Enders Date: Tue, 2 May 2023 12:29:34 -0300 Subject: [PATCH 011/401] Fixed typo in name getMaxLengthRule --- src/Controllers/LoginController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index dc78426d7..dd27b97bf 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -93,7 +93,7 @@ protected function getValidationRules(): array ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLenghtRule(), + 'rules' => 'required|' . Passwords::getMaxLengthRule(), 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], From 0a63c53f5cf1f5f410823050c87d5e6e2d65fc1d Mon Sep 17 00:00:00 2001 From: Bernhard Enders Date: Tue, 2 May 2023 12:31:05 -0300 Subject: [PATCH 012/401] Fixed typo in getMaxLengthRule --- src/Controllers/RegisterController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 6f394faa0..500b1ea36 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -197,7 +197,7 @@ protected function getValidationRules(): array ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLenghtRule() . '|strong_password', + 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password', 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], From c858f9dfe350005af7a1958d58c928fb76eaedc1 Mon Sep 17 00:00:00 2001 From: sarog Date: Sun, 7 May 2023 18:24:14 +0300 Subject: [PATCH 013/401] add login link to magic link form view --- src/Language/bg/Auth.php | 1 + src/Language/de/Auth.php | 1 + src/Language/en/Auth.php | 1 + src/Language/es/Auth.php | 1 + src/Language/fa/Auth.php | 1 + src/Language/fr/Auth.php | 1 + src/Language/id/Auth.php | 1 + src/Language/it/Auth.php | 3 ++- src/Language/ja/Auth.php | 1 + src/Language/lt/Auth.php | 1 + src/Language/pt-BR/Auth.php | 1 + src/Language/pt/Auth.php | 1 + src/Language/sk/Auth.php | 1 + src/Language/sr/Auth.php | 1 + src/Language/sv-SE/Auth.php | 1 + src/Language/tr/Auth.php | 1 + src/Language/uk/Auth.php | 1 + src/Views/magic_link_form.php | 2 ++ tests/Controllers/MagicLinkTest.php | 6 ++++++ 19 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Language/bg/Auth.php b/src/Language/bg/Auth.php index 751b68146..08d507169 100644 --- a/src/Language/bg/Auth.php +++ b/src/Language/bg/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Запомни ме?', 'forgotPassword' => 'Забравена парола?', 'useMagicLink' => 'Използвайте линк за вход', + 'usePassword' => 'Използвайте парола', 'magicLinkSubject' => 'Вашият линк за вход', 'magicTokenNotFound' => 'Не може да се потвърди линка.', 'magicLinkExpired' => 'Съжаляваме, линкът е изтекъл.', diff --git a/src/Language/de/Auth.php b/src/Language/de/Auth.php index a681516bd..dbc1cf922 100644 --- a/src/Language/de/Auth.php +++ b/src/Language/de/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Angemeldet bleiben', 'forgotPassword' => 'Passwort vergessen?', 'useMagicLink' => 'Einen Login-Link verwenden', + 'usePassword' => 'Ein Passwort verwenden', 'magicLinkSubject' => 'Ihr Login-Link', 'magicTokenNotFound' => 'Der Link konnte nicht verifiziert werden.', 'magicLinkExpired' => 'Sorry, der Link ist abgelaufen.', diff --git a/src/Language/en/Auth.php b/src/Language/en/Auth.php index 363fd4af7..0f1a1862f 100644 --- a/src/Language/en/Auth.php +++ b/src/Language/en/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Remember me?', 'forgotPassword' => 'Forgot your password?', 'useMagicLink' => 'Use a Login Link', + 'usePassword' => 'Use a Password', 'magicLinkSubject' => 'Your Login Link', 'magicTokenNotFound' => 'Unable to verify the link.', 'magicLinkExpired' => 'Sorry, link has expired.', diff --git a/src/Language/es/Auth.php b/src/Language/es/Auth.php index 2cf2c6211..38d477763 100644 --- a/src/Language/es/Auth.php +++ b/src/Language/es/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Recordarme', 'forgotPassword' => '¿Olvidaste tu contraseña', 'useMagicLink' => 'Usar un enlace de inicio de sesión', + 'usePassword' => 'Usar una contraseña', 'magicLinkSubject' => 'Tu enlace de inicio de sesión', 'magicTokenNotFound' => 'No se puede verificar el enlace.', 'magicLinkExpired' => 'Lo siento, el enlace ha caducado.', diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index 26d242525..e7a021a52 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'مرا به خاطر بسپار؟', 'forgotPassword' => 'رمز عبور را فراموش کرده اید؟', 'useMagicLink' => 'از لینک ورود استفاده کنید', + 'usePassword' => 'استفاده از رمز عبور', 'magicLinkSubject' => 'لینک ورود شما', 'magicTokenNotFound' => 'تایید لینک ممکن نیست.', 'magicLinkExpired' => 'متاسفانه, لینک منقضی شده است.', diff --git a/src/Language/fr/Auth.php b/src/Language/fr/Auth.php index b43a354b0..0d51d0bb0 100644 --- a/src/Language/fr/Auth.php +++ b/src/Language/fr/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Se souvenir de moi', 'forgotPassword' => 'Mot de passe oublié ?', 'useMagicLink' => 'Utiliser un lien de connexion', + 'usePassword' => 'Utiliser un mot de passe', 'magicLinkSubject' => 'Votre lien de connexion', 'magicTokenNotFound' => 'Impossible de vérifier le lien.', 'magicLinkExpired' => 'Désolé, le lien a expiré.', diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index f2be28a35..f389a3623 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Ingat saya?', 'forgotPassword' => 'Lupa kata sandi?', 'useMagicLink' => 'Gunakan tautan masuk', + 'usePassword' => 'Gunakan kata sandi', 'magicLinkSubject' => 'Tautan masuk Anda', 'magicTokenNotFound' => 'Tidak dapat memverifikasi tautan.', 'magicLinkExpired' => 'Maaf, tautan sudah tidak berlaku.', diff --git a/src/Language/it/Auth.php b/src/Language/it/Auth.php index af2b41e24..fc963259e 100644 --- a/src/Language/it/Auth.php +++ b/src/Language/it/Auth.php @@ -45,7 +45,8 @@ 'needAccount' => 'Hai bisogno di un account?', 'rememberMe' => 'Ricordami?', 'forgotPassword' => 'Password dimenticata?', - 'useMagicLink' => 'Usa un Login Link', + 'useMagicLink' => 'Usa un Login Link', + 'usePassword' => 'Usa una password', 'magicLinkSubject' => 'Il tuo Login Link', 'magicTokenNotFound' => 'Impossibile verificare il link.', 'magicLinkExpired' => 'Spiacente, il link è scaduto.', diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index 1ec2f346b..fa4416160 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'ログイン状態を保持する', // 'Remember me?' 'forgotPassword' => 'パスワードをお忘れの方', // 'Forgot your password?' 'useMagicLink' => 'ログインリンクを使用する', // 'Use a Login Link' + 'usePassword' => 'パスワードを使用する', // 'Use a Password' 'magicLinkSubject' => 'あなたのログインリンク', // 'Your Login Link' 'magicTokenNotFound' => 'リンクを確認できません。', // 'Unable to verify the link.' 'magicLinkExpired' => '申し訳ございません、リンクは切れています。', // 'Sorry, link has expired.' diff --git a/src/Language/lt/Auth.php b/src/Language/lt/Auth.php index eed03fb6f..209d4e23f 100644 --- a/src/Language/lt/Auth.php +++ b/src/Language/lt/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Atsiminti mane?', 'forgotPassword' => 'Pamiršote slaptažodį?', 'useMagicLink' => 'Naudoti prisijungimo nuorodą', + 'usePassword' => 'Naudoti slaptažodį', 'magicLinkSubject' => 'Jūsų prisijungimo nuoroda', 'magicTokenNotFound' => 'Nepavyksta patvirtinti nuorodos.', 'magicLinkExpired' => 'Deja, nuorodos galiojimas baigėsi.', diff --git a/src/Language/pt-BR/Auth.php b/src/Language/pt-BR/Auth.php index b2506bcdd..e1c31aaea 100644 --- a/src/Language/pt-BR/Auth.php +++ b/src/Language/pt-BR/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Lembrar de mim?', 'forgotPassword' => 'Esqueceu sua senha?', 'useMagicLink' => 'Use um Link de Login', + 'usePassword' => 'Use uma senha', 'magicLinkSubject' => 'Seu Link de Login', 'magicTokenNotFound' => 'Não foi possível verificar o link.', 'magicLinkExpired' => 'Desculpe, o link expirou.', diff --git a/src/Language/pt/Auth.php b/src/Language/pt/Auth.php index a5cfa8492..00cb2fd25 100644 --- a/src/Language/pt/Auth.php +++ b/src/Language/pt/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Lembrar', 'forgotPassword' => 'Esqueceu a sua password?', 'useMagicLink' => 'Use um Link de Login', + 'usePassword' => 'Use uma senha', 'magicLinkSubject' => 'O seu Link de Login', 'magicTokenNotFound' => 'Não foi possível verificar o link.', 'magicLinkExpired' => 'Desculpe, o link expirou.', diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index 3424e88c4..9bc77aa2d 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Zapamätať si ma?', 'forgotPassword' => 'Zabudli ste heslo?', 'useMagicLink' => 'Použiť odkaz na prihlásenie', + 'usePassword' => 'Použiť heslo', 'magicLinkSubject' => 'Váš odkaz na prihlásenie', 'magicTokenNotFound' => 'Odkaz sa nepodarilo overiť.', 'magicLinkExpired' => 'Ľutujeme, platnosť odkazu vypršala.', diff --git a/src/Language/sr/Auth.php b/src/Language/sr/Auth.php index 6c8e71f01..e388f20af 100644 --- a/src/Language/sr/Auth.php +++ b/src/Language/sr/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Zapmti me?', 'forgotPassword' => 'Zaboravljena lozinka?', 'useMagicLink' => 'Koristi pristupni link', + 'usePassword' => 'Koristi lozinku', 'magicLinkSubject' => 'Vaš pristupni link', 'magicTokenNotFound' => 'Nije moguća verifikacija linka.', 'magicLinkExpired' => 'Žao nam je, link je istekao.', diff --git a/src/Language/sv-SE/Auth.php b/src/Language/sv-SE/Auth.php index a37b09665..dff8841e0 100644 --- a/src/Language/sv-SE/Auth.php +++ b/src/Language/sv-SE/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Kom ihåg mig?', 'forgotPassword' => 'Glömt ditt lösenord?', 'useMagicLink' => 'Använd en login-länk', + 'usePassword' => 'Använd ett lösenord', 'magicLinkSubject' => 'Din login-länk', 'magicTokenNotFound' => 'Kan inte verifiera länken.', 'magicLinkExpired' => 'Tyvärr, länken har gått ut.', diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 7a2b76710..8acbb16c3 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Beni hatırla?', 'forgotPassword' => 'Şifrenizi mı unuttunuz?', 'useMagicLink' => 'Giriş Bağlantısı Kullanın', + 'usePassword' => 'Parola Kullanın', 'magicLinkSubject' => 'Giriş Bağlantınız', 'magicTokenNotFound' => 'Bağlantı doğrulanamıyor.', 'magicLinkExpired' => 'Üzgünüm, bağlantının süresi doldu.', diff --git a/src/Language/uk/Auth.php b/src/Language/uk/Auth.php index 56828330c..3f0285206 100644 --- a/src/Language/uk/Auth.php +++ b/src/Language/uk/Auth.php @@ -46,6 +46,7 @@ 'rememberMe' => 'Пам’ятай мене?', 'forgotPassword' => 'Забули пароль?', 'useMagicLink' => 'Використовуйте посилання для входу', + 'usePassword' => 'Використовуйте пароль', 'magicLinkSubject' => 'Ваше посилання для входу', 'magicTokenNotFound' => 'Неможливо перевірити посилання.', 'magicLinkExpired' => 'Вибачте, термін дії посилання закінчився.', diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index 35bfde4f1..474b2bda9 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -38,6 +38,8 @@ + +

diff --git a/tests/Controllers/MagicLinkTest.php b/tests/Controllers/MagicLinkTest.php index bec2370b1..b46b4b721 100644 --- a/tests/Controllers/MagicLinkTest.php +++ b/tests/Controllers/MagicLinkTest.php @@ -114,4 +114,10 @@ public function testMagicLinkVerifyPendingRegistrationActivation(): void ); $this->assertFalse(auth()->loggedIn()); } + + public function testUsePassword() + { + $result = $this->get('/login/magic-link'); + $this->assertStringContainsString(lang('Auth.usePassword'), $result->getBody()); + } } From e193c28c83b211a155b89405ac996755910235c4 Mon Sep 17 00:00:00 2001 From: sarog Date: Mon, 8 May 2023 17:18:15 +0300 Subject: [PATCH 014/401] rename language element from usePassword to backToLogin --- src/Language/bg/Auth.php | 2 +- src/Language/de/Auth.php | 2 +- src/Language/en/Auth.php | 2 +- src/Language/es/Auth.php | 2 +- src/Language/fa/Auth.php | 2 +- src/Language/fr/Auth.php | 2 +- src/Language/id/Auth.php | 2 +- src/Language/it/Auth.php | 2 +- src/Language/ja/Auth.php | 2 +- src/Language/lt/Auth.php | 2 +- src/Language/pt-BR/Auth.php | 2 +- src/Language/pt/Auth.php | 2 +- src/Language/sk/Auth.php | 2 +- src/Language/sr/Auth.php | 2 +- src/Language/sv-SE/Auth.php | 2 +- src/Language/tr/Auth.php | 2 +- src/Language/uk/Auth.php | 2 +- src/Views/magic_link_form.php | 2 +- tests/Controllers/MagicLinkTest.php | 4 ++-- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Language/bg/Auth.php b/src/Language/bg/Auth.php index 08d507169..625c52592 100644 --- a/src/Language/bg/Auth.php +++ b/src/Language/bg/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Запомни ме?', 'forgotPassword' => 'Забравена парола?', 'useMagicLink' => 'Използвайте линк за вход', - 'usePassword' => 'Използвайте парола', 'magicLinkSubject' => 'Вашият линк за вход', 'magicTokenNotFound' => 'Не може да се потвърди линка.', 'magicLinkExpired' => 'Съжаляваме, линкът е изтекъл.', 'checkYourEmail' => 'Проверете вашия имейл!', 'magicLinkDetails' => 'Току що ви изпратихме имейл с линк за вход. Линкът ще бъде валиден само {0} минути.', 'successLogout' => 'Успешно излязохте от системата.', + 'backToLogin' => 'Обратно към входа', // Пароли 'errorPasswordLength' => 'Паролите трябва да са поне {0, number} символа дълги.', diff --git a/src/Language/de/Auth.php b/src/Language/de/Auth.php index dbc1cf922..7b982fd2c 100644 --- a/src/Language/de/Auth.php +++ b/src/Language/de/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Angemeldet bleiben', 'forgotPassword' => 'Passwort vergessen?', 'useMagicLink' => 'Einen Login-Link verwenden', - 'usePassword' => 'Ein Passwort verwenden', 'magicLinkSubject' => 'Ihr Login-Link', 'magicTokenNotFound' => 'Der Link konnte nicht verifiziert werden.', 'magicLinkExpired' => 'Sorry, der Link ist abgelaufen.', 'checkYourEmail' => 'Prüfen Sie Ihre E-Mail!', 'magicLinkDetails' => 'Wir haben Ihnen gerade eine E-Mail mit einem Login-Link geschickt. Er ist nur für {0} Minuten gültig.', 'successLogout' => 'Sie haben sich erfolgreich abgemeldet.', + 'backToLogin' => 'Zurück zur Anmeldung', // Passwords 'errorPasswordLength' => 'Passwörter müssen mindestens {0, number} Zeichen lang sein.', diff --git a/src/Language/en/Auth.php b/src/Language/en/Auth.php index 0f1a1862f..f64911f69 100644 --- a/src/Language/en/Auth.php +++ b/src/Language/en/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Remember me?', 'forgotPassword' => 'Forgot your password?', 'useMagicLink' => 'Use a Login Link', - 'usePassword' => 'Use a Password', 'magicLinkSubject' => 'Your Login Link', 'magicTokenNotFound' => 'Unable to verify the link.', 'magicLinkExpired' => 'Sorry, link has expired.', 'checkYourEmail' => 'Check your email!', 'magicLinkDetails' => 'We just sent you an email with a Login link inside. It is only valid for {0} minutes.', 'successLogout' => 'You have successfully logged out.', + 'backToLogin' => 'Back to Login', // Passwords 'errorPasswordLength' => 'Passwords must be at least {0, number} characters long.', diff --git a/src/Language/es/Auth.php b/src/Language/es/Auth.php index 38d477763..a6b52b4bc 100644 --- a/src/Language/es/Auth.php +++ b/src/Language/es/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Recordarme', 'forgotPassword' => '¿Olvidaste tu contraseña', 'useMagicLink' => 'Usar un enlace de inicio de sesión', - 'usePassword' => 'Usar una contraseña', 'magicLinkSubject' => 'Tu enlace de inicio de sesión', 'magicTokenNotFound' => 'No se puede verificar el enlace.', 'magicLinkExpired' => 'Lo siento, el enlace ha caducado.', 'checkYourEmail' => '¡Revisa tu correo electrónico!', 'magicLinkDetails' => 'Acabamos de enviarte un correo electrónico con un enlace de inicio de sesión. Solo es válido durante {0} minutos.', 'successLogout' => 'Has cerrado sesión correctamente.', + 'backToLogin' => 'Volver al inicio de sesión', // Contraseñas 'errorPasswordLength' => 'Las contraseñas deben tener al menos {0, number} caracteres.', diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index e7a021a52..5c0a05f5a 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'مرا به خاطر بسپار؟', 'forgotPassword' => 'رمز عبور را فراموش کرده اید؟', 'useMagicLink' => 'از لینک ورود استفاده کنید', - 'usePassword' => 'استفاده از رمز عبور', 'magicLinkSubject' => 'لینک ورود شما', 'magicTokenNotFound' => 'تایید لینک ممکن نیست.', 'magicLinkExpired' => 'متاسفانه, لینک منقضی شده است.', 'checkYourEmail' => 'ایمیلتان را بررسی کنید!', 'magicLinkDetails' => 'ما فقط یک لینک ورود به ایمیلتان ارسال کردیم. این لینک فقط برای {0} دقیقه معتبر خواهد بود.', 'successLogout' => 'با موفقیت خارج شدید.', + 'backToLogin' => 'بازگشت به ورود به سیستم', // Passwords 'errorPasswordLength' => 'طول رمز های عبور باید حداقل {0, number} کاراکتر باشد.', diff --git a/src/Language/fr/Auth.php b/src/Language/fr/Auth.php index 0d51d0bb0..3a3d2b8a0 100644 --- a/src/Language/fr/Auth.php +++ b/src/Language/fr/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Se souvenir de moi', 'forgotPassword' => 'Mot de passe oublié ?', 'useMagicLink' => 'Utiliser un lien de connexion', - 'usePassword' => 'Utiliser un mot de passe', 'magicLinkSubject' => 'Votre lien de connexion', 'magicTokenNotFound' => 'Impossible de vérifier le lien.', 'magicLinkExpired' => 'Désolé, le lien a expiré.', 'checkYourEmail' => 'Vérifier votre email !', 'magicLinkDetails' => 'Nous venons de vous envoyer un email contenant un lien de connexion. Il n\'est valable que {0} minutes.', 'successLogout' => 'Vous avez été déconnecté avec succès.', + 'backToLogin' => 'Retour à la connexion', // Passwords 'errorPasswordLength' => 'Le mot de passe doit contenir au moins {0, number} caractères.', diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index f389a3623..86f851d1a 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Ingat saya?', 'forgotPassword' => 'Lupa kata sandi?', 'useMagicLink' => 'Gunakan tautan masuk', - 'usePassword' => 'Gunakan kata sandi', 'magicLinkSubject' => 'Tautan masuk Anda', 'magicTokenNotFound' => 'Tidak dapat memverifikasi tautan.', 'magicLinkExpired' => 'Maaf, tautan sudah tidak berlaku.', 'checkYourEmail' => 'Periksa email Anda!', 'magicLinkDetails' => 'Kami baru saja mengirimi Anda email dengan tautan Masuk di dalamnya. Ini hanya berlaku selama {0} menit.', 'successLogout' => 'Anda telah berhasil keluar.', + 'backToLogin' => 'Kembali ke masuk', // Passwords 'errorPasswordLength' => 'Kata sandi harus setidaknya terdiri dari {0, number} karakter.', diff --git a/src/Language/it/Auth.php b/src/Language/it/Auth.php index fc963259e..e62989d7c 100644 --- a/src/Language/it/Auth.php +++ b/src/Language/it/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Ricordami?', 'forgotPassword' => 'Password dimenticata?', 'useMagicLink' => 'Usa un Login Link', - 'usePassword' => 'Usa una password', 'magicLinkSubject' => 'Il tuo Login Link', 'magicTokenNotFound' => 'Impossibile verificare il link.', 'magicLinkExpired' => 'Spiacente, il link è scaduto.', 'checkYourEmail' => 'Controlla la tua email!', 'magicLinkDetails' => 'Ti abbiamo appena inviato una mail contenente un Login link. È valido solo per {0} minuti.', 'successLogout' => 'Hai effettuato il logout con successo.', + 'backToLogin' => 'Torna al login', // Passwords 'errorPasswordLength' => 'Le password devono essere lunghe almeno {0, number} ccaratteri.', diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index fa4416160..0a3befa0a 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'ログイン状態を保持する', // 'Remember me?' 'forgotPassword' => 'パスワードをお忘れの方', // 'Forgot your password?' 'useMagicLink' => 'ログインリンクを使用する', // 'Use a Login Link' - 'usePassword' => 'パスワードを使用する', // 'Use a Password' 'magicLinkSubject' => 'あなたのログインリンク', // 'Your Login Link' 'magicTokenNotFound' => 'リンクを確認できません。', // 'Unable to verify the link.' 'magicLinkExpired' => '申し訳ございません、リンクは切れています。', // 'Sorry, link has expired.' 'checkYourEmail' => 'メールをチェックしてください!', // 'Check your email!' 'magicLinkDetails' => 'ログインリンクが含まれたメールを送信しました。これは {0} 分間だけ有効です。', // 'We just sent you an email with a Login link inside. It is only valid for {0} minutes.' 'successLogout' => '正常にログアウトしました。', // 'You have successfully logged out.' + 'backToLogin' => 'ログインに戻る', // 'Back to Login' // Passwords 'errorPasswordLength' => 'パスワードは最低でも {0, number} 文字でなければなりません。', // 'Passwords must be at least {0, number} characters long.' diff --git a/src/Language/lt/Auth.php b/src/Language/lt/Auth.php index 209d4e23f..6efc8e00a 100644 --- a/src/Language/lt/Auth.php +++ b/src/Language/lt/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Atsiminti mane?', 'forgotPassword' => 'Pamiršote slaptažodį?', 'useMagicLink' => 'Naudoti prisijungimo nuorodą', - 'usePassword' => 'Naudoti slaptažodį', 'magicLinkSubject' => 'Jūsų prisijungimo nuoroda', 'magicTokenNotFound' => 'Nepavyksta patvirtinti nuorodos.', 'magicLinkExpired' => 'Deja, nuorodos galiojimas baigėsi.', 'checkYourEmail' => 'Patikrinkite savo el. paštą!', 'magicLinkDetails' => 'Mes ką tik išsiuntėme Jums el. laišką su prisijungimo nuoroda. Ji galios tiki {0} minučių(-es).', 'successLogout' => 'Jūs sėkmingai atsijungėte.', + 'backToLogin' => 'Grįžti į prisijungimą', // Passwords 'errorPasswordLength' => 'Slaptažodis turi būti bent {0, number} ženklų ilgio.', diff --git a/src/Language/pt-BR/Auth.php b/src/Language/pt-BR/Auth.php index e1c31aaea..4885d3298 100644 --- a/src/Language/pt-BR/Auth.php +++ b/src/Language/pt-BR/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Lembrar de mim?', 'forgotPassword' => 'Esqueceu sua senha?', 'useMagicLink' => 'Use um Link de Login', - 'usePassword' => 'Use uma senha', 'magicLinkSubject' => 'Seu Link de Login', 'magicTokenNotFound' => 'Não foi possível verificar o link.', 'magicLinkExpired' => 'Desculpe, o link expirou.', 'checkYourEmail' => 'Verifique seu e-mail!', 'magicLinkDetails' => 'Acabamos de enviar um e-mail com um link de Login. Ele é válido apenas por {0} minutos.', 'successLogout' => 'Você saiu com sucesso.', + 'backToLogin' => 'Voltar para o login', // Senhas 'errorPasswordLength' => 'As senhas devem ter pelo menos {0, number} caracteres.', diff --git a/src/Language/pt/Auth.php b/src/Language/pt/Auth.php index 00cb2fd25..d4b5261c8 100644 --- a/src/Language/pt/Auth.php +++ b/src/Language/pt/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Lembrar', 'forgotPassword' => 'Esqueceu a sua password?', 'useMagicLink' => 'Use um Link de Login', - 'usePassword' => 'Use uma senha', 'magicLinkSubject' => 'O seu Link de Login', 'magicTokenNotFound' => 'Não foi possível verificar o link.', 'magicLinkExpired' => 'Desculpe, o link expirou.', 'checkYourEmail' => 'Verifique o seu e-mail!', 'magicLinkDetails' => 'Acabamos de enviar um e-mail com um link de Login. Ele é válido apenas por {0} minutos.', 'successLogout' => 'Saiu com sucesso.', + 'backToLogin' => 'Voltar ao login', // Senhas 'errorPasswordLength' => 'As passwords devem ter pelo menos {0, number} caracteres.', diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index 9bc77aa2d..c18741d9b 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Zapamätať si ma?', 'forgotPassword' => 'Zabudli ste heslo?', 'useMagicLink' => 'Použiť odkaz na prihlásenie', - 'usePassword' => 'Použiť heslo', 'magicLinkSubject' => 'Váš odkaz na prihlásenie', 'magicTokenNotFound' => 'Odkaz sa nepodarilo overiť.', 'magicLinkExpired' => 'Ľutujeme, platnosť odkazu vypršala.', 'checkYourEmail' => 'Skontrolujte e-mail', 'magicLinkDetails' => 'Práve sme vám poslali e-mail s odkazom na prihlásenie. Platí iba {0} minút.', 'successLogout' => 'Úspešne ste sa odhlásili.', + 'backToLogin' => 'Späť na prihlásenie', // Passwords 'errorPasswordLength' => 'Heslá musia mať aspoň {0, number} znakov.', diff --git a/src/Language/sr/Auth.php b/src/Language/sr/Auth.php index e388f20af..12236dfc2 100644 --- a/src/Language/sr/Auth.php +++ b/src/Language/sr/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Zapmti me?', 'forgotPassword' => 'Zaboravljena lozinka?', 'useMagicLink' => 'Koristi pristupni link', - 'usePassword' => 'Koristi lozinku', 'magicLinkSubject' => 'Vaš pristupni link', 'magicTokenNotFound' => 'Nije moguća verifikacija linka.', 'magicLinkExpired' => 'Žao nam je, link je istekao.', 'checkYourEmail' => 'Proverite Vaš email!', 'magicLinkDetails' => 'Upravo smo Vam poslali pristupni link. Pristupni link će biti validan još samo {0} minuta.', 'successLogout' => 'Uspešno ste se odjavili sa sistema.', + 'backToLogin' => 'Nazad na prijavljivanje', // Passwords 'errorPasswordLength' => 'Lozinka mora biti najmanje {0, number} znakova dužine.', diff --git a/src/Language/sv-SE/Auth.php b/src/Language/sv-SE/Auth.php index dff8841e0..d57ec3b33 100644 --- a/src/Language/sv-SE/Auth.php +++ b/src/Language/sv-SE/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Kom ihåg mig?', 'forgotPassword' => 'Glömt ditt lösenord?', 'useMagicLink' => 'Använd en login-länk', - 'usePassword' => 'Använd ett lösenord', 'magicLinkSubject' => 'Din login-länk', 'magicTokenNotFound' => 'Kan inte verifiera länken.', 'magicLinkExpired' => 'Tyvärr, länken har gått ut.', 'checkYourEmail' => 'Kontrollera din epost!', 'magicLinkDetails' => 'En login-länk har skickats med epost. Den gäller bara i {0} minuter.', 'successLogout' => 'Du har loggats ut.', + 'backToLogin' => 'Tillbaka till inloggning', // Passwords 'errorPasswordLength' => 'Lösenordet måste vara minst {0, number} tecken långt.', diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 8acbb16c3..6d1766732 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Beni hatırla?', 'forgotPassword' => 'Şifrenizi mı unuttunuz?', 'useMagicLink' => 'Giriş Bağlantısı Kullanın', - 'usePassword' => 'Parola Kullanın', 'magicLinkSubject' => 'Giriş Bağlantınız', 'magicTokenNotFound' => 'Bağlantı doğrulanamıyor.', 'magicLinkExpired' => 'Üzgünüm, bağlantının süresi doldu.', 'checkYourEmail' => 'E-postanı kontrol et!', 'magicLinkDetails' => 'Az önce size içinde bir Giriş bağlantısı olan bir e-posta gönderdik. Bağlantı {0} dakika için geçerlidir.', 'successLogout' => 'Başarıyla çıkış yaptınız.', + 'backToLogin' => 'Girişe Geri Dön', // Passwords 'errorPasswordLength' => 'Şifre en az {0, number} karakter uzunluğunda olmalıdır.', diff --git a/src/Language/uk/Auth.php b/src/Language/uk/Auth.php index 3f0285206..1ff36314b 100644 --- a/src/Language/uk/Auth.php +++ b/src/Language/uk/Auth.php @@ -46,13 +46,13 @@ 'rememberMe' => 'Пам’ятай мене?', 'forgotPassword' => 'Забули пароль?', 'useMagicLink' => 'Використовуйте посилання для входу', - 'usePassword' => 'Використовуйте пароль', 'magicLinkSubject' => 'Ваше посилання для входу', 'magicTokenNotFound' => 'Неможливо перевірити посилання.', 'magicLinkExpired' => 'Вибачте, термін дії посилання закінчився.', 'checkYourEmail' => 'Перевірте свою електронну пошту!', 'magicLinkDetails' => 'Ми щойно надіслали вам електронний лист із посиланням для входу. Він дійсний лише протягом {0} хвилин.', 'successLogout' => 'Ви успішно вийшли.', + 'backToLogin' => 'Повернутися до входу', // Passwords 'errorPasswordLength' => 'Паролі повинні містити принаймні {0, числових} символів.', diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index 474b2bda9..100ce67f6 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -39,7 +39,7 @@ -

+

diff --git a/tests/Controllers/MagicLinkTest.php b/tests/Controllers/MagicLinkTest.php index b46b4b721..7bf92fc6b 100644 --- a/tests/Controllers/MagicLinkTest.php +++ b/tests/Controllers/MagicLinkTest.php @@ -115,9 +115,9 @@ public function testMagicLinkVerifyPendingRegistrationActivation(): void $this->assertFalse(auth()->loggedIn()); } - public function testUsePassword() + public function testBackToLoginLinkOnPage(): void { $result = $this->get('/login/magic-link'); - $this->assertStringContainsString(lang('Auth.usePassword'), $result->getBody()); + $this->assertStringContainsString(lang('Auth.backToLogin'), $result->getBody()); } } From 6d25b299826e31ca3a03b382ca01bc695193f78c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Tue, 9 May 2023 20:09:51 +0330 Subject: [PATCH 015/401] docs: fix typo in `Passwords::getMaxLengthRule()` --- docs/addons/jwt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index efcc54c8c..3cf3f8ae3 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -219,7 +219,7 @@ class LoginController extends BaseController ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLenghtRule(), + 'rules' => 'required|' . Passwords::getMaxLengthRule(), 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], From 5ffb6a65bf5203260f996ea7c5939fc981dea86d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Fri, 12 May 2023 21:15:30 +0330 Subject: [PATCH 016/401] chore: fix psalm error by add phpdocs --- src/Controllers/MagicLinkController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 2f081a82c..6ed442056 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -35,7 +35,10 @@ class MagicLinkController extends BaseController public function __construct() { helper('setting'); - $providerClass = setting('Auth.userProvider'); + + /** @var class-string $providerClass */ + $providerClass = setting('Auth.userProvider'); + $this->provider = new $providerClass(); } From 02d6343920cdedca21a92674470f09dc89f8c204 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Mon, 15 May 2023 19:55:46 +0800 Subject: [PATCH 017/401] fix: simplify extraction of email parts --- .../Passwords/NothingPersonalValidator.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Authentication/Passwords/NothingPersonalValidator.php b/src/Authentication/Passwords/NothingPersonalValidator.php index ef11ffbae..92151e8b0 100644 --- a/src/Authentication/Passwords/NothingPersonalValidator.php +++ b/src/Authentication/Passwords/NothingPersonalValidator.php @@ -72,15 +72,8 @@ protected function isNotPersonal(string $password, ?User $user): bool $needles = $this->strip_explode($userName); // extract local-part and domain parts from email as separate needles - if (str_contains($email, '@')) { - [ - $localPart, - $domain, - ] = explode('@', $email); - } else { - $localPart = $email; - $domain = null; - } + [$localPart, $domain] = explode('@', $email) + [1 => null]; + // might be john.doe@example.com and we want all the needles we can get $emailParts = $this->strip_explode($localPart); if (! empty($domain)) { From 73dc92e0ab4c8e173e7dff9288329cb7355c123a Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 15 May 2023 22:27:31 +0900 Subject: [PATCH 018/401] docs: add property types --- docs/authorization.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/authorization.md b/docs/authorization.md index 16ac5a797..d2aa2875c 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -38,7 +38,7 @@ Groups are defined within the `Shield\Config\AuthGroups` config class. ```php -public $groups = [ +public array $groups = [ 'superadmin' => [ 'title' => 'Super Admin', 'description' => 'Optional description of the group.', @@ -66,7 +66,7 @@ a scope and action, like `users.create`. The scope would be `users` and the acti can have a description for display within UIs if needed. ```php -public $permissions = [ +public array $permissions = [ 'admin.access' => 'Can access the sites admin area', 'admin.settings' => 'Can access the main site settings', 'users.manage-admins' => 'Can manage other admins', @@ -88,7 +88,7 @@ The matrix is an associative array with the group name as the key, and an array of permissions that should be applied to that group. ```php -public $matrix = [ +public array $matrix = [ 'admin' => [ 'admin.access', 'users.create', 'users.edit', 'users.delete', @@ -100,7 +100,7 @@ public $matrix = [ You can use a wildcard within a scope to allow all actions within that scope, by using a `*` in place of the action. ```php -public $matrix = [ +public array $matrix = [ 'superadmin' => ['admin.*', 'users.*', 'beta.*'], ]; ``` From 35c736283f4b11aaa489f8c46ea0e34eba6eadcf Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 15 May 2023 22:27:49 +0900 Subject: [PATCH 019/401] docs: fix incorrect property value --- docs/authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authorization.md b/docs/authorization.md index d2aa2875c..ee3aa104c 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -56,7 +56,7 @@ When a user is first registered on the site, they are assigned to a default user `Config\AuthGroups::$defaultGroup`, and must match the name of one of the defined groups. ```php -public $defaultGroup = 'users'; +public string $defaultGroup = 'user'; ``` ## Defining Available Permissions From 17f27815d22fa3351e316f8d400444c8b0baf1ea Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Tue, 16 May 2023 02:03:45 +0200 Subject: [PATCH 020/401] feat: add 'DBGroup' for customization db group --- src/Config/Auth.php | 10 ++++++++++ src/Models/BaseModel.php | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index b4d9ebae0..f779695e6 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -42,6 +42,16 @@ class Auth extends BaseConfig 'magic-link-email' => '\CodeIgniter\Shield\Views\Email\magic_link_email', ]; + /** + * -------------------------------------------------------------------- + * Customize the DB group used for each model + * -------------------------------------------------------------------- + * if no database connection instance is passed to the constructor, + * it will automatically connect to the default database group, + * as set in the configuration + */ + public string $DBGroup = ''; + /** * -------------------------------------------------------------------- * Customize Name of Shield Tables diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index b15836da5..0505f1dfb 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -16,6 +16,17 @@ abstract class BaseModel extends Model */ protected array $tables; + public function __construct() + { + $authConfig = config('Auth'); + + if (! empty($authConfig->DBGroup)) { + $this->DBGroup = $authConfig->DBGroup; + } + + parent::__construct(); + } + protected function initialize(): void { /** @var Auth $authConfig */ From 6a2b654106ab8d64b8251b37bce6e3c2aca7a56b Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Tue, 16 May 2023 10:22:17 +0300 Subject: [PATCH 021/401] feat: Add InnoDB engine to migration create table methods --- .../2020-12-28-223112_create_auth_tables.php | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 80691cd9b..4965ad7c5 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -15,13 +15,25 @@ class CreateAuthTables extends Migration */ private array $tables; + /** + * @var array|string[] + */ + private array $attributes; + + /** + * @var false + */ + private bool $ifNotExists; + public function __construct(?Forge $forge = null) { parent::__construct($forge); /** @var Auth $authConfig */ - $authConfig = config('Auth'); - $this->tables = $authConfig->tables; + $authConfig = config('Auth'); + $this->tables = $authConfig->tables; + $this->ifNotExists = false; + $this->attributes = ['ENGINE' => 'InnoDB']; } public function up(): void @@ -40,7 +52,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('username'); - $this->forge->createTable($this->tables['users']); + $this->forge->createTable($this->tables['users'], $this->ifNotExists, $this->attributes); /* * Auth Identities Table @@ -64,7 +76,7 @@ public function up(): void $this->forge->addUniqueKey(['type', 'secret']); $this->forge->addKey('user_id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['identities']); + $this->forge->createTable($this->tables['identities'], $this->ifNotExists, $this->attributes); /** * Auth Login Attempts Table @@ -85,7 +97,7 @@ public function up(): void $this->forge->addKey(['id_type', 'identifier']); $this->forge->addKey('user_id'); // NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits - $this->forge->createTable($this->tables['logins']); + $this->forge->createTable($this->tables['logins'], $this->ifNotExists, $this->attributes); /* * Auth Token Login Attempts Table @@ -105,7 +117,7 @@ public function up(): void $this->forge->addKey(['id_type', 'identifier']); $this->forge->addKey('user_id'); // NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits - $this->forge->createTable($this->tables['token_logins']); + $this->forge->createTable($this->tables['token_logins'], $this->ifNotExists, $this->attributes); /* * Auth Remember Tokens (remember-me) Table @@ -123,7 +135,7 @@ public function up(): void $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('selector'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['remember_tokens']); + $this->forge->createTable($this->tables['remember_tokens'], $this->ifNotExists, $this->attributes); // Groups Users Table $this->forge->addField([ @@ -134,7 +146,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['groups_users']); + $this->forge->createTable($this->tables['groups_users'], $this->ifNotExists, $this->attributes); // Users Permissions Table $this->forge->addField([ @@ -145,7 +157,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['permissions_users']); + $this->forge->createTable($this->tables['permissions_users'], $this->ifNotExists, $this->attributes); } // -------------------------------------------------------------------- From 96baffe4b30e22ec631cc1e37a1a49a82d94c5cd Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Tue, 16 May 2023 10:29:48 +0300 Subject: [PATCH 022/401] apply rector suggestions --- .../Migrations/2020-12-28-223112_create_auth_tables.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 4965ad7c5..b3ed443af 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -15,14 +15,7 @@ class CreateAuthTables extends Migration */ private array $tables; - /** - * @var array|string[] - */ private array $attributes; - - /** - * @var false - */ private bool $ifNotExists; public function __construct(?Forge $forge = null) From 04238b4178d3b6949c2c3b6aaf3a3f9758d8bcbc Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Tue, 16 May 2023 15:55:46 +0300 Subject: [PATCH 023/401] add check for MySQLi platform --- .../Migrations/2020-12-28-223112_create_auth_tables.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index b3ed443af..5c5626a87 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -26,7 +26,7 @@ public function __construct(?Forge $forge = null) $authConfig = config('Auth'); $this->tables = $authConfig->tables; $this->ifNotExists = false; - $this->attributes = ['ENGINE' => 'InnoDB']; + $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; } public function up(): void From 3fcfcca5fdaae14b0a0856778bfed7d52ad81f29 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Wed, 17 May 2023 12:21:13 +0300 Subject: [PATCH 024/401] move create table to private method --- .../2020-12-28-223112_create_auth_tables.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 5c5626a87..deb4570ae 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -16,17 +16,15 @@ class CreateAuthTables extends Migration private array $tables; private array $attributes; - private bool $ifNotExists; public function __construct(?Forge $forge = null) { parent::__construct($forge); /** @var Auth $authConfig */ - $authConfig = config('Auth'); - $this->tables = $authConfig->tables; - $this->ifNotExists = false; - $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; + $authConfig = config('Auth'); + $this->tables = $authConfig->tables; + $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; } public function up(): void @@ -45,7 +43,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('username'); - $this->forge->createTable($this->tables['users'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['users']); /* * Auth Identities Table @@ -69,7 +67,7 @@ public function up(): void $this->forge->addUniqueKey(['type', 'secret']); $this->forge->addKey('user_id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['identities'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['identities']); /** * Auth Login Attempts Table @@ -90,7 +88,7 @@ public function up(): void $this->forge->addKey(['id_type', 'identifier']); $this->forge->addKey('user_id'); // NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits - $this->forge->createTable($this->tables['logins'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['logins']); /* * Auth Token Login Attempts Table @@ -110,7 +108,7 @@ public function up(): void $this->forge->addKey(['id_type', 'identifier']); $this->forge->addKey('user_id'); // NOTE: Do NOT delete the user_id or identifier when the user is deleted for security audits - $this->forge->createTable($this->tables['token_logins'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['token_logins']); /* * Auth Remember Tokens (remember-me) Table @@ -128,7 +126,7 @@ public function up(): void $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('selector'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['remember_tokens'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['remember_tokens']); // Groups Users Table $this->forge->addField([ @@ -139,7 +137,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['groups_users'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['groups_users']); // Users Permissions Table $this->forge->addField([ @@ -150,7 +148,7 @@ public function up(): void ]); $this->forge->addPrimaryKey('id'); $this->forge->addForeignKey('user_id', $this->tables['users'], 'id', '', 'CASCADE'); - $this->forge->createTable($this->tables['permissions_users'], $this->ifNotExists, $this->attributes); + $this->createTable($this->tables['permissions_users']); } // -------------------------------------------------------------------- @@ -169,4 +167,9 @@ public function down(): void $this->db->enableForeignKeyChecks(); } + + private function createTable(string $tableName): void + { + $this->forge->createTable($tableName, false, $this->attributes); + } } From 5239ecce0019d43ee728cef857f782df36407d0c Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Wed, 17 May 2023 19:00:47 +0200 Subject: [PATCH 025/401] Passing DBGroup to validateData() Function --- src/Config/Auth.php | 2 +- src/Controllers/LoginController.php | 2 +- src/Controllers/MagicLinkController.php | 2 +- src/Controllers/RegisterController.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index f779695e6..504919cdc 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -50,7 +50,7 @@ class Auth extends BaseConfig * it will automatically connect to the default database group, * as set in the configuration */ - public string $DBGroup = ''; + public ?string $DBGroup = null; /** * -------------------------------------------------------------------- diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index dd27b97bf..0eda50ad3 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -47,7 +47,7 @@ public function loginAction(): RedirectResponse // like the password, can only be validated properly here. $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules)) { + if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 6ed442056..2f9f0a544 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -68,7 +68,7 @@ public function loginAction() { // Validate email format $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules)) { + if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { return redirect()->route('magic-link')->with('errors', $this->validator->getErrors()); } diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 500b1ea36..f57464926 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -100,7 +100,7 @@ public function registerAction(): RedirectResponse // like the password, can only be validated properly here. $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules)) { + if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } From 27ab5615b116c344b39ba6d1e5c8db176a4fb38b Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Wed, 17 May 2023 21:31:00 +0200 Subject: [PATCH 026/401] Fixing the Parameter Order of the validateData Function --- src/Controllers/LoginController.php | 2 +- src/Controllers/MagicLinkController.php | 2 +- src/Controllers/RegisterController.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index 0eda50ad3..eab0aa316 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -47,7 +47,7 @@ public function loginAction(): RedirectResponse // like the password, can only be validated properly here. $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { + if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 2f9f0a544..45cfb1060 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -68,7 +68,7 @@ public function loginAction() { // Validate email format $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { + if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) { return redirect()->route('magic-link')->with('errors', $this->validator->getErrors()); } diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index f57464926..954742dc2 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -100,7 +100,7 @@ public function registerAction(): RedirectResponse // like the password, can only be validated properly here. $rules = $this->getValidationRules(); - if (! $this->validateData($this->request->getPost(), $rules, config('Auth')->DBGroup)) { + if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } From ff53705543fc2044580bccefbe51bae4f07aa60a Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Wed, 17 May 2023 21:38:32 +0200 Subject: [PATCH 027/401] Passing DBGroup to validateData() Function - Docs --- docs/addons/jwt.md | 2 +- docs/guides/mobile_apps.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index 3cf3f8ae3..f312e5919 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -163,7 +163,7 @@ class LoginController extends BaseController $rules = $this->getValidationRules(); // Validate credentials - if (! $this->validateData($this->request->getJSON(true), $rules)) { + if (! $this->validateData($this->request->getJSON(true), $rules, [], config('Auth')->DBGroup)) { return $this->fail( ['errors' => $this->validator->getErrors()], $this->codes['unauthorized'] diff --git a/docs/guides/mobile_apps.md b/docs/guides/mobile_apps.md index 237eeeafd..b6da507b4 100644 --- a/docs/guides/mobile_apps.md +++ b/docs/guides/mobile_apps.md @@ -38,7 +38,7 @@ class LoginController extends BaseController ], ]; - if (! $this->validateData($this->request->getPost(), $rules)) { + if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) { return $this->response ->setJSON(['errors' => $this->validator->getErrors()]) ->setStatusCode(401); From 88c8bd8724b2be621efb9371db654fbda8bb2ed4 Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Thu, 18 May 2023 00:21:59 +0200 Subject: [PATCH 028/401] Add to migration file --- .../Migrations/2020-12-28-223112_create_auth_tables.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index deb4570ae..1e15d72f4 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -19,12 +19,13 @@ class CreateAuthTables extends Migration public function __construct(?Forge $forge = null) { - parent::__construct($forge); - /** @var Auth $authConfig */ $authConfig = config('Auth'); $this->tables = $authConfig->tables; $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; + + $this->DBGroup = $authConfig->DBGroup; + parent::__construct($forge); } public function up(): void From 828f360889c0375ea8b9832b6b21ab7781ee09b7 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Thu, 18 May 2023 15:41:42 +0100 Subject: [PATCH 029/401] add support for all request types when building a user from request. --- src/Authentication/Passwords/ValidationRules.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 53f078b8e..50815af76 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -73,7 +73,7 @@ protected function buildUserFromRequest(): User /** @var IncomingRequest $request */ $request = service('request'); - $data = $request->getPost($fields); + $data = $request->getVar($fields); return new User($data); } From 82d763a51a911259a4e7d5c2b43fafe8164ea14a Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Thu, 18 May 2023 23:53:36 +0200 Subject: [PATCH 030/401] Update src/Models/BaseModel.php Co-authored-by: Pooya Parsa Dadashi --- src/Models/BaseModel.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index 0505f1dfb..ab2b2edb9 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -18,10 +18,10 @@ abstract class BaseModel extends Model public function __construct() { - $authConfig = config('Auth'); + $this->authConfig = config(Auth::class); - if (! empty($authConfig->DBGroup)) { - $this->DBGroup = $authConfig->DBGroup; + if ($authConfig->DBGroup !== null)) { + $this->DBGroup = $this->authConfig->DBGroup; } parent::__construct(); From 5157a08c5b3cb0b1de1b45ece6df68371981a1f2 Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Thu, 18 May 2023 23:54:46 +0200 Subject: [PATCH 031/401] Update src/Models/BaseModel.php Co-authored-by: Pooya Parsa Dadashi --- src/Models/BaseModel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index ab2b2edb9..c85e6b4df 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -15,6 +15,7 @@ abstract class BaseModel extends Model * Auth Table names */ protected array $tables; + protected Auth $authConfig; public function __construct() { From c2d099bd587db42fc074b4c5f7c5f97ce608b813 Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Thu, 18 May 2023 23:56:01 +0200 Subject: [PATCH 032/401] Update src/Database/Migrations/2020-12-28-223112_create_auth_tables.php Co-authored-by: Pooya Parsa Dadashi --- .../2020-12-28-223112_create_auth_tables.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 1e15d72f4..cdc64c8c4 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -19,13 +19,17 @@ class CreateAuthTables extends Migration public function __construct(?Forge $forge = null) { + parent::__construct($forge); + /** @var Auth $authConfig */ - $authConfig = config('Auth'); + $authConfig = config('Auth'); + + if ($authConfig->DBGroup !== null) { + $this->DBGroup = $authConfig->DBGroup; + } + $this->tables = $authConfig->tables; $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; - - $this->DBGroup = $authConfig->DBGroup; - parent::__construct($forge); } public function up(): void From 6d773689b54527bba0607dfa78678b89c798f34a Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Fri, 19 May 2023 00:18:42 +0200 Subject: [PATCH 033/401] Fixed syntax error --- src/Models/BaseModel.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index c85e6b4df..a96d87f81 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -21,7 +21,7 @@ public function __construct() { $this->authConfig = config(Auth::class); - if ($authConfig->DBGroup !== null)) { + if ($this->authConfig->DBGroup !== null) { $this->DBGroup = $this->authConfig->DBGroup; } @@ -30,9 +30,6 @@ public function __construct() protected function initialize(): void { - /** @var Auth $authConfig */ - $authConfig = config('Auth'); - - $this->tables = $authConfig->tables; + $this->tables = $this->authConfig->tables; } } From 6e653ce78554fffff34b4824865ef80c6426e0c8 Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Fri, 19 May 2023 00:39:05 +0200 Subject: [PATCH 034/401] composer cs-fix --- src/Models/BaseModel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index a96d87f81..c266a6070 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -15,6 +15,7 @@ abstract class BaseModel extends Model * Auth Table names */ protected array $tables; + protected Auth $authConfig; public function __construct() From 4e1568e182036fb9a3a457a08538b7d116989a8d Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Sat, 20 May 2023 02:05:05 +0200 Subject: [PATCH 035/401] parent::__construct() moved after ->DBGroup assignment. --- .../Migrations/2020-12-28-223112_create_auth_tables.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index cdc64c8c4..6cbad0f42 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -19,8 +19,6 @@ class CreateAuthTables extends Migration public function __construct(?Forge $forge = null) { - parent::__construct($forge); - /** @var Auth $authConfig */ $authConfig = config('Auth'); @@ -30,6 +28,8 @@ public function __construct(?Forge $forge = null) $this->tables = $authConfig->tables; $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; + + parent::__construct($forge); } public function up(): void From d7e2c99d8103eadb9328c5790efe6220868e7dff Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Sat, 20 May 2023 02:54:48 +0200 Subject: [PATCH 036/401] parent::__construct() moved after ->DBGroup assignment. --- .../Migrations/2020-12-28-223112_create_auth_tables.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 6cbad0f42..792cdad3a 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -26,10 +26,10 @@ public function __construct(?Forge $forge = null) $this->DBGroup = $authConfig->DBGroup; } + parent::__construct($forge); + $this->tables = $authConfig->tables; $this->attributes = ($this->db->getPlatform() === 'MySQLi') ? ['ENGINE' => 'InnoDB'] : []; - - parent::__construct($forge); } public function up(): void From d1f52d7c9e5b607541c7251130f74c5fd375a8e1 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 11:48:03 +0100 Subject: [PATCH 037/401] revert back to getPost(), deprecate buildUserFromRequest() --- src/Authentication/Passwords/ValidationRules.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 50815af76..53f078b8e 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -73,7 +73,7 @@ protected function buildUserFromRequest(): User /** @var IncomingRequest $request */ $request = service('request'); - $data = $request->getVar($fields); + $data = $request->getPost($fields); return new User($data); } From 632bcde62bf69c9d1bcad149a10f23d2e0cdd356 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 11:48:44 +0100 Subject: [PATCH 038/401] revert back to getPost(), deprecate buildUserFromRequest() --- src/Authentication/Passwords/ValidationRules.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 53f078b8e..ffd8a6417 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -65,6 +65,8 @@ public function max_byte(?string $str, string $val): bool /** * Builds a new user instance from the global request. + * + * @deprecated This will be removed soon. */ protected function buildUserFromRequest(): User { From f673d4237170c05051a7a990e6e9c42e7968c493 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 12:02:18 +0100 Subject: [PATCH 039/401] added a reference link to the deprecation. --- src/Authentication/Passwords/ValidationRules.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index ffd8a6417..263954557 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -67,6 +67,7 @@ public function max_byte(?string $str, string $val): bool * Builds a new user instance from the global request. * * @deprecated This will be removed soon. + * @see https://github.com/codeigniter4/shield/pull/747#discussion_r1198778666 */ protected function buildUserFromRequest(): User { From dfc3181ead356bae89cb90d08f6ddae9ab9580c5 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 12:03:13 +0100 Subject: [PATCH 040/401] update rule usage in RegisterController --- src/Controllers/RegisterController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 500b1ea36..9dc5070db 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -197,7 +197,7 @@ protected function getValidationRules(): array ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password', + 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], From 4f907c36449eda1e170183bdd78cf9b5490def5c Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 12:03:36 +0100 Subject: [PATCH 041/401] updated docs --- docs/guides/strengthen_password.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/strengthen_password.md b/docs/guides/strengthen_password.md index 5d9345022..102dc9b1d 100644 --- a/docs/guides/strengthen_password.md +++ b/docs/guides/strengthen_password.md @@ -17,7 +17,7 @@ The longer the password, the stronger it is. Consider increasing the value. > **Note** > -> This checking works when you validate passwords with the `strong_password` +> This checking works when you validate passwords with the `strong_password[]` > validation rule. > > If you disable `CompositionValidator` (enabled by default) in `$passwordValidators`, From 70a2138e4e5aed8e155f18c3ae6d2d3a0c11c6e4 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Sat, 20 May 2023 12:38:40 +0100 Subject: [PATCH 042/401] cs-fix --- src/Authentication/Passwords/ValidationRules.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 263954557..b80ef0468 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -65,8 +65,9 @@ public function max_byte(?string $str, string $val): bool /** * Builds a new user instance from the global request. - * + * * @deprecated This will be removed soon. + * * @see https://github.com/codeigniter4/shield/pull/747#discussion_r1198778666 */ protected function buildUserFromRequest(): User From 78992187d646cd7281f1d796f44d3a3b78611234 Mon Sep 17 00:00:00 2001 From: Arash Saffari Date: Sun, 21 May 2023 16:48:49 +0200 Subject: [PATCH 043/401] Correction of the comment for the DBGroup in the Auth Config file. --- src/Config/Auth.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 504919cdc..1c7d57902 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -46,9 +46,6 @@ class Auth extends BaseConfig * -------------------------------------------------------------------- * Customize the DB group used for each model * -------------------------------------------------------------------- - * if no database connection instance is passed to the constructor, - * it will automatically connect to the default database group, - * as set in the configuration */ public ?string $DBGroup = null; From bee838afbe7f01320c00bc69d88740f1d6fde91b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 20 May 2023 08:54:48 +0900 Subject: [PATCH 044/401] refactor: use ::class keyword in config() --- src/Auth.php | 7 ++++--- src/Authentication/Actions/Email2FA.php | 3 ++- src/Authentication/Actions/EmailActivator.php | 3 ++- src/Authentication/Authenticators/AccessTokens.php | 9 +++++---- src/Authentication/Authenticators/Session.php | 8 ++++---- src/Authentication/Passwords.php | 2 +- src/Authentication/Passwords/ValidationRules.php | 3 ++- src/Authorization/Traits/Authorizable.php | 7 ++++--- src/Config/Services.php | 5 +++-- src/Controllers/LoginController.php | 12 +++++++----- src/Controllers/MagicLinkController.php | 8 +++++--- src/Controllers/RegisterController.php | 14 +++++++------- .../2020-12-28-223112_create_auth_tables.php | 3 +-- src/Filters/ChainAuth.php | 3 ++- src/Filters/ForcePasswordResetFilter.php | 3 ++- src/Filters/SessionAuth.php | 3 ++- src/Views/email_2fa_show.php | 7 ++++++- src/Views/email_2fa_verify.php | 7 ++++++- src/Views/email_activate_show.php | 7 ++++++- src/Views/login.php | 7 ++++++- src/Views/magic_link_form.php | 7 ++++++- src/Views/magic_link_message.php | 7 ++++++- src/Views/register.php | 7 ++++++- 23 files changed, 95 insertions(+), 47 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 41163479a..887dad949 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -8,6 +8,8 @@ use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; +use CodeIgniter\Shield\Config\Auth as AuthConfig; +use CodeIgniter\Shield\Config\AuthRoutes; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserModel; @@ -106,7 +108,7 @@ public function authenticate(array $credentials): Result */ public function routes(RouteCollection &$routes, array $config = []): void { - $authRoutes = config('AuthRoutes')->routes; + $authRoutes = config(AuthRoutes::class)->routes; $routes->group('/', ['namespace' => 'CodeIgniter\Shield\Controllers'], static function (RouteCollection $routes) use ($authRoutes, $config): void { foreach ($authRoutes as $name => $row) { @@ -133,8 +135,7 @@ public function getProvider(): UserModel return $this->userProvider; } - /** @var \CodeIgniter\Shield\Config\Auth $config */ - $config = config('Auth'); + $config = config(AuthConfig::class); if (! property_exists($config, 'userProvider')) { throw AuthenticationException::forUnknownUserProvider(); diff --git a/src/Authentication/Actions/Email2FA.php b/src/Authentication/Actions/Email2FA.php index 52827c99a..1a3e4c75e 100644 --- a/src/Authentication/Actions/Email2FA.php +++ b/src/Authentication/Actions/Email2FA.php @@ -8,6 +8,7 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\RuntimeException; @@ -120,7 +121,7 @@ public function verify(IncomingRequest $request) } // Get our login redirect url - return redirect()->to(config('Auth')->loginRedirect()); + return redirect()->to(config(Auth::class)->loginRedirect()); } /** diff --git a/src/Authentication/Actions/EmailActivator.php b/src/Authentication/Actions/EmailActivator.php index 8e5216074..eeba13c03 100644 --- a/src/Authentication/Actions/EmailActivator.php +++ b/src/Authentication/Actions/EmailActivator.php @@ -10,6 +10,7 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\LogicException; @@ -114,7 +115,7 @@ public function verify(IncomingRequest $request) $user->activate(); // Success! - return redirect()->to(config('Auth')->registerRedirect()) + return redirect()->to(config(Auth::class)->registerRedirect()) ->with('message', lang('Auth.registerSuccess')); } diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index a09000703..27beef2e8 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -8,6 +8,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; use CodeIgniter\Shield\Models\TokenLoginModel; @@ -104,7 +105,7 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), + 'reason' => lang('Auth.noToken', [config(Auth::class)->authenticatorHeader['tokens']]), ]); } @@ -129,7 +130,7 @@ public function check(array $credentials): Result // Hasn't been used in a long time if ( $token->last_used_at - && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) + && $token->last_used_at->isBefore(Time::now()->subSeconds(config(Auth::class)->unusedTokenLifetime)) ) { return new Result([ 'success' => false, @@ -168,7 +169,7 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']), + 'token' => $request->getHeaderLine(config(Auth::class)->authenticatorHeader['tokens']), ])->isOK(); } @@ -226,7 +227,7 @@ public function getBearerToken(): ?string /** @var IncomingRequest $request */ $request = service('request'); - $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); + $header = $request->getHeaderLine(config(Auth::class)->authenticatorHeader['tokens']); if (empty($header)) { return null; diff --git a/src/Authentication/Authenticators/Session.php b/src/Authentication/Authenticators/Session.php index c5e9ba138..c3c65a8fa 100644 --- a/src/Authentication/Authenticators/Session.php +++ b/src/Authentication/Authenticators/Session.php @@ -13,6 +13,7 @@ use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Authentication\Passwords; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; @@ -89,8 +90,7 @@ public function __construct(UserModel $provider) */ private function checkSecurityConfig(): void { - /** @var Security $securityConfig */ - $securityConfig = config('Security'); + $securityConfig = config(Security::class); if ($securityConfig->csrfProtection === 'cookie') { throw new SecurityException( @@ -275,8 +275,8 @@ private function recordLoginAttempt( ): void { // Determine the type of ID we're using. // Standard fields would be email, username, - // but any column within config('Auth')->validFields can be used. - $field = array_intersect(config('Auth')->validFields ?? [], array_keys($credentials)); + // but any column within config(Auth::class)->validFields can be used. + $field = array_intersect(config(Auth::class)->validFields ?? [], array_keys($credentials)); if (count($field) !== 1) { throw new InvalidArgumentException('Invalid credentials passed to recordLoginAttempt.'); diff --git a/src/Authentication/Passwords.php b/src/Authentication/Passwords.php index 3a5b7b041..b466fca6c 100644 --- a/src/Authentication/Passwords.php +++ b/src/Authentication/Passwords.php @@ -145,7 +145,7 @@ public function check(string $password, ?User $user = null): Result */ public static function getMaxLengthRule(): string { - if (config('Auth')->hashAlgorithm === PASSWORD_BCRYPT) { + if (config(Auth::class)->hashAlgorithm === PASSWORD_BCRYPT) { return 'max_byte[72]'; } diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 53f078b8e..36fc0477d 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -6,6 +6,7 @@ use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\Shield\Authentication\Passwords; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; /** @@ -97,7 +98,7 @@ protected function buildUserFromData(array $data = []): User */ protected function prepareValidFields(): array { - $config = config('Auth'); + $config = config(Auth::class); $fields = array_merge($config->validFields, $config->personalFields); $fields[] = 'password'; diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index 9e3f32ff1..967eed725 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -6,6 +6,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authorization\AuthorizationException; +use CodeIgniter\Shield\Config\AuthGroups; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Shield\Models\GroupModel; use CodeIgniter\Shield\Models\PermissionModel; @@ -257,7 +258,7 @@ public function can(string $permission): bool $matrix = function_exists('setting') ? setting('AuthGroups.matrix') - : config('AuthGroups')->matrix; + : config(AuthGroups::class)->matrix; foreach ($this->groupCache as $group) { // Check exact match @@ -393,7 +394,7 @@ private function getConfigGroups(): array { return function_exists('setting') ? array_keys(setting('AuthGroups.groups')) - : array_keys(config('AuthGroups')->groups); + : array_keys(config(AuthGroups::class)->groups); } /** @@ -403,6 +404,6 @@ private function getConfigPermissions(): array { return function_exists('setting') ? array_keys(setting('AuthGroups.permissions')) - : array_keys(config('AuthGroups')->permissions); + : array_keys(config(AuthGroups::class)->permissions); } } diff --git a/src/Config/Services.php b/src/Config/Services.php index 1e002b6a0..201b2e199 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -8,6 +8,7 @@ use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\JWTManager; use CodeIgniter\Shield\Authentication\Passwords; +use CodeIgniter\Shield\Config\Auth as AuthConfig; use Config\Services as BaseService; class Services extends BaseService @@ -21,7 +22,7 @@ public static function auth(bool $getShared = true): Auth return self::getSharedInstance('auth'); } - $config = config('Auth'); + $config = config(AuthConfig::class); return new Auth(new Authentication($config)); } @@ -35,7 +36,7 @@ public static function passwords(bool $getShared = true): Passwords return self::getSharedInstance('passwords'); } - return new Passwords(config('Auth')); + return new Passwords(config(AuthConfig::class)); } /** diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index eab0aa316..a4adacaba 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -8,6 +8,8 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; +use CodeIgniter\Shield\Config\Auth; +use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Traits\Viewable; class LoginController extends BaseController @@ -24,7 +26,7 @@ class LoginController extends BaseController public function loginView() { if (auth()->loggedIn()) { - return redirect()->to(config('Auth')->loginRedirect()); + return redirect()->to(config(Auth::class)->loginRedirect()); } /** @var Session $authenticator */ @@ -71,7 +73,7 @@ public function loginAction(): RedirectResponse return redirect()->route('auth-action-show')->withCookies(); } - return redirect()->to(config('Auth')->loginRedirect())->withCookies(); + return redirect()->to(config(Auth::class)->loginRedirect())->withCookies(); } /** @@ -85,11 +87,11 @@ protected function getValidationRules(): array return setting('Validation.login') ?? [ // 'username' => [ // 'label' => 'Auth.username', - // 'rules' => config('AuthSession')->usernameValidationRules, + // 'rules' => config(AuthSession::class)->usernameValidationRules, // ], 'email' => [ 'label' => 'Auth.email', - 'rules' => config('AuthSession')->emailValidationRules, + 'rules' => config(AuthSession::class)->emailValidationRules, ], 'password' => [ 'label' => 'Auth.password', @@ -108,7 +110,7 @@ public function logoutAction(): RedirectResponse { // Capture logout redirect URL before auth logout, // otherwise you cannot check the user in `logoutRedirect()`. - $url = config('Auth')->logoutRedirect(); + $url = config(Auth::class)->logoutRedirect(); auth()->logout(); diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 45cfb1060..a4422a3a1 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -10,6 +10,8 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; +use CodeIgniter\Shield\Config\Auth; +use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Models\LoginModel; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; @@ -51,7 +53,7 @@ public function __construct() public function loginView() { if (auth()->loggedIn()) { - return redirect()->to(config('Auth')->loginRedirect()); + return redirect()->to(config(Auth::class)->loginRedirect()); } return $this->view(setting('Auth.views')['magic-link-login']); @@ -189,7 +191,7 @@ public function verify(): RedirectResponse Events::trigger('magicLogin'); // Get our login redirect url - return redirect()->to(config('Auth')->loginRedirect()); + return redirect()->to(config(Auth::class)->loginRedirect()); } /** @@ -224,7 +226,7 @@ protected function getValidationRules(): array return [ 'email' => [ 'label' => 'Auth.email', - 'rules' => config('AuthSession')->emailValidationRules, + 'rules' => config(AuthSession::class)->emailValidationRules, ], ]; } diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 954742dc2..e48509585 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -12,6 +12,7 @@ use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Config\Auth; +use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\ValidationException; use CodeIgniter\Shield\Models\UserModel; @@ -46,8 +47,7 @@ public function initController( $logger ); - /** @var Auth $authConfig */ - $authConfig = config('Auth'); + $authConfig = config(Auth::class); $this->tables = $authConfig->tables; } @@ -59,7 +59,7 @@ public function initController( public function registerView() { if (auth()->loggedIn()) { - return redirect()->to(config('Auth')->registerRedirect()); + return redirect()->to(config(Auth::class)->registerRedirect()); } // Check if registration is allowed @@ -85,7 +85,7 @@ public function registerView() public function registerAction(): RedirectResponse { if (auth()->loggedIn()) { - return redirect()->to(config('Auth')->registerRedirect()); + return redirect()->to(config(Auth::class)->registerRedirect()); } // Check if registration is allowed @@ -145,7 +145,7 @@ public function registerAction(): RedirectResponse $authenticator->completeLogin($user); // Success! - return redirect()->to(config('Auth')->registerRedirect()) + return redirect()->to(config(Auth::class)->registerRedirect()) ->with('message', lang('Auth.registerSuccess')); } @@ -178,11 +178,11 @@ protected function getUserEntity(): User protected function getValidationRules(): array { $registrationUsernameRules = array_merge( - config('AuthSession')->usernameValidationRules, + config(AuthSession::class)->usernameValidationRules, [sprintf('is_unique[%s.username]', $this->tables['users'])] ); $registrationEmailRules = array_merge( - config('AuthSession')->emailValidationRules, + config(AuthSession::class)->emailValidationRules, [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 792cdad3a..042c352e0 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -19,8 +19,7 @@ class CreateAuthTables extends Migration public function __construct(?Forge $forge = null) { - /** @var Auth $authConfig */ - $authConfig = config('Auth'); + $authConfig = config(Auth::class); if ($authConfig->DBGroup !== null) { $this->DBGroup = $authConfig->DBGroup; diff --git a/src/Filters/ChainAuth.php b/src/Filters/ChainAuth.php index eb4ee0fbb..2eccdd105 100644 --- a/src/Filters/ChainAuth.php +++ b/src/Filters/ChainAuth.php @@ -10,6 +10,7 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Shield\Config\Auth; /** * Chain Authentication Filter. @@ -41,7 +42,7 @@ public function before(RequestInterface $request, $arguments = null) helper('settings'); - $chain = config('Auth')->authenticationChain; + $chain = config(Auth::class)->authenticationChain; foreach ($chain as $alias) { if (auth($alias)->loggedIn()) { diff --git a/src/Filters/ForcePasswordResetFilter.php b/src/Filters/ForcePasswordResetFilter.php index bfa3d21aa..8b320cbdc 100644 --- a/src/Filters/ForcePasswordResetFilter.php +++ b/src/Filters/ForcePasswordResetFilter.php @@ -11,6 +11,7 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; +use CodeIgniter\Shield\Config\Auth; /** * Force Password Reset Filter. @@ -38,7 +39,7 @@ public function before(RequestInterface $request, $arguments = null) $authenticator = auth('session')->getAuthenticator(); if ($authenticator->loggedIn() && $authenticator->getUser()->requiresPasswordReset()) { - return redirect()->to(config('Auth')->forcePasswordResetRedirect()); + return redirect()->to(config(Auth::class)->forcePasswordResetRedirect()); } } diff --git a/src/Filters/SessionAuth.php b/src/Filters/SessionAuth.php index d95f89c07..e280f4785 100644 --- a/src/Filters/SessionAuth.php +++ b/src/Filters/SessionAuth.php @@ -11,6 +11,7 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; +use CodeIgniter\Shield\Config\Auth; /** * Session Authentication Filter. @@ -56,7 +57,7 @@ public function before(RequestInterface $request, $arguments = null) $error = $user->getBanMessage() ?? lang('Auth.logOutBannedUser'); $authenticator->logout(); - return redirect()->to(config('Auth')->logoutRedirect()) + return redirect()->to(config(Auth::class)->logoutRedirect()) ->with('error', $error); } diff --git a/src/Views/email_2fa_show.php b/src/Views/email_2fa_show.php index 46cf68cf4..4a8275ff4 100644 --- a/src/Views/email_2fa_show.php +++ b/src/Views/email_2fa_show.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/email_2fa_verify.php b/src/Views/email_2fa_verify.php index 722977b8c..29aeb8440 100644 --- a/src/Views/email_2fa_verify.php +++ b/src/Views/email_2fa_verify.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index 491fc2fbf..fe5a4f11d 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/login.php b/src/Views/login.php index 34193ce19..7d47f5ea0 100644 --- a/src/Views/login.php +++ b/src/Views/login.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index 100ce67f6..16fe326f3 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/magic_link_message.php b/src/Views/magic_link_message.php index 0855272f3..f1d961110 100644 --- a/src/Views/magic_link_message.php +++ b/src/Views/magic_link_message.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/register.php b/src/Views/register.php index 4c8fb301e..d6060a7e5 100644 --- a/src/Views/register.php +++ b/src/Views/register.php @@ -1,4 +1,9 @@ -extend(config('Auth')->views['layout']) ?> + +extend(config(Auth::class)->views['layout']) ?> section('title') ?> endSection() ?> From ad2e232a32773f49aed8b706678b43ee328e8e23 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Tue, 23 May 2023 14:26:21 +0100 Subject: [PATCH 045/401] suppress phpstan deprecation error warning --- src/Authentication/Passwords/ValidationRules.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 480920d28..29885e217 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -67,6 +67,8 @@ public function max_byte(?string $str, string $val): bool /** * Builds a new user instance from the global request. * + * @phpstan-ignore-next-line + * * @deprecated This will be removed soon. * * @see https://github.com/codeigniter4/shield/pull/747#discussion_r1198778666 From eedc9452ebc408f0a844dfa67f57a3dc2c60aca0 Mon Sep 17 00:00:00 2001 From: Samuel Asor Date: Tue, 23 May 2023 14:44:46 +0100 Subject: [PATCH 046/401] suppress phpstan deprecation warning --- src/Authentication/Passwords/ValidationRules.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 29885e217..c240ec016 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -40,6 +40,7 @@ public function strong_password(string $value, ?string &$error1 = null, array $d if (function_exists('auth') && auth()->user()) { $user = auth()->user(); } else { + /** @phpstan-ignore-next-line */ $user = empty($data) ? $this->buildUserFromRequest() : $this->buildUserFromData($data); } @@ -67,8 +68,6 @@ public function max_byte(?string $str, string $val): bool /** * Builds a new user instance from the global request. * - * @phpstan-ignore-next-line - * * @deprecated This will be removed soon. * * @see https://github.com/codeigniter4/shield/pull/747#discussion_r1198778666 From 12b326e792cb2bba4c9e0dc6dbfe271bc1083c85 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 21 Jun 2023 16:32:30 -0700 Subject: [PATCH 047/401] Update events.md Corrected typo --- docs/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/events.md b/docs/events.md index 0fd544153..c1609f030 100644 --- a/docs/events.md +++ b/docs/events.md @@ -23,7 +23,7 @@ for more information. #### register -Triggered when a new user has registered in the system. It's only argument is the `User` entity itself. +Triggered when a new user has registered in the system. The only argument is the `User` entity itself. ```php Events::trigger('register', $user); From 9416b09f45edf4039d9f5a8f7c6fd81b0ffd693a Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 28 Jun 2023 04:51:45 +0330 Subject: [PATCH 048/401] chore: remove php-stan from the precommit hook --- admin/pre-commit | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/admin/pre-commit b/admin/pre-commit index 296e140e2..617482977 100644 --- a/admin/pre-commit +++ b/admin/pre-commit @@ -19,21 +19,6 @@ if [ "$STAGED_PHP_FILES" != "" ]; then done fi -if [ "$FILES" != "" ]; then - echo "Running PHPStan..." - - if [ -d /proc/cygdrive ]; then - ./vendor/bin/phpstan analyse - else - php ./vendor/bin/phpstan analyse - fi - - if [ $? != 0 ]; then - echo "Fix the phpstan error(s) before commit." - exit 1 - fi -fi - if [ "$FILES" != "" ]; then echo "Running PHP CS Fixer..." From 4b7994cadba5728c7cf20fbdbecd7a877bf7842d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 28 Jun 2023 05:39:04 +0330 Subject: [PATCH 049/401] chore: update support docs link --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 42ee9f2d5..3a7963a38 100644 --- a/composer.json +++ b/composer.json @@ -91,6 +91,6 @@ "slack": "https://codeigniterchat.slack.com", "source": "https://github.com/codeigniter4/shield", "issues": "https://github.com/codeigniter4/shield/issues", - "docs": "https://github.com/codeigniter4/shield/blob/develop/docs/index.md" + "docs": "https://codeigniter4.github.io/shield/" } } From 40b95cbfd045fad4f7553c9f705cc1e1960eb7dc Mon Sep 17 00:00:00 2001 From: M Asnan Mustakim Date: Thu, 29 Jun 2023 12:30:16 +0700 Subject: [PATCH 050/401] add indonesian translation --- src/Language/id/Auth.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index 86f851d1a..ff97d9a3a 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -7,8 +7,8 @@ 'unknownAuthenticator' => '{0} bukan otentikator yang sah.', 'unknownUserProvider' => 'Tidak dapat menentukan Penyedia Pengguna yang akan digunakan.', 'invalidUser' => 'Tidak dapat menemukan pengguna yang spesifik.', - 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', - 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', + 'bannedUser' => 'Anda tidak dapat masuk karena saat ini Anda diblokir.', + 'logOutBannedUser' => 'Anda telah keluar karena Anda telah diblokir.', 'badAttempt' => 'Anda tidak dapat masuk. Harap periksa kredensial Anda.', 'noPassword' => 'Tidak dapat memvalidasi pengguna tanpa kata sandi.', 'invalidPassword' => 'Anda tidak dapat masuk. Harap periksa kata sandi Anda.', @@ -21,9 +21,9 @@ 'throttled' => 'Terlalu banyak permintaan yang dibuat dari alamat IP ini. Anda dapat mencoba lagi dalam {0} detik.', 'notEnoughPrivilege' => 'Anda tidak memiliki izin yang diperlukan untuk melakukan operasi yang diinginkan.', // JWT Exceptions - 'invalidJWT' => '(To be translated) The token is invalid.', - 'expiredJWT' => '(To be translated) The token has expired.', - 'beforeValidJWT' => '(To be translated) The token is not yet available.', + 'invalidJWT' => 'Token tidak valid.', + 'expiredJWT' => 'Token telah kedaluwarsa.', + 'beforeValidJWT' => 'Token belum tersedia.', 'email' => 'Alamat Email', 'username' => 'Nama Pengguna', @@ -66,7 +66,7 @@ 'errorPasswordPwned' => 'Kata sandi {0} telah bocor karena pelanggaran data dan telah dilihat {1, number} kali dalam {2} sandi yang disusupi.', 'suggestPasswordPwned' => '{0} tidak boleh digunakan sebagai kata sandi. Jika Anda menggunakannya di mana saja, segera ubah.', 'errorPasswordEmpty' => 'Kata sandi wajib diisi.', - 'errorPasswordTooLongBytes' => '(To be translated) Password cannot exceed {param} bytes in length.', + 'errorPasswordTooLongBytes' => 'Panjang kata sandi tidak boleh lebih dari {param} byte.', 'passwordChangeSuccess' => 'Kata sandi berhasil diubah', 'userDoesNotExist' => 'Kata sandi tidak diubah. User tidak ditemukan', 'resetTokenExpired' => 'Maaf, token setel ulang Anda sudah habis waktu.', @@ -95,7 +95,7 @@ 'emailActivateMailBody' => 'Silahkan gunakan kode dibawah ini untuk mengaktivasi akun Anda.', 'invalidActivateToken' => 'Kode tidak sesuai.', 'needActivate' => 'Anda harus menyelesaikan registrasi Anda dengan mengonfirmasi kode yang dikirim ke alamat email Anda.', - 'activationBlocked' => '(to be translated) You must activate your account before logging in.', + 'activationBlocked' => 'Anda harus mengaktifkan akun Anda sebelum masuk.', // Groups 'unknownGroup' => '{0} bukan grup yang sah.', From e53c7c955d0418c96aa9ba31b21362636ec1500a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 5 Jul 2023 20:13:20 +0900 Subject: [PATCH 051/401] Revert "refactor: use ::class keyword in config()" --- src/Auth.php | 7 +++---- src/Authentication/Actions/Email2FA.php | 3 +-- src/Authentication/Actions/EmailActivator.php | 3 +-- src/Authentication/Authenticators/AccessTokens.php | 9 ++++----- src/Authentication/Authenticators/Session.php | 8 ++++---- src/Authentication/Passwords.php | 2 +- src/Authentication/Passwords/ValidationRules.php | 3 +-- src/Authorization/Traits/Authorizable.php | 7 +++---- src/Config/Services.php | 5 ++--- src/Controllers/LoginController.php | 12 +++++------- src/Controllers/MagicLinkController.php | 8 +++----- src/Controllers/RegisterController.php | 14 +++++++------- .../2020-12-28-223112_create_auth_tables.php | 3 ++- src/Filters/ChainAuth.php | 3 +-- src/Filters/ForcePasswordResetFilter.php | 3 +-- src/Filters/SessionAuth.php | 3 +-- src/Views/email_2fa_show.php | 7 +------ src/Views/email_2fa_verify.php | 7 +------ src/Views/email_activate_show.php | 7 +------ src/Views/login.php | 7 +------ src/Views/magic_link_form.php | 7 +------ src/Views/magic_link_message.php | 7 +------ src/Views/register.php | 7 +------ 23 files changed, 47 insertions(+), 95 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 887dad949..41163479a 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -8,8 +8,6 @@ use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; -use CodeIgniter\Shield\Config\Auth as AuthConfig; -use CodeIgniter\Shield\Config\AuthRoutes; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserModel; @@ -108,7 +106,7 @@ public function authenticate(array $credentials): Result */ public function routes(RouteCollection &$routes, array $config = []): void { - $authRoutes = config(AuthRoutes::class)->routes; + $authRoutes = config('AuthRoutes')->routes; $routes->group('/', ['namespace' => 'CodeIgniter\Shield\Controllers'], static function (RouteCollection $routes) use ($authRoutes, $config): void { foreach ($authRoutes as $name => $row) { @@ -135,7 +133,8 @@ public function getProvider(): UserModel return $this->userProvider; } - $config = config(AuthConfig::class); + /** @var \CodeIgniter\Shield\Config\Auth $config */ + $config = config('Auth'); if (! property_exists($config, 'userProvider')) { throw AuthenticationException::forUnknownUserProvider(); diff --git a/src/Authentication/Actions/Email2FA.php b/src/Authentication/Actions/Email2FA.php index 1a3e4c75e..52827c99a 100644 --- a/src/Authentication/Actions/Email2FA.php +++ b/src/Authentication/Actions/Email2FA.php @@ -8,7 +8,6 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\RuntimeException; @@ -121,7 +120,7 @@ public function verify(IncomingRequest $request) } // Get our login redirect url - return redirect()->to(config(Auth::class)->loginRedirect()); + return redirect()->to(config('Auth')->loginRedirect()); } /** diff --git a/src/Authentication/Actions/EmailActivator.php b/src/Authentication/Actions/EmailActivator.php index eeba13c03..8e5216074 100644 --- a/src/Authentication/Actions/EmailActivator.php +++ b/src/Authentication/Actions/EmailActivator.php @@ -10,7 +10,6 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\LogicException; @@ -115,7 +114,7 @@ public function verify(IncomingRequest $request) $user->activate(); // Success! - return redirect()->to(config(Auth::class)->registerRedirect()) + return redirect()->to(config('Auth')->registerRedirect()) ->with('message', lang('Auth.registerSuccess')); } diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index 27beef2e8..a09000703 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -8,7 +8,6 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; use CodeIgniter\Shield\Models\TokenLoginModel; @@ -105,7 +104,7 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config(Auth::class)->authenticatorHeader['tokens']]), + 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), ]); } @@ -130,7 +129,7 @@ public function check(array $credentials): Result // Hasn't been used in a long time if ( $token->last_used_at - && $token->last_used_at->isBefore(Time::now()->subSeconds(config(Auth::class)->unusedTokenLifetime)) + && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) ) { return new Result([ 'success' => false, @@ -169,7 +168,7 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config(Auth::class)->authenticatorHeader['tokens']), + 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']), ])->isOK(); } @@ -227,7 +226,7 @@ public function getBearerToken(): ?string /** @var IncomingRequest $request */ $request = service('request'); - $header = $request->getHeaderLine(config(Auth::class)->authenticatorHeader['tokens']); + $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); if (empty($header)) { return null; diff --git a/src/Authentication/Authenticators/Session.php b/src/Authentication/Authenticators/Session.php index c3c65a8fa..c5e9ba138 100644 --- a/src/Authentication/Authenticators/Session.php +++ b/src/Authentication/Authenticators/Session.php @@ -13,7 +13,6 @@ use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; @@ -90,7 +89,8 @@ public function __construct(UserModel $provider) */ private function checkSecurityConfig(): void { - $securityConfig = config(Security::class); + /** @var Security $securityConfig */ + $securityConfig = config('Security'); if ($securityConfig->csrfProtection === 'cookie') { throw new SecurityException( @@ -275,8 +275,8 @@ private function recordLoginAttempt( ): void { // Determine the type of ID we're using. // Standard fields would be email, username, - // but any column within config(Auth::class)->validFields can be used. - $field = array_intersect(config(Auth::class)->validFields ?? [], array_keys($credentials)); + // but any column within config('Auth')->validFields can be used. + $field = array_intersect(config('Auth')->validFields ?? [], array_keys($credentials)); if (count($field) !== 1) { throw new InvalidArgumentException('Invalid credentials passed to recordLoginAttempt.'); diff --git a/src/Authentication/Passwords.php b/src/Authentication/Passwords.php index b466fca6c..3a5b7b041 100644 --- a/src/Authentication/Passwords.php +++ b/src/Authentication/Passwords.php @@ -145,7 +145,7 @@ public function check(string $password, ?User $user = null): Result */ public static function getMaxLengthRule(): string { - if (config(Auth::class)->hashAlgorithm === PASSWORD_BCRYPT) { + if (config('Auth')->hashAlgorithm === PASSWORD_BCRYPT) { return 'max_byte[72]'; } diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index c240ec016..087b15a30 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -6,7 +6,6 @@ use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; /** @@ -103,7 +102,7 @@ protected function buildUserFromData(array $data = []): User */ protected function prepareValidFields(): array { - $config = config(Auth::class); + $config = config('Auth'); $fields = array_merge($config->validFields, $config->personalFields); $fields[] = 'password'; diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index 967eed725..9e3f32ff1 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -6,7 +6,6 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authorization\AuthorizationException; -use CodeIgniter\Shield\Config\AuthGroups; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Shield\Models\GroupModel; use CodeIgniter\Shield\Models\PermissionModel; @@ -258,7 +257,7 @@ public function can(string $permission): bool $matrix = function_exists('setting') ? setting('AuthGroups.matrix') - : config(AuthGroups::class)->matrix; + : config('AuthGroups')->matrix; foreach ($this->groupCache as $group) { // Check exact match @@ -394,7 +393,7 @@ private function getConfigGroups(): array { return function_exists('setting') ? array_keys(setting('AuthGroups.groups')) - : array_keys(config(AuthGroups::class)->groups); + : array_keys(config('AuthGroups')->groups); } /** @@ -404,6 +403,6 @@ private function getConfigPermissions(): array { return function_exists('setting') ? array_keys(setting('AuthGroups.permissions')) - : array_keys(config(AuthGroups::class)->permissions); + : array_keys(config('AuthGroups')->permissions); } } diff --git a/src/Config/Services.php b/src/Config/Services.php index 201b2e199..1e002b6a0 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -8,7 +8,6 @@ use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\JWTManager; use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\Auth as AuthConfig; use Config\Services as BaseService; class Services extends BaseService @@ -22,7 +21,7 @@ public static function auth(bool $getShared = true): Auth return self::getSharedInstance('auth'); } - $config = config(AuthConfig::class); + $config = config('Auth'); return new Auth(new Authentication($config)); } @@ -36,7 +35,7 @@ public static function passwords(bool $getShared = true): Passwords return self::getSharedInstance('passwords'); } - return new Passwords(config(AuthConfig::class)); + return new Passwords(config('Auth')); } /** diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index a4adacaba..eab0aa316 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -8,8 +8,6 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\Auth; -use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Traits\Viewable; class LoginController extends BaseController @@ -26,7 +24,7 @@ class LoginController extends BaseController public function loginView() { if (auth()->loggedIn()) { - return redirect()->to(config(Auth::class)->loginRedirect()); + return redirect()->to(config('Auth')->loginRedirect()); } /** @var Session $authenticator */ @@ -73,7 +71,7 @@ public function loginAction(): RedirectResponse return redirect()->route('auth-action-show')->withCookies(); } - return redirect()->to(config(Auth::class)->loginRedirect())->withCookies(); + return redirect()->to(config('Auth')->loginRedirect())->withCookies(); } /** @@ -87,11 +85,11 @@ protected function getValidationRules(): array return setting('Validation.login') ?? [ // 'username' => [ // 'label' => 'Auth.username', - // 'rules' => config(AuthSession::class)->usernameValidationRules, + // 'rules' => config('AuthSession')->usernameValidationRules, // ], 'email' => [ 'label' => 'Auth.email', - 'rules' => config(AuthSession::class)->emailValidationRules, + 'rules' => config('AuthSession')->emailValidationRules, ], 'password' => [ 'label' => 'Auth.password', @@ -110,7 +108,7 @@ public function logoutAction(): RedirectResponse { // Capture logout redirect URL before auth logout, // otherwise you cannot check the user in `logoutRedirect()`. - $url = config(Auth::class)->logoutRedirect(); + $url = config('Auth')->logoutRedirect(); auth()->logout(); diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index a4422a3a1..45cfb1060 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -10,8 +10,6 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Config\Auth; -use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Models\LoginModel; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; @@ -53,7 +51,7 @@ public function __construct() public function loginView() { if (auth()->loggedIn()) { - return redirect()->to(config(Auth::class)->loginRedirect()); + return redirect()->to(config('Auth')->loginRedirect()); } return $this->view(setting('Auth.views')['magic-link-login']); @@ -191,7 +189,7 @@ public function verify(): RedirectResponse Events::trigger('magicLogin'); // Get our login redirect url - return redirect()->to(config(Auth::class)->loginRedirect()); + return redirect()->to(config('Auth')->loginRedirect()); } /** @@ -226,7 +224,7 @@ protected function getValidationRules(): array return [ 'email' => [ 'label' => 'Auth.email', - 'rules' => config(AuthSession::class)->emailValidationRules, + 'rules' => config('AuthSession')->emailValidationRules, ], ]; } diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index e0e2a8045..469c23007 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -12,7 +12,6 @@ use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Config\Auth; -use CodeIgniter\Shield\Config\AuthSession; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\ValidationException; use CodeIgniter\Shield\Models\UserModel; @@ -47,7 +46,8 @@ public function initController( $logger ); - $authConfig = config(Auth::class); + /** @var Auth $authConfig */ + $authConfig = config('Auth'); $this->tables = $authConfig->tables; } @@ -59,7 +59,7 @@ public function initController( public function registerView() { if (auth()->loggedIn()) { - return redirect()->to(config(Auth::class)->registerRedirect()); + return redirect()->to(config('Auth')->registerRedirect()); } // Check if registration is allowed @@ -85,7 +85,7 @@ public function registerView() public function registerAction(): RedirectResponse { if (auth()->loggedIn()) { - return redirect()->to(config(Auth::class)->registerRedirect()); + return redirect()->to(config('Auth')->registerRedirect()); } // Check if registration is allowed @@ -145,7 +145,7 @@ public function registerAction(): RedirectResponse $authenticator->completeLogin($user); // Success! - return redirect()->to(config(Auth::class)->registerRedirect()) + return redirect()->to(config('Auth')->registerRedirect()) ->with('message', lang('Auth.registerSuccess')); } @@ -178,11 +178,11 @@ protected function getUserEntity(): User protected function getValidationRules(): array { $registrationUsernameRules = array_merge( - config(AuthSession::class)->usernameValidationRules, + config('AuthSession')->usernameValidationRules, [sprintf('is_unique[%s.username]', $this->tables['users'])] ); $registrationEmailRules = array_merge( - config(AuthSession::class)->emailValidationRules, + config('AuthSession')->emailValidationRules, [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); diff --git a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php index 042c352e0..792cdad3a 100644 --- a/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php +++ b/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php @@ -19,7 +19,8 @@ class CreateAuthTables extends Migration public function __construct(?Forge $forge = null) { - $authConfig = config(Auth::class); + /** @var Auth $authConfig */ + $authConfig = config('Auth'); if ($authConfig->DBGroup !== null) { $this->DBGroup = $authConfig->DBGroup; diff --git a/src/Filters/ChainAuth.php b/src/Filters/ChainAuth.php index 2eccdd105..eb4ee0fbb 100644 --- a/src/Filters/ChainAuth.php +++ b/src/Filters/ChainAuth.php @@ -10,7 +10,6 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Shield\Config\Auth; /** * Chain Authentication Filter. @@ -42,7 +41,7 @@ public function before(RequestInterface $request, $arguments = null) helper('settings'); - $chain = config(Auth::class)->authenticationChain; + $chain = config('Auth')->authenticationChain; foreach ($chain as $alias) { if (auth($alias)->loggedIn()) { diff --git a/src/Filters/ForcePasswordResetFilter.php b/src/Filters/ForcePasswordResetFilter.php index 8b320cbdc..bfa3d21aa 100644 --- a/src/Filters/ForcePasswordResetFilter.php +++ b/src/Filters/ForcePasswordResetFilter.php @@ -11,7 +11,6 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Config\Auth; /** * Force Password Reset Filter. @@ -39,7 +38,7 @@ public function before(RequestInterface $request, $arguments = null) $authenticator = auth('session')->getAuthenticator(); if ($authenticator->loggedIn() && $authenticator->getUser()->requiresPasswordReset()) { - return redirect()->to(config(Auth::class)->forcePasswordResetRedirect()); + return redirect()->to(config('Auth')->forcePasswordResetRedirect()); } } diff --git a/src/Filters/SessionAuth.php b/src/Filters/SessionAuth.php index e280f4785..d95f89c07 100644 --- a/src/Filters/SessionAuth.php +++ b/src/Filters/SessionAuth.php @@ -11,7 +11,6 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Config\Auth; /** * Session Authentication Filter. @@ -57,7 +56,7 @@ public function before(RequestInterface $request, $arguments = null) $error = $user->getBanMessage() ?? lang('Auth.logOutBannedUser'); $authenticator->logout(); - return redirect()->to(config(Auth::class)->logoutRedirect()) + return redirect()->to(config('Auth')->logoutRedirect()) ->with('error', $error); } diff --git a/src/Views/email_2fa_show.php b/src/Views/email_2fa_show.php index 4a8275ff4..46cf68cf4 100644 --- a/src/Views/email_2fa_show.php +++ b/src/Views/email_2fa_show.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/email_2fa_verify.php b/src/Views/email_2fa_verify.php index 29aeb8440..722977b8c 100644 --- a/src/Views/email_2fa_verify.php +++ b/src/Views/email_2fa_verify.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index fe5a4f11d..491fc2fbf 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/login.php b/src/Views/login.php index 7d47f5ea0..34193ce19 100644 --- a/src/Views/login.php +++ b/src/Views/login.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index 16fe326f3..100ce67f6 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/magic_link_message.php b/src/Views/magic_link_message.php index f1d961110..0855272f3 100644 --- a/src/Views/magic_link_message.php +++ b/src/Views/magic_link_message.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> diff --git a/src/Views/register.php b/src/Views/register.php index d6060a7e5..4c8fb301e 100644 --- a/src/Views/register.php +++ b/src/Views/register.php @@ -1,9 +1,4 @@ - -extend(config(Auth::class)->views['layout']) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> endSection() ?> From 1464165b3318ad91bc9b7b02141e24efb883a8da Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Thu, 6 Jul 2023 11:51:09 +0800 Subject: [PATCH 052/401] chore: add no-merge-commits.yml --- .github/workflows/no-merge-commits.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/no-merge-commits.yml diff --git a/.github/workflows/no-merge-commits.yml b/.github/workflows/no-merge-commits.yml new file mode 100644 index 000000000..9c9388589 --- /dev/null +++ b/.github/workflows/no-merge-commits.yml @@ -0,0 +1,22 @@ +name: Detect Merge Commits + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + test: + name: Check for merge commits + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Run test + uses: NexusPHP/no-merge-commits@v1.2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} From 377cb5eedf3213e8b1c05df1cb25e25afad97645 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Fri, 4 Aug 2023 10:18:15 +0330 Subject: [PATCH 053/401] docs: fix example code of extending controller --- docs/customization.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/customization.md b/docs/customization.md index 6e21b7564..52f6108a3 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -133,10 +133,11 @@ updates that might happen in the controllers. namespace App\Controllers; use CodeIgniter\Shield\Controllers\LoginController as ShieldLogin; +use CodeIgniter\HTTP\RedirectResponse; class LoginController extends ShieldLogin { - public function logoutAction() + public function logoutAction(): RedirectResponse { // new functionality } From 5603e052e277ad5df5815a9352355e681554ce4e Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Fri, 11 Aug 2023 03:36:35 +0300 Subject: [PATCH 054/401] lang: [ar] adding Arabic translation support to shield --- .../Language/AbstractTranslationTestCase.php | 2 +- tests/Language/ArabicTranslationTest.php | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/Language/ArabicTranslationTest.php diff --git a/tests/Language/AbstractTranslationTestCase.php b/tests/Language/AbstractTranslationTestCase.php index 75feb3a5c..b3b45a21f 100644 --- a/tests/Language/AbstractTranslationTestCase.php +++ b/tests/Language/AbstractTranslationTestCase.php @@ -47,7 +47,7 @@ abstract class AbstractTranslationTestCase extends TestCase * @var array */ public static array $locales = [ - // ArabicTranslationTest::class => 'ar', + ArabicTranslationTest::class => 'ar', BulgarianTranslationTest::class => 'bg', // BosnianTranslationTest::class => 'bs', // CzechTranslationTest::class => 'cs', diff --git a/tests/Language/ArabicTranslationTest.php b/tests/Language/ArabicTranslationTest.php new file mode 100644 index 000000000..22eeaf14f --- /dev/null +++ b/tests/Language/ArabicTranslationTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Language; + +/** + * @internal + */ +final class ArabicTranslationTest extends AbstractTranslationTestCase +{ +} From 2994f7f9b20c916bfd27351628c79bc8ec7fd262 Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Fri, 11 Aug 2023 03:39:02 +0300 Subject: [PATCH 055/401] lang: [ar] adding Arabic translation support to shield --- src/Language/ar/Auth.php | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/Language/ar/Auth.php diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php new file mode 100644 index 000000000..9e940dbb0 --- /dev/null +++ b/src/Language/ar/Auth.php @@ -0,0 +1,107 @@ + '{0} ليس توثيق صحيح.', + 'unknownUserProvider' => 'تعذر تحديد موفر المستخدم الذي يجب استخدامه.', + 'invalidUser' => 'تعذر تحديد المستخدم المدخل.', + 'bannedUser' => 'لا يمكن تسجيل الدخول حيث أن حسابك موقوف حالياً.', + 'logOutBannedUser' => 'لقد تم تسجيل خروجك وذلك لانه تم حظرك.', + 'badAttempt' => 'لا يمكن تسجيل دخولك. يُرجى التحقق من صحة البيانات الخاصة بك.', + 'noPassword' => 'لا يمكن التحقق من هوية المستخدم بدون كلمة مرور.', + 'invalidPassword' => 'تعذر تسجيل الدخول. يرجى التحقق من كلمة المرور الخاصة بك.', + 'noToken' => 'يجب أن يحتوي كل طلب على رمز حامل (token) في الهيدر {0}.', + 'badToken' => 'رمز الوصول (Token) غير صالح.', + 'oldToken' => 'انتهت صلاحية رمز الوصول.', + 'noUserEntity' => 'يجب توفير كيان المستخدم للتحقق من صحة كلمة المرور.', + 'invalidEmail' => 'تعذر التحقق من تطابق عنوان البريد الإلكتروني مع البريد الإلكتروني المسجل.', + 'unableSendEmailToUser' => 'عذرا ، كانت هناك مشكلة في إرسال البريد الإلكتروني. لم نتمكن من إرسال بريد إلكتروني إلى "{0}".', + 'throttled' => 'تم إجراء العديد من الطلبات من عنوان IP هذا. يمكنك المحاولة مرة أخرى في غضون {0} ثانية.', + 'notEnoughPrivilege' => 'ليس لديك الإذن اللازم لإجراء العملية المطلوبة.', + // JWT Exceptions + 'invalidJWT' => 'الرمز غير صالح.', + 'expiredJWT' => 'انتهت صلاحية الرمز.', + 'beforeValidJWT' => 'الرمز غير متوفر بعد.', + + 'email' => 'عنوان البريد الالكتروني', + 'username' => 'اسم المستخدم', + 'password' => 'كلمة المرور', + 'passwordConfirm' => 'كلمة المرور (مرة اخرى)', + 'haveAccount' => 'هل لديك حساب بالفعل؟', + + // Buttons + 'confirm' => 'تاكيد', + 'send' => 'ارسال', + + // Registration + 'register' => 'تسجيل حساب', + 'registerDisabled' => 'تسجيل حساب جديد غير مسموح الان.', + 'registerSuccess' => 'أهلا بك!', + + // Login + 'login' => 'تسجيل دخول', + 'needAccount' => 'هل تحتاج الى حساب؟', + 'rememberMe' => 'تذكر دخولي؟', + 'forgotPassword' => 'نسيت كلمة المرور؟', + 'useMagicLink' => 'تسجيل دخول بواسطة رابط دخول', + 'magicLinkSubject' => 'رابط الدخول الخاص بك', + 'magicTokenNotFound' => 'تعذر التحقق من صحة الرابط.', + 'magicLinkExpired' => 'عذرا ، لقد انتهت صلاحية الرابط.', + 'checkYourEmail' => 'تحقق من بريدك الالكتروني!', + 'magicLinkDetails' => 'لقد أرسلنا لك بريدًا إلكترونيًا يحتوي على رابط تسجيل الدخول بالداخل. الرابط صالح فقط لمدة {0} دقيقة.', + 'magicLinkDisabled' => 'استخدام رابط الدخول MagicLink غير مسموح به حاليا.', + 'successLogout' => 'لقد قمت بتسجيل الخروج بنجاح.', + 'backToLogin' => 'العودة إلى نموذج تسجيل الدخول', + + // Passwords + 'errorPasswordLength' => 'يجب أن تتكون كلمات المرور من {0 ، number} من الأحرف على الأقل.', + 'suggestPasswordLength' => 'عبارات المرور - التي يصل طولها إلى 255 حرفًا - تجعل كلمات المرور أكثر أمانًا ويسهل تذكرها.', + 'errorPasswordCommon' => 'يجب ألا تكون كلمة المرور كلمة مرور شائعة.', + 'suggestPasswordCommon' => 'تم فحص كلمة المرور مقابل أكثر من 65 ألف كلمة مرور أو كلمات مرور شائعة الاستخدام تم تسريبها من خلال الاختراقات.', + 'errorPasswordPersonal' => 'لا يمكن أن تحتوي كلمات المرور على معلومات شخصية تم إعادة تجزئتها (re-hashed).', + 'suggestPasswordPersonal' => 'لا يجب اجزاء من عنوان بريدك الإلكتروني أو اسم المستخدم ككلمات مرور.', + 'errorPasswordTooSimilar' => 'كلمة المرور مشابهة جدًا لاسم المستخدم.', + 'suggestPasswordTooSimilar' => 'لا تستخدم أجزاء من اسم المستخدم الخاص بك في كلمة المرور الخاصة بك.', + 'errorPasswordPwned' => 'تم الكشف عن كلمة المرور {0} بسبب اختراق البيانات وشوهدت {1 ، عدد} مرة في {2} في كلمات المرور المخترقة.', + 'suggestPasswordPwned' => 'يجب عدم استخدام {0} أبدًا ككلمة مرور. إذا كنت تستخدمها في أي مكان ، فقم بتغييرها على الفور.', + 'errorPasswordEmpty' => 'كلمة مرور مطلوبة', + 'errorPasswordTooLongBytes' => 'لا يمكن أن يتجاوز طول كلمة المرور {param} بايت.', + 'passwordChangeSuccess' => 'تم تغيير كلمة المرور بنجاح', + 'userDoesNotExist' => 'لم يتم تغيير كلمة المرور. المستخدم غير موجود', + 'resetTokenExpired' => 'آسف. انتهت صلاحية رمز إعادة التعيين الخاص بك.', + + // Email Globals + 'emailInfo' => 'بعض المعلومات عن الشخص:', + 'emailIpAddress' => 'عنوان IP:', + 'emailDevice' => 'الجهاز:', + 'emailDate' => 'التاريخ:', + + // 2FA + 'email2FATitle' => 'التحقق بخطوتين', + 'confirmEmailAddress' => 'أكد عنوان بريدك الألكتروني.', + 'emailEnterCode' => 'تأكيد بريدك الإلكتروني', + 'emailConfirmCode' => 'أدخل الرمز المكون من 6 أرقام الذي أرسلناه للتو إلى عنوان بريدك الإلكتروني.', + 'email2FASubject' => 'رمز المصادقة الخاص بك', + 'email2FAMailBody' => 'رمز المصادقة الخاص بك هو:', + 'invalid2FAToken' => 'رمز المصادقة غير صحيح.', + 'need2FA' => 'يجب عليك إكمال التحقق بخطوتين.', + 'needVerification' => 'تحقق من بريدك الإلكتروني لإكمال تنشيط الحساب.', + + // Activate + 'emailActivateTitle' => 'تفعيل البريد الإلكتروني', + 'emailActivateBody' => 'لقد أرسلنا إليك بريدًا إلكترونيًا يحتوي على رمز لتأكيد عنوان بريدك الإلكتروني. انسخ هذا الرمز والصقه أدناه.', + 'emailActivateSubject' => 'رمز التفعيل الخاص بك', + 'emailActivateMailBody' => 'يرجى استخدام الكود أدناه لتفعيل حسابك والبدء في استخدام الموقع.', + 'invalidActivateToken' => 'الرمز غير صحيح', + 'needActivate' => 'يجب عليك إكمال تسجيل حسابك عن طريق تأكيد الرمز المرسل إلى عنوان بريدك الإلكتروني.', + 'activationBlocked' => 'يجب عليك تفعيل حسابك قبل تسجيل الدخول.', + + // Groups + 'unknownGroup' => '{0} ليست مجموعة صالحة.', + 'missingTitle' => 'يجب أن يكون للمجموعات عنوان.', + + // Permissions + 'unknownPermission' => '{0} ليس صلاحية صحيحة.', +]; From f08e11b8d667f8b8c81b813d1b8a8f7380a1e558 Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Fri, 11 Aug 2023 03:52:59 +0300 Subject: [PATCH 056/401] remove the unused yet language variable magicLinkDisabled from language file [ar] --- src/Language/ar/Auth.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 9e940dbb0..460241154 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -51,7 +51,6 @@ 'magicLinkExpired' => 'عذرا ، لقد انتهت صلاحية الرابط.', 'checkYourEmail' => 'تحقق من بريدك الالكتروني!', 'magicLinkDetails' => 'لقد أرسلنا لك بريدًا إلكترونيًا يحتوي على رابط تسجيل الدخول بالداخل. الرابط صالح فقط لمدة {0} دقيقة.', - 'magicLinkDisabled' => 'استخدام رابط الدخول MagicLink غير مسموح به حاليا.', 'successLogout' => 'لقد قمت بتسجيل الخروج بنجاح.', 'backToLogin' => 'العودة إلى نموذج تسجيل الدخول', From 84d6ed407ed870c7173511868c521b6c5b64d91d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 9 Aug 2023 22:26:55 +0330 Subject: [PATCH 057/401] fix: add limitation if `allowMagicLinkLogins` is `false` --- src/Controllers/MagicLinkController.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 45cfb1060..4ff0e86b7 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -50,6 +50,11 @@ public function __construct() */ public function loginView() { + // Check if magic-link is not allowed + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } + if (auth()->loggedIn()) { return redirect()->to(config('Auth')->loginRedirect()); } @@ -66,6 +71,11 @@ public function loginView() */ public function loginAction() { + // Check if magic-link is not allowed + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } + // Validate email format $rules = $this->getValidationRules(); if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) { From 40af448fb188ebef1f922e0cc50265361e07734b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 9 Aug 2023 22:42:10 +0330 Subject: [PATCH 058/401] lang: add new string for `magicLinkDisabled` --- src/Language/bg/Auth.php | 1 + src/Language/de/Auth.php | 1 + src/Language/en/Auth.php | 1 + src/Language/es/Auth.php | 1 + src/Language/fa/Auth.php | 1 + src/Language/fr/Auth.php | 1 + src/Language/id/Auth.php | 1 + src/Language/it/Auth.php | 1 + src/Language/ja/Auth.php | 1 + src/Language/lt/Auth.php | 1 + src/Language/pt-BR/Auth.php | 1 + src/Language/pt/Auth.php | 1 + src/Language/sk/Auth.php | 1 + src/Language/sr/Auth.php | 1 + src/Language/sv-SE/Auth.php | 1 + src/Language/tr/Auth.php | 1 + src/Language/uk/Auth.php | 1 + 17 files changed, 17 insertions(+) diff --git a/src/Language/bg/Auth.php b/src/Language/bg/Auth.php index 625c52592..f83fda6e9 100644 --- a/src/Language/bg/Auth.php +++ b/src/Language/bg/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Съжаляваме, линкът е изтекъл.', 'checkYourEmail' => 'Проверете вашия имейл!', 'magicLinkDetails' => 'Току що ви изпратихме имейл с линк за вход. Линкът ще бъде валиден само {0} минути.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Успешно излязохте от системата.', 'backToLogin' => 'Обратно към входа', diff --git a/src/Language/de/Auth.php b/src/Language/de/Auth.php index 7b982fd2c..977feb0a6 100644 --- a/src/Language/de/Auth.php +++ b/src/Language/de/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Sorry, der Link ist abgelaufen.', 'checkYourEmail' => 'Prüfen Sie Ihre E-Mail!', 'magicLinkDetails' => 'Wir haben Ihnen gerade eine E-Mail mit einem Login-Link geschickt. Er ist nur für {0} Minuten gültig.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Sie haben sich erfolgreich abgemeldet.', 'backToLogin' => 'Zurück zur Anmeldung', diff --git a/src/Language/en/Auth.php b/src/Language/en/Auth.php index f64911f69..d9271cc78 100644 --- a/src/Language/en/Auth.php +++ b/src/Language/en/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Sorry, link has expired.', 'checkYourEmail' => 'Check your email!', 'magicLinkDetails' => 'We just sent you an email with a Login link inside. It is only valid for {0} minutes.', + 'magicLinkDisabled' => 'Use of MagicLink is currently not allowed.', 'successLogout' => 'You have successfully logged out.', 'backToLogin' => 'Back to Login', diff --git a/src/Language/es/Auth.php b/src/Language/es/Auth.php index a6b52b4bc..027e7d4ec 100644 --- a/src/Language/es/Auth.php +++ b/src/Language/es/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Lo siento, el enlace ha caducado.', 'checkYourEmail' => '¡Revisa tu correo electrónico!', 'magicLinkDetails' => 'Acabamos de enviarte un correo electrónico con un enlace de inicio de sesión. Solo es válido durante {0} minutos.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Has cerrado sesión correctamente.', 'backToLogin' => 'Volver al inicio de sesión', diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index 5c0a05f5a..4cedff90d 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'متاسفانه, لینک منقضی شده است.', 'checkYourEmail' => 'ایمیلتان را بررسی کنید!', 'magicLinkDetails' => 'ما فقط یک لینک ورود به ایمیلتان ارسال کردیم. این لینک فقط برای {0} دقیقه معتبر خواهد بود.', + 'magicLinkDisabled' => 'امکان استفاده از لینک جادویی وجود ندارد.', 'successLogout' => 'با موفقیت خارج شدید.', 'backToLogin' => 'بازگشت به ورود به سیستم', diff --git a/src/Language/fr/Auth.php b/src/Language/fr/Auth.php index 3a3d2b8a0..9bf794ea3 100644 --- a/src/Language/fr/Auth.php +++ b/src/Language/fr/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Désolé, le lien a expiré.', 'checkYourEmail' => 'Vérifier votre email !', 'magicLinkDetails' => 'Nous venons de vous envoyer un email contenant un lien de connexion. Il n\'est valable que {0} minutes.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Vous avez été déconnecté avec succès.', 'backToLogin' => 'Retour à la connexion', diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index ff97d9a3a..62a90d8e6 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Maaf, tautan sudah tidak berlaku.', 'checkYourEmail' => 'Periksa email Anda!', 'magicLinkDetails' => 'Kami baru saja mengirimi Anda email dengan tautan Masuk di dalamnya. Ini hanya berlaku selama {0} menit.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Anda telah berhasil keluar.', 'backToLogin' => 'Kembali ke masuk', diff --git a/src/Language/it/Auth.php b/src/Language/it/Auth.php index e62989d7c..dc531886a 100644 --- a/src/Language/it/Auth.php +++ b/src/Language/it/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Spiacente, il link è scaduto.', 'checkYourEmail' => 'Controlla la tua email!', 'magicLinkDetails' => 'Ti abbiamo appena inviato una mail contenente un Login link. È valido solo per {0} minuti.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Hai effettuato il logout con successo.', 'backToLogin' => 'Torna al login', diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index 0a3befa0a..f8722ab12 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => '申し訳ございません、リンクは切れています。', // 'Sorry, link has expired.' 'checkYourEmail' => 'メールをチェックしてください!', // 'Check your email!' 'magicLinkDetails' => 'ログインリンクが含まれたメールを送信しました。これは {0} 分間だけ有効です。', // 'We just sent you an email with a Login link inside. It is only valid for {0} minutes.' + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => '正常にログアウトしました。', // 'You have successfully logged out.' 'backToLogin' => 'ログインに戻る', // 'Back to Login' diff --git a/src/Language/lt/Auth.php b/src/Language/lt/Auth.php index 6efc8e00a..0b10631c0 100644 --- a/src/Language/lt/Auth.php +++ b/src/Language/lt/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Deja, nuorodos galiojimas baigėsi.', 'checkYourEmail' => 'Patikrinkite savo el. paštą!', 'magicLinkDetails' => 'Mes ką tik išsiuntėme Jums el. laišką su prisijungimo nuoroda. Ji galios tiki {0} minučių(-es).', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Jūs sėkmingai atsijungėte.', 'backToLogin' => 'Grįžti į prisijungimą', diff --git a/src/Language/pt-BR/Auth.php b/src/Language/pt-BR/Auth.php index 4885d3298..b001a9511 100644 --- a/src/Language/pt-BR/Auth.php +++ b/src/Language/pt-BR/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Desculpe, o link expirou.', 'checkYourEmail' => 'Verifique seu e-mail!', 'magicLinkDetails' => 'Acabamos de enviar um e-mail com um link de Login. Ele é válido apenas por {0} minutos.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Você saiu com sucesso.', 'backToLogin' => 'Voltar para o login', diff --git a/src/Language/pt/Auth.php b/src/Language/pt/Auth.php index d4b5261c8..ee7a5ab90 100644 --- a/src/Language/pt/Auth.php +++ b/src/Language/pt/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Desculpe, o link expirou.', 'checkYourEmail' => 'Verifique o seu e-mail!', 'magicLinkDetails' => 'Acabamos de enviar um e-mail com um link de Login. Ele é válido apenas por {0} minutos.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Saiu com sucesso.', 'backToLogin' => 'Voltar ao login', diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index c18741d9b..bc0de4962 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Ľutujeme, platnosť odkazu vypršala.', 'checkYourEmail' => 'Skontrolujte e-mail', 'magicLinkDetails' => 'Práve sme vám poslali e-mail s odkazom na prihlásenie. Platí iba {0} minút.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Úspešne ste sa odhlásili.', 'backToLogin' => 'Späť na prihlásenie', diff --git a/src/Language/sr/Auth.php b/src/Language/sr/Auth.php index 12236dfc2..8030c6a4d 100644 --- a/src/Language/sr/Auth.php +++ b/src/Language/sr/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Žao nam je, link je istekao.', 'checkYourEmail' => 'Proverite Vaš email!', 'magicLinkDetails' => 'Upravo smo Vam poslali pristupni link. Pristupni link će biti validan još samo {0} minuta.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Uspešno ste se odjavili sa sistema.', 'backToLogin' => 'Nazad na prijavljivanje', diff --git a/src/Language/sv-SE/Auth.php b/src/Language/sv-SE/Auth.php index d57ec3b33..1031b7963 100644 --- a/src/Language/sv-SE/Auth.php +++ b/src/Language/sv-SE/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Tyvärr, länken har gått ut.', 'checkYourEmail' => 'Kontrollera din epost!', 'magicLinkDetails' => 'En login-länk har skickats med epost. Den gäller bara i {0} minuter.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Du har loggats ut.', 'backToLogin' => 'Tillbaka till inloggning', diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 6d1766732..b54865ba5 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Üzgünüm, bağlantının süresi doldu.', 'checkYourEmail' => 'E-postanı kontrol et!', 'magicLinkDetails' => 'Az önce size içinde bir Giriş bağlantısı olan bir e-posta gönderdik. Bağlantı {0} dakika için geçerlidir.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Başarıyla çıkış yaptınız.', 'backToLogin' => 'Girişe Geri Dön', diff --git a/src/Language/uk/Auth.php b/src/Language/uk/Auth.php index 1ff36314b..c012540ce 100644 --- a/src/Language/uk/Auth.php +++ b/src/Language/uk/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'Вибачте, термін дії посилання закінчився.', 'checkYourEmail' => 'Перевірте свою електронну пошту!', 'magicLinkDetails' => 'Ми щойно надіслали вам електронний лист із посиланням для входу. Він дійсний лише протягом {0} хвилин.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'Ви успішно вийшли.', 'backToLogin' => 'Повернутися до входу', From 71fee5b5d5ba202ff765aa54e52b4e33614e2ddb Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 9 Aug 2023 22:57:25 +0330 Subject: [PATCH 059/401] tests: add test for magicLinkDisabled --- tests/Controllers/MagicLinkTest.php | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Controllers/MagicLinkTest.php b/tests/Controllers/MagicLinkTest.php index 7bf92fc6b..dfb963993 100644 --- a/tests/Controllers/MagicLinkTest.php +++ b/tests/Controllers/MagicLinkTest.php @@ -120,4 +120,36 @@ public function testBackToLoginLinkOnPage(): void $result = $this->get('/login/magic-link'); $this->assertStringContainsString(lang('Auth.backToLogin'), $result->getBody()); } + + public function testMagicLinkRedirectsIfNotAllowed(): void + { + $config = config('Auth'); + $config->allowMagicLinkLogins = false; + Factories::injectMock('config', 'Auth', $config); + + $result = $this->withSession()->get('/login/magic-link'); + + $result->assertStatus(302); + $result->assertRedirect(); + $result->assertSessionHas( + 'error', + lang('Auth.magicLinkDisabled'), + ); + } + + public function testMagicLinkActionRedirectsIfNotAllowed(): void + { + $config = config('Auth'); + $config->allowMagicLinkLogins = false; + Factories::injectMock('config', 'Auth', $config); + + $result = $this->withSession()->post('/login/magic-link'); + + $result->assertStatus(302); + $result->assertRedirect(); + $result->assertSessionHas( + 'error', + lang('Auth.magicLinkDisabled'), + ); + } } From 9faa42e33ee8fceb5937c615a0aa774c7e1f23c4 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 10 Aug 2023 07:20:07 +0330 Subject: [PATCH 060/401] tests: add test for check verify-magic-link route if `allowMagicLinkLogins=false` --- tests/Controllers/MagicLinkTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Controllers/MagicLinkTest.php b/tests/Controllers/MagicLinkTest.php index dfb963993..b8b1f794e 100644 --- a/tests/Controllers/MagicLinkTest.php +++ b/tests/Controllers/MagicLinkTest.php @@ -152,4 +152,20 @@ public function testMagicLinkActionRedirectsIfNotAllowed(): void lang('Auth.magicLinkDisabled'), ); } + + public function testMagicLinkVerifyRedirectsIfNotAllowed(): void + { + $config = config('Auth'); + $config->allowMagicLinkLogins = false; + Factories::injectMock('config', 'Auth', $config); + + $result = $this->withSession()->get('/login/verify-magic-link'); + + $result->assertStatus(302); + $result->assertRedirect(); + $result->assertSessionHas( + 'error', + lang('Auth.magicLinkDisabled'), + ); + } } From 569d4b902831871f6dbde8075ada5282562fb913 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 10 Aug 2023 07:32:08 +0330 Subject: [PATCH 061/401] fix: add limite verify-magic-link route if magicLinkDisabled --- src/Controllers/MagicLinkController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 4ff0e86b7..4f1ea05a9 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -145,6 +145,11 @@ protected function displayMessage(): string */ public function verify(): RedirectResponse { + // Check if magic-link is not allowed + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } + $token = $this->request->getGet('token'); /** @var UserIdentityModel $identityModel */ From 635aacb9181a327ce3dcba87566a5f5a38266a94 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Fri, 11 Aug 2023 07:32:22 +0330 Subject: [PATCH 062/401] refactor: use praivate method for check magicLink is disabled --- src/Controllers/MagicLinkController.php | 27 ++++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 4f1ea05a9..a6c16558e 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -50,10 +50,7 @@ public function __construct() */ public function loginView() { - // Check if magic-link is not allowed - if (! setting('Auth.allowMagicLinkLogins')) { - return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); - } + $this->displayErrorMagicLinkDisabled(); if (auth()->loggedIn()) { return redirect()->to(config('Auth')->loginRedirect()); @@ -71,10 +68,7 @@ public function loginView() */ public function loginAction() { - // Check if magic-link is not allowed - if (! setting('Auth.allowMagicLinkLogins')) { - return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); - } + $this->displayErrorMagicLinkDisabled(); // Validate email format $rules = $this->getValidationRules(); @@ -145,10 +139,7 @@ protected function displayMessage(): string */ public function verify(): RedirectResponse { - // Check if magic-link is not allowed - if (! setting('Auth.allowMagicLinkLogins')) { - return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); - } + $this->displayErrorMagicLinkDisabled(); $token = $this->request->getGet('token'); @@ -228,6 +219,18 @@ private function recordLoginAttempt( ); } + /** + * Display error to the user if magic-link is not allowed. + * + * @return RedirectResponse|void + */ + private function displayErrorMagicLinkDisabled() + { + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } + } + /** * Returns the rules that should be used for validation. * From 6d56e4635079cace978ac72b6104f5e486cdeeaf Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Fri, 11 Aug 2023 07:35:42 +0330 Subject: [PATCH 063/401] lang: [ar] add string magicLinkDisabled --- src/Language/ar/Auth.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 460241154..2eaad4faa 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -51,6 +51,7 @@ 'magicLinkExpired' => 'عذرا ، لقد انتهت صلاحية الرابط.', 'checkYourEmail' => 'تحقق من بريدك الالكتروني!', 'magicLinkDetails' => 'لقد أرسلنا لك بريدًا إلكترونيًا يحتوي على رابط تسجيل الدخول بالداخل. الرابط صالح فقط لمدة {0} دقيقة.', + 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', 'successLogout' => 'لقد قمت بتسجيل الخروج بنجاح.', 'backToLogin' => 'العودة إلى نموذج تسجيل الدخول', From c0e067dc6c8e6db1710a5c5eb06437ee0d416bb4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 12 Aug 2023 09:37:16 +0900 Subject: [PATCH 064/401] docs: update RELEASE.md --- admin/RELEASE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin/RELEASE.md b/admin/RELEASE.md index 27f73b24a..69cb216b2 100644 --- a/admin/RELEASE.md +++ b/admin/RELEASE.md @@ -71,7 +71,7 @@ the changelog. * Watch for the "docs" action and verify that the user guide updated: * [docs](https://github.com/codeigniter4/shield/actions/workflows/docs.yml) * Fast-forward `develop` branch to catch the merge commit from `master` - (note: pushing to develop is restricted to administrators): + (note: pushing to `develop` is restricted to administrators): ```console git fetch origin git checkout develop @@ -79,6 +79,9 @@ the changelog. git merge origin/master git push origin HEAD # Only administrators can push to the protected branch. ``` + **At this point, `master` must be merged into `develop`.** Otherwise, the + GitHub-generated release note from `develop` for the next release will not be + generated correctly. * Publish any Security Advisories that were resolved from private forks (note: publishing is restricted to administrators) * Announce the release on the forums and Slack channel From 48aa4759f3e488853fa1c5a9032ca3b4bb14cd9e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Sat, 12 Aug 2023 07:45:58 +0330 Subject: [PATCH 065/401] lang: [ja] add string magicLinkDisabled --- src/Language/ja/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index f8722ab12..10af048fe 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -51,7 +51,7 @@ 'magicLinkExpired' => '申し訳ございません、リンクは切れています。', // 'Sorry, link has expired.' 'checkYourEmail' => 'メールをチェックしてください!', // 'Check your email!' 'magicLinkDetails' => 'ログインリンクが含まれたメールを送信しました。これは {0} 分間だけ有効です。', // 'We just sent you an email with a Login link inside. It is only valid for {0} minutes.' - 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', + 'magicLinkDisabled' => 'マジックリンクは使えません。', // 'Use of MagicLink is currently not allowed.' 'successLogout' => '正常にログアウトしました。', // 'You have successfully logged out.' 'backToLogin' => 'ログインに戻る', // 'Back to Login' From eb5375869226a7f55c73f813847338f04d52672b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Sat, 12 Aug 2023 08:21:20 +0330 Subject: [PATCH 066/401] revert: back to old ver --- src/Controllers/MagicLinkController.php | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index a6c16558e..fb036e640 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -50,7 +50,9 @@ public function __construct() */ public function loginView() { - $this->displayErrorMagicLinkDisabled(); + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } if (auth()->loggedIn()) { return redirect()->to(config('Auth')->loginRedirect()); @@ -68,7 +70,9 @@ public function loginView() */ public function loginAction() { - $this->displayErrorMagicLinkDisabled(); + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } // Validate email format $rules = $this->getValidationRules(); @@ -139,7 +143,9 @@ protected function displayMessage(): string */ public function verify(): RedirectResponse { - $this->displayErrorMagicLinkDisabled(); + if (! setting('Auth.allowMagicLinkLogins')) { + return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); + } $token = $this->request->getGet('token'); @@ -219,18 +225,6 @@ private function recordLoginAttempt( ); } - /** - * Display error to the user if magic-link is not allowed. - * - * @return RedirectResponse|void - */ - private function displayErrorMagicLinkDisabled() - { - if (! setting('Auth.allowMagicLinkLogins')) { - return redirect()->route('login')->with('error', lang('Auth.magicLinkDisabled')); - } - } - /** * Returns the rules that should be used for validation. * From abcddea7add5171a879aa125cffdd388ee49a42b Mon Sep 17 00:00:00 2001 From: Jozef Rebjak Date: Sat, 12 Aug 2023 22:42:18 +0200 Subject: [PATCH 067/401] refactor: translate missing slovak sentences --- src/Language/sk/Auth.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index bc0de4962..39e286892 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -21,9 +21,9 @@ 'throttled' => 'Z tejto adresy IP bolo odoslaných príliš veľa žiadostí. Môžete to skúsiť znova o {0} sekúnd.', 'notEnoughPrivilege' => 'Nemáte potrebné povolenie na vykonanie požadovanej operácie.', // JWT Exceptions - 'invalidJWT' => '(To be translated) The token is invalid.', - 'expiredJWT' => '(To be translated) The token has expired.', - 'beforeValidJWT' => '(To be translated) The token is not yet available.', + 'invalidJWT' => 'Neplatný token.', + 'expiredJWT' => 'Platnosť tokenu vypršala.', + 'beforeValidJWT' => 'Token ešte nie je dostupný.', 'email' => 'Emailová adresa', 'username' => 'Používateľské meno', @@ -51,7 +51,7 @@ 'magicLinkExpired' => 'Ľutujeme, platnosť odkazu vypršala.', 'checkYourEmail' => 'Skontrolujte e-mail', 'magicLinkDetails' => 'Práve sme vám poslali e-mail s odkazom na prihlásenie. Platí iba {0} minút.', - 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', + 'magicLinkDisabled' => 'Použitie magického linku momentálne nie je povolené.', 'successLogout' => 'Úspešne ste sa odhlásili.', 'backToLogin' => 'Späť na prihlásenie', From 7e43fd63a8b23d4afdb3a64c2d09eac9ae211e2c Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Tue, 15 Aug 2023 17:35:44 +0300 Subject: [PATCH 068/401] lang: [ar] translating missed --- src/Language/ar/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 2eaad4faa..17fb43a68 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -51,7 +51,7 @@ 'magicLinkExpired' => 'عذرا ، لقد انتهت صلاحية الرابط.', 'checkYourEmail' => 'تحقق من بريدك الالكتروني!', 'magicLinkDetails' => 'لقد أرسلنا لك بريدًا إلكترونيًا يحتوي على رابط تسجيل الدخول بالداخل. الرابط صالح فقط لمدة {0} دقيقة.', - 'magicLinkDisabled' => '(To be translated) Use of MagicLink is currently not allowed.', + 'magicLinkDisabled' => 'استخدام الرابط للدخول MagicLink غير مسموح به حاليًا.', 'successLogout' => 'لقد قمت بتسجيل الخروج بنجاح.', 'backToLogin' => 'العودة إلى نموذج تسجيل الدخول', From c0bf12ec7ec2b19c035b7017a5f07b2313dfd817 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Tue, 15 Aug 2023 18:36:13 +0330 Subject: [PATCH 069/401] chore: run `cs-fix` by latest version --- tests/Unit/FilterInCliTest.php | 5 ++--- tests/Unit/NothingPersonalValidatorTest.php | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/Unit/FilterInCliTest.php b/tests/Unit/FilterInCliTest.php index dd96fc09a..fa9a7c77d 100644 --- a/tests/Unit/FilterInCliTest.php +++ b/tests/Unit/FilterInCliTest.php @@ -10,7 +10,6 @@ use CodeIgniter\Shield\Filters\ChainAuth; use CodeIgniter\Shield\Filters\SessionAuth; use CodeIgniter\Shield\Filters\TokenAuth; -use Generator; use Tests\Support\TestCase; /** @@ -19,7 +18,7 @@ final class FilterInCliTest extends TestCase { /** - * @dataProvider filterProvider + * @dataProvider provideWhenInCliDoNothing */ public function testWhenInCliDoNothing(FilterInterface $filter): void { @@ -31,7 +30,7 @@ public function testWhenInCliDoNothing(FilterInterface $filter): void $filter->before($clirequest); } - public static function filterProvider(): Generator + public static function provideWhenInCliDoNothing(): iterable { yield from [ [new AuthRates()], diff --git a/tests/Unit/NothingPersonalValidatorTest.php b/tests/Unit/NothingPersonalValidatorTest.php index a0d3848d5..b3e7d8e77 100644 --- a/tests/Unit/NothingPersonalValidatorTest.php +++ b/tests/Unit/NothingPersonalValidatorTest.php @@ -158,7 +158,7 @@ public function testFalseForSensibleMatch(): void * * $config->maxSimilarity = 50; is the highest setting where all tests pass. * - * @dataProvider passwordProvider + * @dataProvider provideIsNotPersonalFalsePositivesCaughtByIsNotSimilar * * @param mixed $password */ @@ -180,7 +180,7 @@ public function testIsNotPersonalFalsePositivesCaughtByIsNotSimilar($password): $this->assertNotSame($isNotPersonal, $isNotSimilar); } - public static function passwordProvider(): array + public static function provideIsNotPersonalFalsePositivesCaughtByIsNotSimilar(): iterable { return [ ['JoeTheCaptain'], @@ -195,7 +195,7 @@ public static function passwordProvider(): array } /** - * @dataProvider firstLastNameProvider + * @dataProvider provideConfigPersonalFieldsValues * * @param mixed $firstName * @param mixed $lastName @@ -225,7 +225,7 @@ public function testConfigPersonalFieldsValues($firstName, $lastName, $expected) $this->assertSame($expected, $result->isOK()); } - public static function firstLastNameProvider() + public static function provideConfigPersonalFieldsValues(): iterable { return [ [ @@ -247,7 +247,7 @@ public static function firstLastNameProvider() } /** - * @dataProvider maxSimilarityProvider + * @dataProvider provideMaxSimilarityZeroTurnsOffSimilarityCalculation * * The calculated similarity of 'captnjoe' and 'CaptainJoe' is 88.89. * With $config->maxSimilarity = 66; the password should be rejected, @@ -275,7 +275,7 @@ public function testMaxSimilarityZeroTurnsOffSimilarityCalculation($maxSimilarit $this->assertSame($expected, $result->isOK()); } - public static function maxSimilarityProvider() + public static function provideMaxSimilarityZeroTurnsOffSimilarityCalculation(): iterable { return [ [ @@ -289,7 +289,7 @@ public static function maxSimilarityProvider() } /** - * @dataProvider badEmailsProvider + * @dataProvider provideCheckPasswordWithBadEmail */ public function testCheckPasswordWithBadEmail(string $email, bool $expected): void { @@ -308,7 +308,7 @@ public function testCheckPasswordWithBadEmail(string $email, bool $expected): vo $this->assertSame($expected, $result->isOK()); } - public static function badEmailsProvider() + public static function provideCheckPasswordWithBadEmail(): iterable { return [ [ From 2408adfe4a82f74612b8cbefada01280c082338c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 15 Aug 2023 19:07:37 +0330 Subject: [PATCH 070/401] chore: add status badge for Rector/CS/Psalm I would like the important QC tools used to be listed. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 73b4f360c..72ccee147 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ [![Unit Tests](https://github.com/codeigniter4/shield/workflows/PHPUnit/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpunit.yml) [![Static Analysis](https://github.com/codeigniter4/shield/workflows/PHPStan/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpstan.yml) +[![PHPCSFixer](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml) +[![Rector](https://github.com/codeigniter4/shield/actions/workflows/rector.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/rector.yml) +[![Psalm](https://github.com/codeigniter4/shield/actions/workflows/psalm.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/psalm.yml) [![Architecture](https://github.com/codeigniter4/shield/workflows/Deptrac/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/deptrac.yml) [![Coverage Status](https://coveralls.io/repos/github/codeigniter4/shield/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/shield?branch=develop) From abc03686e8cc5a760227729dcf0518a313a560fe Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 15 Aug 2023 19:21:08 +0330 Subject: [PATCH 071/401] title readability for linter Co-authored-by: John Paul E. Balandan, CPA --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72ccee147..2ea308457 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Unit Tests](https://github.com/codeigniter4/shield/workflows/PHPUnit/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpunit.yml) [![Static Analysis](https://github.com/codeigniter4/shield/workflows/PHPStan/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpstan.yml) -[![PHPCSFixer](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml) +[![PHP-CS-Fixer](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml) [![Rector](https://github.com/codeigniter4/shield/actions/workflows/rector.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/rector.yml) [![Psalm](https://github.com/codeigniter4/shield/actions/workflows/psalm.yml/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/psalm.yml) [![Architecture](https://github.com/codeigniter4/shield/workflows/Deptrac/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/deptrac.yml) From f962bcdf0f9d2e535654016036859884a5662902 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Wed, 16 Aug 2023 20:50:24 +0100 Subject: [PATCH 072/401] Add support for multiple permissions check for users. --- docs/authorization.md | 6 +- src/Authorization/Traits/Authorizable.php | 67 ++++++++++++----------- tests/Authorization/AuthorizableTest.php | 7 +++ 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/docs/authorization.md b/docs/authorization.md index ee3aa104c..8264748fd 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -111,7 +111,7 @@ The `Authorizable` trait on the `User` entity provides the following methods to #### can() -Allows you to check if a user is permitted to do a specific action. The only argument is the permission string. Returns +Allows you to check if a user is permitted to do a specific action or group or actions. The permission string(s) should be passed as the argument(s). Returns boolean `true`/`false`. Will check the user's direct permissions (**user-level permissions**) first, and then check against all of the user's groups permissions (**group-level permissions**) to determine if they are allowed. @@ -119,6 +119,10 @@ permissions (**group-level permissions**) to determine if they are allowed. if ($user->can('users.create')) { // } +OR +if ($user->can('users.create', 'users.edit')) { + // +} ``` #### inGroup() diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index 9e3f32ff1..bb6f09a97 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -228,47 +228,50 @@ public function hasPermission(string $permission): bool * Checks user permissions and their group permissions * to see if the user has a specific permission. * - * @param string $permission string consisting of a scope and action, like `users.create` + * @param string $permission string(s) consisting of a scope and action, like `users.create` */ - public function can(string $permission): bool + public function can(string ...$permissions): bool { - if (strpos($permission, '.') === false) { - throw new LogicException( - 'A permission must be a string consisting of a scope and action, like `users.create`.' - . ' Invalid permission: ' . $permission - ); - } - - $this->populatePermissions(); - - $permission = strtolower($permission); + foreach ($permissions as $permission) { + // Permission must contain a scope and action + if (strpos($permission, '.') === false) { + throw new LogicException( + 'A permission must be a string consisting of a scope and action, like `users.create`.' + . ' Invalid permission: ' . $permission + ); + } - // Check user's permissions - if (in_array($permission, $this->permissionsCache, true)) { - return true; - } + $this->populatePermissions(); - // Check the groups the user belongs to - $this->populateGroups(); + $permission = strtolower($permission); - if (! count($this->groupCache)) { - return false; - } + // Check user's permissions + if (in_array($permission, $this->permissionsCache, true)) { + return true; + } - $matrix = function_exists('setting') - ? setting('AuthGroups.matrix') - : config('AuthGroups')->matrix; + // Check the groups the user belongs to + $this->populateGroups(); - foreach ($this->groupCache as $group) { - // Check exact match - if (isset($matrix[$group]) && in_array($permission, $matrix[$group], true)) { - return true; + if (! count($this->groupCache)) { + return false; } - // Check wildcard match - $check = substr($permission, 0, strpos($permission, '.')) . '.*'; - if (isset($matrix[$group]) && in_array($check, $matrix[$group], true)) { - return true; + $matrix = function_exists('setting') + ? setting('AuthGroups.matrix') + : config('AuthGroups')->matrix; + + foreach ($this->groupCache as $group) { + // Check exact match + if (isset($matrix[$group]) && in_array($permission, $matrix[$group], true)) { + return true; + } + + // Check wildcard match + $check = substr($permission, 0, strpos($permission, '.')) . '.*'; + if (isset($matrix[$group]) && in_array($check, $matrix[$group], true)) { + return true; + } } } diff --git a/tests/Authorization/AuthorizableTest.php b/tests/Authorization/AuthorizableTest.php index 464cfcf1c..2ae9f50a7 100644 --- a/tests/Authorization/AuthorizableTest.php +++ b/tests/Authorization/AuthorizableTest.php @@ -288,6 +288,13 @@ public function testCanCascadesToGroupsSimple(): void $this->assertTrue($this->user->can('admin.access')); } + public function testCanCascadesToGroupsMultiple(): void + { + $this->user->addGroup('superadmin'); + + $this->assertTrue($this->user->can('admin.access', 'users.*')); + } + public function testCanCascadesToGroupsWithWildcards(): void { $this->user->addGroup('superadmin'); From d7ea8445f3cbbbe34fe19cbfcb2cceefec47ce15 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Wed, 16 Aug 2023 20:59:48 +0100 Subject: [PATCH 073/401] fixed static analysis --- src/Authorization/Traits/Authorizable.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index bb6f09a97..c922fe41f 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -226,9 +226,10 @@ public function hasPermission(string $permission): bool /** * Checks user permissions and their group permissions - * to see if the user has a specific permission. + * to see if the user has a specific permission or group + * of permissions. * - * @param string $permission string(s) consisting of a scope and action, like `users.create` + * @param string $permissions string(s) consisting of a scope and action, like `users.create` */ public function can(string ...$permissions): bool { From 35828ffc3eef12c518d08f5bec5fb339d7279338 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Fri, 18 Aug 2023 00:07:56 +0100 Subject: [PATCH 074/401] Added tests for user's direct permissions and user's group-level permissions --- tests/Authorization/AuthorizableTest.php | 28 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/Authorization/AuthorizableTest.php b/tests/Authorization/AuthorizableTest.php index 2ae9f50a7..fac80ec94 100644 --- a/tests/Authorization/AuthorizableTest.php +++ b/tests/Authorization/AuthorizableTest.php @@ -288,13 +288,6 @@ public function testCanCascadesToGroupsSimple(): void $this->assertTrue($this->user->can('admin.access')); } - public function testCanCascadesToGroupsMultiple(): void - { - $this->user->addGroup('superadmin'); - - $this->assertTrue($this->user->can('admin.access', 'users.*')); - } - public function testCanCascadesToGroupsWithWildcards(): void { $this->user->addGroup('superadmin'); @@ -312,6 +305,27 @@ public function testCanGetsInvalidPermission(): void $this->assertTrue($this->user->can('developer')); } + /** + * @see https://github.com/codeigniter4/shield/pull/791#discussion_r1297712860 + */ + public function testCanWorksWithMultiplePermissions(): void + { + // Check for user's direct permissions (user-level permissions) + $this->user->addPermission('users.create', 'users.edit'); + + $this->assertTrue($this->user->can('users.create', 'users.edit')); + $this->assertFalse($this->user->can('beta.access', 'admin.access')); + + $this->user->removePermission('users.create', 'users.edit'); + + $this->assertFalse($this->user->can('users.edit', 'users.create')); + + // Check for user's group permissions (group-level permissions) + $this->user->addGroup('superadmin'); + + $this->assertTrue($this->user->can('admin.access', 'beta.access')); + } + /** * @see https://github.com/codeigniter4/shield/pull/238 */ From eb50f850350c2a4a2900c1258313c2b2d6bdc236 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Fri, 18 Aug 2023 00:10:04 +0100 Subject: [PATCH 075/401] Updated docs --- docs/authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authorization.md b/docs/authorization.md index 8264748fd..06a2cbc9d 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -119,7 +119,7 @@ permissions (**group-level permissions**) to determine if they are allowed. if ($user->can('users.create')) { // } -OR +// Or if ($user->can('users.create', 'users.edit')) { // } From acbeb07ecbdd792a8afd90e8b89afae3e18c5fb8 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Fri, 18 Aug 2023 07:32:36 +0100 Subject: [PATCH 076/401] Applied code suggestions --- src/Authorization/Traits/Authorizable.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index c922fe41f..6cee326ce 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -233,6 +233,12 @@ public function hasPermission(string $permission): bool */ public function can(string ...$permissions): bool { + // Get user's permissions and store in cache + $this->populatePermissions(); + + // Check the groups the user belongs to + $this->populateGroups(); + foreach ($permissions as $permission) { // Permission must contain a scope and action if (strpos($permission, '.') === false) { @@ -242,8 +248,6 @@ public function can(string ...$permissions): bool ); } - $this->populatePermissions(); - $permission = strtolower($permission); // Check user's permissions @@ -251,9 +255,6 @@ public function can(string ...$permissions): bool return true; } - // Check the groups the user belongs to - $this->populateGroups(); - if (! count($this->groupCache)) { return false; } From e922392cb1c66bbd6264420ee062013045cd63b5 Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Sun, 20 Aug 2023 21:35:06 +0300 Subject: [PATCH 077/401] feat: redirect after login to entrance url --- src/Config/Auth.php | 3 ++- src/Filters/SessionAuth.php | 5 +++++ tests/Authentication/Filters/SessionFilterTest.php | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 1c7d57902..792a8ba83 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -409,7 +409,8 @@ class Auth extends BaseConfig */ public function loginRedirect(): string { - $url = setting('Auth.redirects')['login']; + $session = session(); + $url = $session->getTempdata('beforeLoginUrl') ?? setting('Auth.redirects')['login']; return $this->getUrl($url); } diff --git a/src/Filters/SessionAuth.php b/src/Filters/SessionAuth.php index d95f89c07..6a8a0d8fe 100644 --- a/src/Filters/SessionAuth.php +++ b/src/Filters/SessionAuth.php @@ -75,6 +75,11 @@ public function before(RequestInterface $request, $arguments = null) ->with('error', $authenticator->getPendingMessage()); } + if (! url_is('login')) { + $session = session(); + $session->setTempdata('beforeLoginUrl', current_url(), 300); + } + return redirect()->route('login'); } diff --git a/tests/Authentication/Filters/SessionFilterTest.php b/tests/Authentication/Filters/SessionFilterTest.php index 759a88535..31b2b007c 100644 --- a/tests/Authentication/Filters/SessionFilterTest.php +++ b/tests/Authentication/Filters/SessionFilterTest.php @@ -83,4 +83,12 @@ public function testBlocksInactiveUsers(): void setting('Auth.actions', ['register' => null]); } + + public function testStoreRedirectsToEntraceUrlIntoSession(): void + { + $result = $this->call('get', 'protected-route'); + $result->assertRedirectTo('/login'); + $this->assertNotEmpty($_SESSION['beforeLoginUrl']); + $this->assertSame(site_url('protected-route'), $_SESSION['beforeLoginUrl']); + } } From 292cb3cedbbd6b14c282170a91f7b03b1c8cf654 Mon Sep 17 00:00:00 2001 From: Mohammed AlShannaq Date: Mon, 21 Aug 2023 04:09:42 +0300 Subject: [PATCH 078/401] Update tests/Authentication/Filters/SessionFilterTest.php Co-authored-by: kenjis --- tests/Authentication/Filters/SessionFilterTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/Authentication/Filters/SessionFilterTest.php b/tests/Authentication/Filters/SessionFilterTest.php index 31b2b007c..be66fd2d5 100644 --- a/tests/Authentication/Filters/SessionFilterTest.php +++ b/tests/Authentication/Filters/SessionFilterTest.php @@ -87,8 +87,11 @@ public function testBlocksInactiveUsers(): void public function testStoreRedirectsToEntraceUrlIntoSession(): void { $result = $this->call('get', 'protected-route'); + $result->assertRedirectTo('/login'); - $this->assertNotEmpty($_SESSION['beforeLoginUrl']); - $this->assertSame(site_url('protected-route'), $_SESSION['beforeLoginUrl']); + + $session = session(); + $this->assertNotEmpty($session->get('beforeLogginUrl')); + $this->assertSame(site_url('protected-route'), $session->get('beforeLogginUrl')); } } From 098ac9bfedceee0c043d0dd095d742a0723ded6d Mon Sep 17 00:00:00 2001 From: irbidnet internal lab Date: Mon, 21 Aug 2023 04:52:42 +0300 Subject: [PATCH 079/401] fix session name beforeLoginUrl --- tests/Authentication/Filters/SessionFilterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Authentication/Filters/SessionFilterTest.php b/tests/Authentication/Filters/SessionFilterTest.php index be66fd2d5..1b379ccc3 100644 --- a/tests/Authentication/Filters/SessionFilterTest.php +++ b/tests/Authentication/Filters/SessionFilterTest.php @@ -91,7 +91,7 @@ public function testStoreRedirectsToEntraceUrlIntoSession(): void $result->assertRedirectTo('/login'); $session = session(); - $this->assertNotEmpty($session->get('beforeLogginUrl')); - $this->assertSame(site_url('protected-route'), $session->get('beforeLogginUrl')); + $this->assertNotEmpty($session->get('beforeLoginUrl')); + $this->assertSame(site_url('protected-route'), $session->get('beforeLoginUrl')); } } From c39b70d566ebafea3bb05623f5e9ee9bb16868ff Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Mon, 21 Aug 2023 08:40:32 +0100 Subject: [PATCH 080/401] Added test for wildcard permission matrix --- tests/Authorization/AuthorizableTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Authorization/AuthorizableTest.php b/tests/Authorization/AuthorizableTest.php index fac80ec94..1dc098abf 100644 --- a/tests/Authorization/AuthorizableTest.php +++ b/tests/Authorization/AuthorizableTest.php @@ -324,6 +324,7 @@ public function testCanWorksWithMultiplePermissions(): void $this->user->addGroup('superadmin'); $this->assertTrue($this->user->can('admin.access', 'beta.access')); + $this->assertTrue($this->user->can('admin.*', 'users.*')); } /** From 6d9e5be14c51305c438ce8ac9f300e860ce3a7dd Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Mon, 21 Aug 2023 04:40:33 +0100 Subject: [PATCH 081/401] Update docs/authorization.md Co-authored-by: kenjis --- docs/authorization.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/authorization.md b/docs/authorization.md index 06a2cbc9d..7265ce6c2 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -119,7 +119,8 @@ permissions (**group-level permissions**) to determine if they are allowed. if ($user->can('users.create')) { // } -// Or + +// If multiple permissions are specified, true is returned if the user has any of them. if ($user->can('users.create', 'users.edit')) { // } From 23286e1726a4b13d88fe8fbdc1b3d8c6a5e06b7b Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 24 Aug 2023 16:52:00 +0900 Subject: [PATCH 082/401] fix: use short classname for config() If FQCN is specified, devs cannot override. --- src/Authentication/Authenticators/JWT.php | 8 +++++--- src/Authentication/JWT/Adapters/FirebaseAdapter.php | 6 ++++-- src/Authentication/JWT/JWSEncoder.php | 3 ++- src/Filters/JWTAuth.php | 3 ++- src/Models/BaseModel.php | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Authentication/Authenticators/JWT.php b/src/Authentication/Authenticators/JWT.php index c223bdbc7..09590275c 100644 --- a/src/Authentication/Authenticators/JWT.php +++ b/src/Authentication/Authenticators/JWT.php @@ -61,7 +61,8 @@ public function __construct(UserModel $provider) */ public function attempt(array $credentials): Result { - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); /** @var IncomingRequest $request */ $request = service('request'); @@ -142,7 +143,7 @@ public function check(array $credentials): Result 'success' => false, 'reason' => lang( 'Auth.noToken', - [config(AuthJWT::class)->authenticatorHeader] + [config('AuthJWT')->authenticatorHeader] ), ]); } @@ -196,7 +197,8 @@ public function loggedIn(): bool /** @var IncomingRequest $request */ $request = service('request'); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); return $this->attempt([ 'token' => $request->getHeaderLine($config->authenticatorHeader), diff --git a/src/Authentication/JWT/Adapters/FirebaseAdapter.php b/src/Authentication/JWT/Adapters/FirebaseAdapter.php index 3a246ed3f..70141393d 100644 --- a/src/Authentication/JWT/Adapters/FirebaseAdapter.php +++ b/src/Authentication/JWT/Adapters/FirebaseAdapter.php @@ -79,7 +79,8 @@ public function decode(string $encodedToken, $keyset): stdClass */ private function createKeysForDecode($keyset) { - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $configKeys = $config->keys[$keyset]; @@ -127,7 +128,8 @@ public function encode(array $payload, $keyset, ?array $headers = null): string */ private function createKeysForEncode($keyset): array { - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); if (isset($config->keys[$keyset][0]['secret'])) { $key = $config->keys[$keyset][0]['secret']; diff --git a/src/Authentication/JWT/JWSEncoder.php b/src/Authentication/JWT/JWSEncoder.php index 327d5ea03..e426e7eda 100644 --- a/src/Authentication/JWT/JWSEncoder.php +++ b/src/Authentication/JWT/JWSEncoder.php @@ -39,7 +39,8 @@ public function encode( 'Cannot pass $claims[\'exp\'] and $ttl at the same time.' ); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $payload = array_merge( $config->defaultClaims, diff --git a/src/Filters/JWTAuth.php b/src/Filters/JWTAuth.php index a0a6c2a7d..84914511b 100644 --- a/src/Filters/JWTAuth.php +++ b/src/Filters/JWTAuth.php @@ -59,7 +59,8 @@ private function getTokenFromHeader(RequestInterface $request): string { assert($request instanceof IncomingRequest); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $tokenHeader = $request->getHeaderLine( $config->authenticatorHeader ?? 'Authorization' diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index c266a6070..e0db1dec1 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -20,7 +20,7 @@ abstract class BaseModel extends Model public function __construct() { - $this->authConfig = config(Auth::class); + $this->authConfig = config('Auth'); if ($this->authConfig->DBGroup !== null) { $this->DBGroup = $this->authConfig->DBGroup; From 53e46d29ed2d3bf1765aeb812a6356657ea252d6 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:23:08 +0100 Subject: [PATCH 083/401] Store beforeLoginUrl to session before redirecting to login page. --- src/Filters/AbstractAuthFilter.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Filters/AbstractAuthFilter.php b/src/Filters/AbstractAuthFilter.php index 25d6ff416..750f5993a 100644 --- a/src/Filters/AbstractAuthFilter.php +++ b/src/Filters/AbstractAuthFilter.php @@ -30,6 +30,12 @@ public function before(RequestInterface $request, $arguments = null) } if (! auth()->loggedIn()) { + // Set the entrance url to redirect a user after successful login + if (! url_is('login')) { + $session = session(); + $session->setTempdata('beforeLoginUrl', current_url(), 300); + } + return redirect()->route('login'); } From 22edd9111d1610dc79466ed605b43c83f0ba48aa Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:24:14 +0100 Subject: [PATCH 084/401] Added tests --- tests/Authentication/Filters/GroupFilterTest.php | 11 +++++++++++ tests/Authentication/Filters/PermissionFilterTest.php | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/Authentication/Filters/GroupFilterTest.php b/tests/Authentication/Filters/GroupFilterTest.php index 2e27e03ae..e1e549508 100644 --- a/tests/Authentication/Filters/GroupFilterTest.php +++ b/tests/Authentication/Filters/GroupFilterTest.php @@ -31,6 +31,17 @@ public function testFilterNotAuthorized(): void $result->assertSee('Open'); } + public function testFilterNotAuthorizedStoresRedirectToEntranceUrlIntoSession(): void + { + $result = $this->call('get', 'protected-route'); + + $result->assertRedirectTo('/login'); + + $session = session(); + $this->assertNotEmpty($session->get('beforeLoginUrl')); + $this->assertSame(site_url('protected-route'), $session->get('beforeLoginUrl')); + } + public function testFilterSuccess(): void { /** @var User $user */ diff --git a/tests/Authentication/Filters/PermissionFilterTest.php b/tests/Authentication/Filters/PermissionFilterTest.php index 77aabdf4c..77764ec6e 100644 --- a/tests/Authentication/Filters/PermissionFilterTest.php +++ b/tests/Authentication/Filters/PermissionFilterTest.php @@ -31,6 +31,17 @@ public function testFilterNotAuthorized(): void $result->assertSee('Open'); } + public function testFilterNotAuthorizedStoresRedirectToEntranceUrlIntoSession(): void + { + $result = $this->call('get', 'protected-route'); + + $result->assertRedirectTo('/login'); + + $session = session(); + $this->assertNotEmpty($session->get('beforeLoginUrl')); + $this->assertSame(site_url('protected-route'), $session->get('beforeLoginUrl')); + } + public function testFilterSuccess(): void { /** @var User $user */ From 6c747ddb9a04c35bcefdc2f39ea22d16c7a788d9 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:42:43 +0100 Subject: [PATCH 085/401] Fixed PHPCPD --- tests/Authentication/Filters/GroupFilterTest.php | 5 ++--- tests/Authentication/Filters/PermissionFilterTest.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/Authentication/Filters/GroupFilterTest.php b/tests/Authentication/Filters/GroupFilterTest.php index e1e549508..2690eeaff 100644 --- a/tests/Authentication/Filters/GroupFilterTest.php +++ b/tests/Authentication/Filters/GroupFilterTest.php @@ -37,9 +37,8 @@ public function testFilterNotAuthorizedStoresRedirectToEntranceUrlIntoSession(): $result->assertRedirectTo('/login'); - $session = session(); - $this->assertNotEmpty($session->get('beforeLoginUrl')); - $this->assertSame(site_url('protected-route'), $session->get('beforeLoginUrl')); + $this->assertNotEmpty(session()->getTempdata('beforeLoginUrl')); + $this->assertSame(site_url('protected-route'), session()->getTempdata('beforeLoginUrl')); } public function testFilterSuccess(): void diff --git a/tests/Authentication/Filters/PermissionFilterTest.php b/tests/Authentication/Filters/PermissionFilterTest.php index 77764ec6e..140bde35b 100644 --- a/tests/Authentication/Filters/PermissionFilterTest.php +++ b/tests/Authentication/Filters/PermissionFilterTest.php @@ -37,9 +37,8 @@ public function testFilterNotAuthorizedStoresRedirectToEntranceUrlIntoSession(): $result->assertRedirectTo('/login'); - $session = session(); - $this->assertNotEmpty($session->get('beforeLoginUrl')); - $this->assertSame(site_url('protected-route'), $session->get('beforeLoginUrl')); + $this->assertNotEmpty(session()->getTempdata('beforeLoginUrl')); + $this->assertSame(site_url('protected-route'), session()->getTempdata('beforeLoginUrl')); } public function testFilterSuccess(): void From 6fd86e00e96a439f0d9dda0789d085435a315c45 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 26 Aug 2023 10:02:25 +0100 Subject: [PATCH 086/401] Improved user experience in forms --- src/Views/email_activate_show.php | 5 +++-- src/Views/login.php | 10 ++++++---- src/Views/magic_link_form.php | 5 +++-- src/Views/register.php | 20 ++++++++++++-------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index 491fc2fbf..d14e2fbb2 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -19,9 +19,10 @@ -
- + +
diff --git a/src/Views/login.php b/src/Views/login.php index 34193ce19..71b4501d0 100644 --- a/src/Views/login.php +++ b/src/Views/login.php @@ -32,13 +32,15 @@ -
- +
+ +
-
- +
+ +
diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index 100ce67f6..ed820717e 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -28,9 +28,10 @@ -
- + +
diff --git a/src/Views/register.php b/src/Views/register.php index 4c8fb301e..7adba1aa3 100644 --- a/src/Views/register.php +++ b/src/Views/register.php @@ -28,23 +28,27 @@ -
- +
+ +
-
- +
+ +
-
- +
+ +
-
- +
+ +
From a376dccf341c555c7b83de42fcfb65f3e4c18bf9 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 26 Aug 2023 11:14:40 +0100 Subject: [PATCH 087/401] Added translations --- src/Language/ar/Auth.php | 1 + src/Language/bg/Auth.php | 1 + src/Language/de/Auth.php | 1 + src/Language/en/Auth.php | 1 + src/Language/es/Auth.php | 1 + src/Language/fa/Auth.php | 1 + src/Language/fr/Auth.php | 1 + src/Language/id/Auth.php | 1 + src/Language/it/Auth.php | 1 + src/Language/ja/Auth.php | 1 + src/Language/lt/Auth.php | 1 + src/Language/pt-BR/Auth.php | 1 + src/Language/pt/Auth.php | 1 + src/Language/sk/Auth.php | 1 + src/Language/sr/Auth.php | 1 + src/Language/sv-SE/Auth.php | 1 + src/Language/tr/Auth.php | 1 + src/Language/uk/Auth.php | 1 + src/Views/email_activate_show.php | 2 +- 19 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 17fb43a68..5184ff2b2 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -30,6 +30,7 @@ 'password' => 'كلمة المرور', 'passwordConfirm' => 'كلمة المرور (مرة اخرى)', 'haveAccount' => 'هل لديك حساب بالفعل؟', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'تاكيد', diff --git a/src/Language/bg/Auth.php b/src/Language/bg/Auth.php index f83fda6e9..b4bee29dd 100644 --- a/src/Language/bg/Auth.php +++ b/src/Language/bg/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Парола', 'passwordConfirm' => 'Парола (отново)', 'haveAccount' => 'Вече имате акаунт?', + 'token' => '(To be translated) Token', // Бутони 'confirm' => 'Потвърди', diff --git a/src/Language/de/Auth.php b/src/Language/de/Auth.php index 977feb0a6..53b67542a 100644 --- a/src/Language/de/Auth.php +++ b/src/Language/de/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Passwort', 'passwordConfirm' => 'Passwort (erneut)', 'haveAccount' => 'Haben Sie bereits ein Konto?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Bestätigen', diff --git a/src/Language/en/Auth.php b/src/Language/en/Auth.php index d9271cc78..1dfdfe7b1 100644 --- a/src/Language/en/Auth.php +++ b/src/Language/en/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Password', 'passwordConfirm' => 'Password (again)', 'haveAccount' => 'Already have an account?', + 'token' => 'Token', // Buttons 'confirm' => 'Confirm', diff --git a/src/Language/es/Auth.php b/src/Language/es/Auth.php index 027e7d4ec..5fdd2ae0c 100644 --- a/src/Language/es/Auth.php +++ b/src/Language/es/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Contraseña', 'passwordConfirm' => 'Contraseña (otra vez)', 'haveAccount' => '¿Ya tienes una cuenta?', + 'token' => '(To be translated) Token', // Botones 'confirm' => 'Confirmar', diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index 4cedff90d..a1dec430a 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -30,6 +30,7 @@ 'password' => 'رمز عبور', 'passwordConfirm' => 'رمز عبور (تکرار)', 'haveAccount' => 'از قبل حساب کاربری دارید؟', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'تایید', diff --git a/src/Language/fr/Auth.php b/src/Language/fr/Auth.php index 9bf794ea3..fb167b954 100644 --- a/src/Language/fr/Auth.php +++ b/src/Language/fr/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Mot de passe', 'passwordConfirm' => 'Mot de passe (répéter)', 'haveAccount' => 'Vous avez déjà un compte ?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Confirmer', diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index 62a90d8e6..0c5e1533c 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Kata Sandi', 'passwordConfirm' => 'Kata Sandi (lagi)', 'haveAccount' => 'Sudah punya akun?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Konfirmasi', diff --git a/src/Language/it/Auth.php b/src/Language/it/Auth.php index dc531886a..0b4e845ac 100644 --- a/src/Language/it/Auth.php +++ b/src/Language/it/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Password', 'passwordConfirm' => 'Password (ancora)', 'haveAccount' => 'Hai già un account?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Conferma', diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index 10af048fe..c2395258e 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -30,6 +30,7 @@ 'password' => 'パスワード', // 'Password' 'passwordConfirm' => 'パスワード(再)', // 'Password (again)' 'haveAccount' => 'すでにアカウントをお持ちの方', // 'Already have an account?' + 'token' => '(To be translated) Token', // Buttons 'confirm' => '確認する', // 'Confirm' diff --git a/src/Language/lt/Auth.php b/src/Language/lt/Auth.php index 0b10631c0..035d80070 100644 --- a/src/Language/lt/Auth.php +++ b/src/Language/lt/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Slaptažodis', 'passwordConfirm' => 'Slaptažodis (pakartoti)', 'haveAccount' => 'Jau turite paskyrą?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Patvirtinti', diff --git a/src/Language/pt-BR/Auth.php b/src/Language/pt-BR/Auth.php index b001a9511..e4b479b5b 100644 --- a/src/Language/pt-BR/Auth.php +++ b/src/Language/pt-BR/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Senha', 'passwordConfirm' => 'Senha (novamente)', 'haveAccount' => 'Já tem uma conta?', + 'token' => '(To be translated) Token', // Botões 'confirm' => 'Confirmar', diff --git a/src/Language/pt/Auth.php b/src/Language/pt/Auth.php index ee7a5ab90..765db2070 100644 --- a/src/Language/pt/Auth.php +++ b/src/Language/pt/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Senha', 'passwordConfirm' => 'Senha (novamente)', 'haveAccount' => 'Já tem uma conta?', + 'token' => '(To be translated) Token', // Botões 'confirm' => 'Confirmar', diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index 39e286892..11579e7ba 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Heslo', 'passwordConfirm' => 'Heslo (znova)', 'haveAccount' => 'Máte už účet?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Potvrdiť', diff --git a/src/Language/sr/Auth.php b/src/Language/sr/Auth.php index 8030c6a4d..9211ba0d4 100644 --- a/src/Language/sr/Auth.php +++ b/src/Language/sr/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Lozinka', 'passwordConfirm' => 'Lozinka (ponovo)', 'haveAccount' => 'Već imate nalog?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Potvrdi', diff --git a/src/Language/sv-SE/Auth.php b/src/Language/sv-SE/Auth.php index 1031b7963..ab206d7d5 100644 --- a/src/Language/sv-SE/Auth.php +++ b/src/Language/sv-SE/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Lösenord', 'passwordConfirm' => 'Lösenord (igen)', 'haveAccount' => 'Har du redan ett konto?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Bekräfta', diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index b54865ba5..2b35941d7 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Şifre', 'passwordConfirm' => 'Şifre (tekrar)', 'haveAccount' => 'Zaten hesabınız var mı?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Onayla', diff --git a/src/Language/uk/Auth.php b/src/Language/uk/Auth.php index c012540ce..d54b30dd1 100644 --- a/src/Language/uk/Auth.php +++ b/src/Language/uk/Auth.php @@ -30,6 +30,7 @@ 'password' => 'Пароль', 'passwordConfirm' => 'Пароль (ще раз)', 'haveAccount' => 'Вже є обліковий запис?', + 'token' => '(To be translated) Token', // Buttons 'confirm' => 'Підтвердити', diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index d14e2fbb2..9d29ef8d8 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -22,7 +22,7 @@
- +
From 21bef49e7c009e6d590fd39b5c61b90541f161d4 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 26 Aug 2023 13:08:26 +0100 Subject: [PATCH 088/401] Update src/Language/fa/Auth.php Co-authored-by: Pooya Parsa --- src/Language/fa/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index a1dec430a..c3bf118f0 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -30,7 +30,7 @@ 'password' => 'رمز عبور', 'passwordConfirm' => 'رمز عبور (تکرار)', 'haveAccount' => 'از قبل حساب کاربری دارید؟', - 'token' => '(To be translated) Token', + 'token' => 'توکن', // Buttons 'confirm' => 'تایید', From a90e40a638f5bf243fba90ede0f7757c3330a52b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 27 Aug 2023 05:45:30 +0900 Subject: [PATCH 089/401] test: use short classname for config() We should use the same classname in the product code. --- .../Authenticators/JWTAuthenticatorTest.php | 2 +- .../Authentication/Filters/JWTFilterTest.php | 3 ++- .../JWT/Adapters/FirebaseAdapaterTest.php | 9 +++++--- .../Authentication/JWT/JWTManagerTest.php | 21 ++++++++++++------- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/tests/Authentication/Authenticators/JWTAuthenticatorTest.php b/tests/Authentication/Authenticators/JWTAuthenticatorTest.php index 5c074df12..3bd24070f 100644 --- a/tests/Authentication/Authenticators/JWTAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/JWTAuthenticatorTest.php @@ -105,7 +105,7 @@ public function testCheckNoToken(): void $this->assertFalse($result->isOK()); $this->assertSame( - \lang('Auth.noToken', [config(AuthJWT::class)->authenticatorHeader]), + \lang('Auth.noToken', [config('AuthJWT')->authenticatorHeader]), $result->reason() ); } diff --git a/tests/Authentication/Filters/JWTFilterTest.php b/tests/Authentication/Filters/JWTFilterTest.php index 082627888..e8f4d3b39 100644 --- a/tests/Authentication/Filters/JWTFilterTest.php +++ b/tests/Authentication/Filters/JWTFilterTest.php @@ -33,7 +33,8 @@ protected function setUp(): void $_SESSION = []; // Add JWT Authenticator - $config = config(Auth::class); + /** @var Auth $config */ + $config = config('Auth'); $config->authenticators['jwt'] = JWT::class; // Register our filter diff --git a/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php b/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php index 9618c151e..8385a3f00 100644 --- a/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php +++ b/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php @@ -82,7 +82,8 @@ public function testDecodeInvalidTokenExceptionUnexpectedValueException(): void $token = $this->generateJWT(); // Change algorithm and it makes the key invalid. - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0]['alg'] = 'ES256'; $adapter = new FirebaseAdapter(); @@ -110,7 +111,8 @@ public function testDecodeInvalidArgumentException(): void $token = $this->generateJWT(); // Set invalid key. - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0] = [ 'alg' => '', 'secret' => '', @@ -128,7 +130,8 @@ public function testEncodeLogicExceptionLogicException(): void $this->expectExceptionMessage('Cannot encode JWT: Algorithm not supported'); // Set unsupported algorithm. - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0]['alg'] = 'PS256'; $adapter = new FirebaseAdapter(); diff --git a/tests/Unit/Authentication/JWT/JWTManagerTest.php b/tests/Unit/Authentication/JWT/JWTManagerTest.php index 7f5adfc1a..045e70633 100644 --- a/tests/Unit/Authentication/JWT/JWTManagerTest.php +++ b/tests/Unit/Authentication/JWT/JWTManagerTest.php @@ -55,7 +55,8 @@ public function testGenerateTokenPayload(array $data): void $manager = $this->createJWTManager(); $payload = $manager->parse($token); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $expected = [ 'iss' => $config->defaultClaims['iss'], 'sub' => '1', @@ -121,7 +122,8 @@ public function testIssuePayload(array $data): void $manager = $this->createJWTManager(); $payload = $manager->parse($token); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $expected = [ 'iss' => $config->defaultClaims['iss'], 'user_id' => '1', @@ -137,7 +139,8 @@ public function testIssueSetKid(): void $manager = $this->createJWTManager(); // Set kid - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0]['kid'] = 'Key01'; $payload = [ @@ -181,7 +184,8 @@ public function testIssueWithAsymmetricKey(): void { $manager = $this->createJWTManager(); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0] = [ 'alg' => 'RS256', // algorithm. 'public' => '', // Public Key @@ -257,7 +261,8 @@ private function decodeJWT(string $token, $part): array public function testParseCanDecodeTokenSignedByOldKey(): void { - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'] = [ [ 'kid' => 'Key01', @@ -294,7 +299,8 @@ public function testParseCanDecodeTokenSignedByOldKey(): void public function testParseCanSpecifyKey(): void { - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['mobile'] = [ [ 'kid' => 'Key01', @@ -329,7 +335,8 @@ private function generateJWTWithAsymmetricKey(): string { $manager = $this->createJWTManager(); - $config = config(AuthJWT::class); + /** @var AuthJWT $config */ + $config = config('AuthJWT'); $config->keys['default'][0] = [ 'alg' => 'RS256', // algorithm. 'public' => <<<'EOD' From aabaaef8a080596c7b8cd8f05e0ae90dba7a99d5 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sun, 27 Aug 2023 08:05:32 +0100 Subject: [PATCH 090/401] Update src/Language/ja/Auth.php Co-authored-by: kenjis --- src/Language/ja/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index c2395258e..7823e88cb 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -30,7 +30,7 @@ 'password' => 'パスワード', // 'Password' 'passwordConfirm' => 'パスワード(再)', // 'Password (again)' 'haveAccount' => 'すでにアカウントをお持ちの方', // 'Already have an account?' - 'token' => '(To be translated) Token', + 'token' => 'トークン', // 'Token' // Buttons 'confirm' => '確認する', // 'Confirm' From 7945f047c60cd0074103b1cbf565302a58492507 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 29 Aug 2023 17:35:06 +0200 Subject: [PATCH 091/401] fix: UserModel::assignIdentities() always produces a DB query --- src/Entities/User.php | 5 +++++ src/Models/UserModel.php | 15 +++++++++------ tests/Unit/UserTest.php | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Entities/User.php b/src/Entities/User.php index 00a420fe7..2c966e424 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -117,6 +117,11 @@ public function getIdentities(string $type = 'all'): array return $identities; } + public function setIdentities(array $identities): void + { + $this->identities = $identities; + } + /** * Creates a new identity for this user with an email/password * combination. diff --git a/src/Models/UserModel.php b/src/Models/UserModel.php index ab5eb49c2..1bae3a03a 100644 --- a/src/Models/UserModel.php +++ b/src/Models/UserModel.php @@ -114,6 +114,7 @@ protected function fetchIdentities(array $data): array private function assignIdentities(array $data, array $identities): array { $mappedUsers = []; + $userIdentities = []; $users = $data['singleton'] ? [$data['data']] : $data['data']; @@ -122,15 +123,17 @@ private function assignIdentities(array $data, array $identities): array } unset($users); - // Now assign the identities to the user + // Now group the identities by user foreach ($identities as $identity) { - $userId = $identity->user_id; - - $newIdentities = $mappedUsers[$userId]->identities; - $newIdentities[] = $identity; + $userIdentities[$identity->user_id][] = $identity; + } + unset($identities); - $mappedUsers[$userId]->identities = $newIdentities; + // Now assign the identities to the user + foreach ($userIdentities as $userId => $identityArray) { + $mappedUsers[$userId]->identities = $identityArray; } + unset($userIdentities); return $mappedUsers; } diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index b10860ebc..fd7584416 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -87,6 +87,31 @@ public function testModelFindByIdWithIdentities(): void $this->assertCount(2, $user->identities); } + public function testModelFindAllWithIdentitiesWhereInQuery(): void + { + fake(UserIdentityModel::class, ['user_id' => $this->user->id, 'type' => 'password']); + fake(UserIdentityModel::class, ['user_id' => $this->user->id, 'type' => 'access_token']); + + // Grab the user again, using the model's identity helper + $users = model(UserModel::class)->withIdentities()->findAll(); + + $identities = 0; + + foreach ($users as $user) { + if ($user->id !== $this->user->id) { + continue; + } + + $identities = $user->identities; + + // Check the last query and see if a proper type of query was used + $query = (string) model(UserModel::class)->getLastQuery(); + $this->assertMatchesRegularExpression('/WHERE\s+.*\s+IN\s+\([^)]+\)/i', $query, 'Identities were not obtained with the single query (missing "WHERE ... IN" condition)'); + } + + $this->assertCount(2, $identities); + } + public function testModelFindByIdWithIdentitiesUserNotExists(): void { $user = model(UserModel::class)->where('active', 0)->withIdentities()->findById(1); From 8e854c859fd988e41f367a5b8a9db0f95ac2ca4a Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 29 Aug 2023 17:58:29 +0200 Subject: [PATCH 092/401] code style fixes --- phpstan.neon.dist | 3 ++- src/Models/UserModel.php | 2 +- tests/Unit/UserTest.php | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 8c77114cc..b4186e33f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -12,7 +12,8 @@ parameters: ignoreErrors: - '#Call to an undefined method CodeIgniter\\Database\\ConnectionInterface::[A-Za-z].+\(\)#' - '#Cannot access property [\$a-z_]+ on (array|object)#' - - + - '#Call to an undefined method CodeIgniter\\Shield\\Models\\UserModel::getLastQuery\(\)#' + - message: '#Call to deprecated function random_string\(\):#' paths: - src/Authentication/Actions/Email2FA.php diff --git a/src/Models/UserModel.php b/src/Models/UserModel.php index 1bae3a03a..3c6f5f518 100644 --- a/src/Models/UserModel.php +++ b/src/Models/UserModel.php @@ -113,7 +113,7 @@ protected function fetchIdentities(array $data): array */ private function assignIdentities(array $data, array $identities): array { - $mappedUsers = []; + $mappedUsers = []; $userIdentities = []; $users = $data['singleton'] ? [$data['data']] : $data['data']; diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index fd7584416..55caab6e3 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -106,7 +106,11 @@ public function testModelFindAllWithIdentitiesWhereInQuery(): void // Check the last query and see if a proper type of query was used $query = (string) model(UserModel::class)->getLastQuery(); - $this->assertMatchesRegularExpression('/WHERE\s+.*\s+IN\s+\([^)]+\)/i', $query, 'Identities were not obtained with the single query (missing "WHERE ... IN" condition)'); + $this->assertMatchesRegularExpression( + '/WHERE\s+.*\s+IN\s+\([^)]+\)/i', + $query, + 'Identities were not obtained with the single query (missing "WHERE ... IN" condition)' + ); } $this->assertCount(2, $identities); From d5f605851d794dd5045a49f72e42654c4adfc731 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:41:58 +0000 Subject: [PATCH 093/401] build(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deptrac.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/no-merge-commits.yml | 2 +- .github/workflows/phpcpd.yml | 2 +- .github/workflows/phpcsfixer.yml | 2 +- .github/workflows/phpstan.yml | 2 +- .github/workflows/phpunit-lang.yml | 2 +- .github/workflows/phpunit.yml | 2 +- .github/workflows/psalm.yml | 2 +- .github/workflows/rector.yml | 2 +- .github/workflows/unused.yml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index b8d7bcfba..078f78b56 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 61269b119..520a60791 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/.github/workflows/no-merge-commits.yml b/.github/workflows/no-merge-commits.yml index 9c9388589..2fab382c8 100644 --- a/.github/workflows/no-merge-commits.yml +++ b/.github/workflows/no-merge-commits.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run test uses: NexusPHP/no-merge-commits@v1.2.0 diff --git a/.github/workflows/phpcpd.yml b/.github/workflows/phpcpd.yml index ecd45d1c6..f7063e715 100644 --- a/.github/workflows/phpcpd.yml +++ b/.github/workflows/phpcpd.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml index cbf5566a8..915633b98 100644 --- a/.github/workflows/phpcsfixer.yml +++ b/.github/workflows/phpcsfixer.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 9e0f6339a..638291de8 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/phpunit-lang.yml b/.github/workflows/phpunit-lang.yml index f8f1db140..dbd7f5a2b 100644 --- a/.github/workflows/phpunit-lang.yml +++ b/.github/workflows/phpunit-lang.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f88c90578..0482ce217 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -122,7 +122,7 @@ jobs: run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 67be11c56..57ad2c7e9 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 0abf2719b..7ad653414 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index baec23af2..6341fbb86 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 From e3f4d8f750446bb46f374e23e024b1640623482a Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Tue, 5 Sep 2023 07:50:00 +0200 Subject: [PATCH 094/401] Apply suggestions from code review Co-authored-by: kenjis --- tests/Unit/UserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index 55caab6e3..784800063 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -95,7 +95,7 @@ public function testModelFindAllWithIdentitiesWhereInQuery(): void // Grab the user again, using the model's identity helper $users = model(UserModel::class)->withIdentities()->findAll(); - $identities = 0; + $identities = []; foreach ($users as $user) { if ($user->id !== $this->user->id) { From 4c7a3affe8e1ff056ea9a1e59c3a985ee35b9a23 Mon Sep 17 00:00:00 2001 From: Mohammed AlShannaq Date: Tue, 5 Sep 2023 20:18:25 +0300 Subject: [PATCH 095/401] lang: [ar] update arabic translate --- src/Language/ar/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 5184ff2b2..3b08f8f8f 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -30,7 +30,7 @@ 'password' => 'كلمة المرور', 'passwordConfirm' => 'كلمة المرور (مرة اخرى)', 'haveAccount' => 'هل لديك حساب بالفعل؟', - 'token' => '(To be translated) Token', + 'token' => 'رمز الوصول', // Buttons 'confirm' => 'تاكيد', From 1834262fabbee0539232e8979b1b21f81657c8e6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 15:55:58 +0900 Subject: [PATCH 096/401] chore: phpcsfixer.yml uses workflow in codeigniter4/.github --- .github/workflows/phpcsfixer.yml | 54 ++------------------------------ 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml index 915633b98..84cbc39e7 100644 --- a/.github/workflows/phpcsfixer.yml +++ b/.github/workflows/phpcsfixer.yml @@ -2,58 +2,8 @@ name: PHPCSFixer on: pull_request: - branches: - - develop - paths: - - '**.php' - - '.github/workflows/phpcsfixer.yml' push: - branches: - - develop - paths: - - '**.php' - - '.github/workflows/phpcsfixer.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Coding Standards - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: json, tokenizer - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Check code for standards compliance - run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff + phpcsfixer: + uses: codeigniter4/.github/.github/workflows/phpcsfixer.yml@main From c7bc8e47c94b47d33a447ccd99c3e647ffce217a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 16:02:12 +0900 Subject: [PATCH 097/401] chore: deptrac.yml uses workflow in codeigniter4/.github --- .github/workflows/deptrac.yml | 67 ++--------------------------------- 1 file changed, 2 insertions(+), 65 deletions(-) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index 078f78b56..0de9b55fe 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -2,71 +2,8 @@ name: Deptrac on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'depfile.yaml' - - '.github/workflows/deptrac.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'depfile.yaml' - - '.github/workflows/deptrac.yml' jobs: - build: - name: Dependency Tracing - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - tools: phive - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Deptrac cache directory - run: mkdir -p build/ - - - name: Cache Deptrac results - uses: actions/cache@v3 - with: - path: build - key: ${{ runner.os }}-deptrac-${{ github.sha }} - restore-keys: ${{ runner.os }}-deptrac- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Trace dependencies - run: | - sudo phive --no-progress install --global --trust-gpg-keys B8F640134AB1782E,A98E898BB53EB748 qossmic/deptrac - deptrac analyze --cache-file=build/deptrac.cache + deptrac: + uses: codeigniter4/.github/.github/workflows/deptrac.yml@main From 57653b51ace014f510c401085256d7a72eef956e Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 16:07:26 +0900 Subject: [PATCH 098/401] chore: use workflows in codeigniter4/.github --- .github/workflows/phpstan.yml | 69 +------------ .github/workflows/phpunit.yml | 180 +--------------------------------- .github/workflows/psalm.yml | 65 +----------- .github/workflows/rector.yml | 61 +----------- .github/workflows/unused.yml | 53 +--------- 5 files changed, 10 insertions(+), 418 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 638291de8..fabe55856 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -2,73 +2,8 @@ name: PHPStan on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'phpstan*' - - '.github/workflows/phpstan.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'phpstan*' - - '.github/workflows/phpstan.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Static Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: phpstan, phpunit - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create PHPStan cache directory - run: mkdir -p build/phpstan - - - name: Cache PHPStan results - uses: actions/cache@v3 - with: - path: build/phpstan - key: ${{ runner.os }}-phpstan-${{ github.sha }} - restore-keys: ${{ runner.os }}-phpstan- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Run static analysis - run: vendor/bin/phpstan analyze + phpstan: + uses: codeigniter4/.github/.github/workflows/phpstan.yml@main diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 0482ce217..3d476af3b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -2,184 +2,8 @@ name: PHPUnit on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'phpunit*' - - '.github/workflows/phpunit.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'phpunit*' - - '.github/workflows/phpunit.yml' jobs: - main: - name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - ${{ matrix.dependencies }} - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - matrix: - php-versions: ['7.4', '8.0', '8.1', '8.2'] - db-platforms: ['MySQLi', 'SQLite3'] - mysql-versions: ['5.7'] - dependencies: ['highest'] - include: - # MySQL 8.0 - - php-versions: '7.4' - db-platforms: MySQLi - mysql-versions: '8.0' - dependencies: 'highest' - # Lowest Dependency - - php-versions: '7.4' - db-platforms: MySQLi - mysql-versions: '5.7' - dependencies: 'lowest' - # Postgre - - php-versions: '7.4' - db-platforms: Postgre - mysql-versions: '5.7' - dependencies: 'highest' - # SQLSRV - - php-versions: '7.4' - db-platforms: SQLSRV - mysql-versions: '5.7' - dependencies: 'highest' - # OCI8 - - php-versions: '7.4' - db-platforms: OCI8 - mysql-versions: '5.7' - dependencies: 'highest' - - services: - mysql: - image: mysql:${{ matrix.mysql-versions }} - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: test - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - postgres: - image: postgres - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test - ports: - - 5432:5432 - options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 - - mssql: - image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 - env: - SA_PASSWORD: 1Secure*Password1 - ACCEPT_EULA: Y - MSSQL_PID: Developer - ports: - - 1433:1433 - options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 - - oracle: - image: quillbuilduser/oracle-18-xe - env: - ORACLE_ALLOW_REMOTE: true - ports: - - 1521:1521 - options: --health-cmd="/opt/oracle/product/18c/dbhomeXE/bin/sqlplus -s sys/Oracle18@oracledbxe/XE as sysdba <<< 'SELECT 1 FROM DUAL'" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Create database for MSSQL Server - if: matrix.db-platforms == 'SQLSRV' - run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" - - - name: Install Oracle InstantClient - if: matrix.db-platforms == 'OCI8' - run: | - sudo apt-get install wget libaio1 alien - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo dpkg -i oracle-instantclient18.5-basic_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-devel_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-sqlplus_18.5.0.0.0-4_amd64.deb - echo "LD_LIBRARY_PATH=/lib/oracle/18.5/client64/lib/" >> $GITHUB_ENV - echo "NLS_LANG=AMERICAN_AMERICA.UTF8" >> $GITHUB_ENV - echo "C_INCLUDE_PATH=/usr/include/oracle/18.5/client64" >> $GITHUB_ENV - echo 'NLS_DATE_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_TZ_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - - - name: Create database for Oracle Database - if: matrix.db-platforms == 'OCI8' - run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: composer, phive, phpunit - extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, sqlsrv, oci8, pgsql - coverage: xdebug - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install ${{ env.COMPOSER_UPDATE_FLAGS }} --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update ${{ env.COMPOSER_UPDATE_FLAGS }} --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - env: - COMPOSER_UPDATE_FLAGS: ${{ matrix.dependencies == 'lowest' && '--prefer-lowest' || '' }} - - - name: Test with PHPUnit - run: vendor/bin/phpunit --verbose --coverage-text --testsuite main - env: - DB: ${{ matrix.db-platforms }} - TERM: xterm-256color - TACHYCARDIA_MONITOR_GA: enabled - - - if: matrix.php-versions == '8.0' - name: Run Coveralls - continue-on-error: true - run: | - sudo phive --no-progress install --global --trust-gpg-keys E82B2FB314E9906E php-coveralls - php-coveralls --verbose --coverage_clover=build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - - coveralls: - needs: [main] - name: Coveralls Finished - runs-on: ubuntu-latest - steps: - - name: Upload Coveralls results - uses: coverallsapp/github-action@master - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + phpunit: + uses: codeigniter4/.github/.github/workflows/phpunit.yml@main diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 57ad2c7e9..472244417 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -2,69 +2,8 @@ name: Psalm on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'psalm*' - - '.github/workflows/psalm.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'psalm*' - - '.github/workflows/psalm.yml' jobs: - build: - name: Psalm Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phpstan, phpunit - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Psalm cache directory - run: mkdir -p build/psalm - - - name: Cache Psalm results - uses: actions/cache@v3 - with: - path: build/psalm - key: ${{ runner.os }}-psalm-${{ github.sha }} - restore-keys: ${{ runner.os }}-psalm- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Run Psalm analysis - run: vendor/bin/psalm + psalm: + uses: codeigniter4/.github/.github/workflows/psalm.yml@main diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 7ad653414..88eec78c7 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -2,65 +2,8 @@ name: Rector on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'rector.php' - - '.github/workflows/rector.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - 'rector.php' - - '.github/workflows/rector.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Rector Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: phpstan - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Analyze for refactoring - run: | - composer global require --dev rector/rector:^0.15.1 - rector process --dry-run --no-progress-bar + rector: + uses: codeigniter4/.github/.github/workflows/rector.yml@main diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index 6341fbb86..13b155cce 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -2,57 +2,8 @@ name: Unused on: pull_request: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - '.github/workflows/unused.yml' push: - branches: - - develop - paths: - - '**.php' - - 'composer.*' - - '.github/workflows/unused.yml' jobs: - build: - name: Unused Package Detection - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: composer, composer-unused - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Detect unused packages - run: composer-unused -vvv --output-format=github --ansi --no-interaction --no-progress + unused: + uses: codeigniter4/.github/.github/workflows/unused.yml@main From ab626d530d49e210d027bf4ff0e9ead09b3cd6dc Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 16:42:17 +0900 Subject: [PATCH 099/401] chore: install/update rector to 0.18.1 --- composer.json | 3 ++- rector.php | 30 +++++------------------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 3a7963a38..08315f74c 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "codeigniter4/framework": "^4.2.7", "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", - "firebase/php-jwt": "^6.4" + "firebase/php-jwt": "^6.4", + "rector/rector": "0.18.1" }, "provide": { "codeigniter4/authentication-implementation": "1.0" diff --git a/rector.php b/rector.php index bbc490708..5bff4da9b 100644 --- a/rector.php +++ b/rector.php @@ -5,9 +5,7 @@ use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector; use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector; use Rector\CodeQuality\Rector\Expression\InlineIfToExplicitIfRector; -use Rector\CodeQuality\Rector\For_\ForToForeachRector; use Rector\CodeQuality\Rector\Foreach_\UnusedForeachValueToArrayKeysRector; -use Rector\CodeQuality\Rector\FuncCall\AddPregQuoteDelimiterRector; use Rector\CodeQuality\Rector\FuncCall\ChangeArrayPushToArrayAssignRector; use Rector\CodeQuality\Rector\FuncCall\SimplifyRegexPatternRector; use Rector\CodeQuality\Rector\FuncCall\SimplifyStrposLowerRector; @@ -24,7 +22,6 @@ use Rector\Core\ValueObject\PhpVersion; use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPromotedPropertyRector; use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector; -use Rector\DeadCode\Rector\MethodCall\RemoveEmptyMethodCallRector; use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector; use Rector\DeadCode\Rector\StmtsAwareInterface\RemoveJustPropertyFetchForAssignRector; use Rector\EarlyReturn\Rector\Foreach_\ChangeNestedForeachIfsToEarlyContinueRector; @@ -32,13 +29,12 @@ use Rector\EarlyReturn\Rector\If_\RemoveAlwaysElseRector; use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; -use Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector; use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; -use Rector\PHPUnit\Rector\Class_\AnnotationWithValueToAttributeRector; +use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector; +use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector; -use Rector\PSR4\Rector\FileWithoutNamespace\NormalizeNamespaceByPSR4ComposerAutoloadRector; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; @@ -46,7 +42,7 @@ $rectorConfig->sets([ SetList::DEAD_CODE, LevelSetList::UP_TO_PHP_74, - PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, PHPUnitSetList::PHPUNIT_100, ]); @@ -84,29 +80,15 @@ JsonThrowOnErrorRector::class, StringifyStrNeedlesRector::class, + YieldDataProviderRector::class, // Note: requires php 8 RemoveUnusedPromotedPropertyRector::class, AnnotationWithValueToAttributeRector::class, - // Ignore tests that might make calls without a result - RemoveEmptyMethodCallRector::class => [ - __DIR__ . '/tests', - ], - - // Ignore files that should not be namespaced to their folder - NormalizeNamespaceByPSR4ComposerAutoloadRector::class => [ - __DIR__ . '/src/Helpers', - __DIR__ . '/src/Language', - __DIR__ . '/tests/_support', - ], - // May load view files directly when detecting classes StringClassNameToClassConstantRector::class, - // May be uninitialized on purpose - AddDefaultValueForUndefinedVariableRector::class, - // See https://github.com/codeigniter4/shield/issues/228 RemoveJustPropertyFetchForAssignRector::class => [ __DIR__ . '/src/Models/UserModel.php', @@ -124,13 +106,13 @@ __DIR__ . '/tests/Commands/SetupTest.php', ], ]); + // auto import fully qualified class names $rectorConfig->importNames(); $rectorConfig->rule(SimplifyUselessVariableRector::class); $rectorConfig->rule(RemoveAlwaysElseRector::class); $rectorConfig->rule(CountArrayToEmptyArrayComparisonRector::class); - $rectorConfig->rule(ForToForeachRector::class); $rectorConfig->rule(ChangeNestedForeachIfsToEarlyContinueRector::class); $rectorConfig->rule(ChangeIfElseValueAssignToEarlyReturnRector::class); $rectorConfig->rule(SimplifyStrposLowerRector::class); @@ -143,12 +125,10 @@ $rectorConfig->rule(UnusedForeachValueToArrayKeysRector::class); $rectorConfig->rule(ChangeArrayPushToArrayAssignRector::class); $rectorConfig->rule(UnnecessaryTernaryExpressionRector::class); - $rectorConfig->rule(AddPregQuoteDelimiterRector::class); $rectorConfig->rule(SimplifyRegexPatternRector::class); $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class); $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class); $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class); - $rectorConfig->rule(NormalizeNamespaceByPSR4ComposerAutoloadRector::class); $rectorConfig->rule(StringClassNameToClassConstantRector::class); $rectorConfig->rule(PrivatizeFinalClassPropertyRector::class); $rectorConfig->rule(CompleteDynamicPropertiesRector::class); From f249b188b894794f246f1d73d8d598c666f37315 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 6 Sep 2023 17:20:51 +0900 Subject: [PATCH 100/401] test: refactor by rector --- tests/Controllers/ActionsTest.php | 2 +- tests/Unit/PwnedValidatorTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Controllers/ActionsTest.php b/tests/Controllers/ActionsTest.php index c8530ffe4..cb815af40 100644 --- a/tests/Controllers/ActionsTest.php +++ b/tests/Controllers/ActionsTest.php @@ -93,7 +93,7 @@ public function testEmail2FAHandleInvalidEmail(): void ]); $result->assertRedirect(); - $result->assertEquals(site_url('/auth/a/show'), $result->getRedirectUrl()); + $result->assertSame(site_url('/auth/a/show'), $result->getRedirectUrl()); $result->assertSessionHas('error', lang('Auth.invalidEmail')); } diff --git a/tests/Unit/PwnedValidatorTest.php b/tests/Unit/PwnedValidatorTest.php index 8c86706fb..1d0c7ec90 100644 --- a/tests/Unit/PwnedValidatorTest.php +++ b/tests/Unit/PwnedValidatorTest.php @@ -122,10 +122,10 @@ public function testCheckCatchesAndRethrowsCurlExceptionAsAuthException(): void ->getMock(); $curlrequest->method('get') - ->will($this->throwException(HTTPException::forCurlError( + ->willThrowException(HTTPException::forCurlError( '7', 'Failed to connect' - ))); + )); Services::injectMock('curlrequest', $curlrequest); $this->expectException(AuthenticationException::class); From becdbcdbc9d0b3d134a4b917b33062c3ec4a0ded Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 7 Sep 2023 00:42:58 +0330 Subject: [PATCH 101/401] chore: add new GitHub action for smart commenting --- .github/workflows/smart_commenting.yml | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/smart_commenting.yml diff --git a/.github/workflows/smart_commenting.yml b/.github/workflows/smart_commenting.yml new file mode 100644 index 000000000..b0d934291 --- /dev/null +++ b/.github/workflows/smart_commenting.yml @@ -0,0 +1,45 @@ +name: Smart commenting +on: + pull_request: + types: + - labeled +jobs: + + add-comment-for-GPG-Signing: + if: github.event.label.name == 'GPG-Signing needed' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for GPG-sign + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + You must GPG-sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. See Developer's Certificate of Origin. + See [signing][1]. + + **Note that all your commits must be signed.** If you have an unsigned commit, you can sign the previous commits by referring to [gpg-signing-old-commits][2]. + + [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#signing + [2]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/workflow.md#gpg-signing-old-commits + + add-comment-for-tests: + if: github.event.label.name == 'tests needed' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for PHPUnit test + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + **Unit Testing:** + Unit testing is expected for all CodeIgniter components. We use PHPUnit, and run unit tests using GitHub Actions for each PR submitted or changed. + + **So please write a unit test or update the existing tests.** + + See [unit testing][1] for more info. + + [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#unit-testing From fdf6af28ab7d334655d4519229315460079a48c1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 7 Sep 2023 08:59:13 +0900 Subject: [PATCH 102/401] chore: update rector Co-authored-by: Pooya Parsa --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 08315f74c..e431bff3e 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", "firebase/php-jwt": "^6.4", - "rector/rector": "0.18.1" + "rector/rector": "0.18.2" }, "provide": { "codeigniter4/authentication-implementation": "1.0" From 32d1ff71b03aef192aa7b573bed4524681bfff10 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 10:38:31 +0900 Subject: [PATCH 103/401] docs: fix filename --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ea308457..d5c97a907 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ whether you can code, write documentation, or help find bugs, all contributions ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Acknowledgements From e1edca6f9f3febba4b5360e5c4216b4c958a12ab Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 10:58:26 +0900 Subject: [PATCH 104/401] chore: rename filename --- .github/workflows/{smart_commenting.yml => smart-commenting.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{smart_commenting.yml => smart-commenting.yml} (100%) diff --git a/.github/workflows/smart_commenting.yml b/.github/workflows/smart-commenting.yml similarity index 100% rename from .github/workflows/smart_commenting.yml rename to .github/workflows/smart-commenting.yml From 93a156728d9cfd483f9a95f52fadd72068018a67 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Sep 2023 10:59:22 +0900 Subject: [PATCH 105/401] chore: fix name --- .github/workflows/smart-commenting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smart-commenting.yml b/.github/workflows/smart-commenting.yml index b0d934291..25b6c99ec 100644 --- a/.github/workflows/smart-commenting.yml +++ b/.github/workflows/smart-commenting.yml @@ -1,4 +1,4 @@ -name: Smart commenting +name: Smart Commenting on: pull_request: types: From 15da7528746cad7632343fe75dec80801cc4a58f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:00:32 +0000 Subject: [PATCH 106/401] build(deps): bump NexusPHP/no-merge-commits from 1.2.0 to 2.0.0 Bumps [NexusPHP/no-merge-commits](https://github.com/nexusphp/no-merge-commits) from 1.2.0 to 2.0.0. - [Release notes](https://github.com/nexusphp/no-merge-commits/releases) - [Changelog](https://github.com/NexusPHP/no-merge-commits/blob/2.x/CHANGELOG.md) - [Commits](https://github.com/nexusphp/no-merge-commits/compare/v1.2.0...v2.0.0) --- updated-dependencies: - dependency-name: NexusPHP/no-merge-commits dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/no-merge-commits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-merge-commits.yml b/.github/workflows/no-merge-commits.yml index 2fab382c8..11173fbb4 100644 --- a/.github/workflows/no-merge-commits.yml +++ b/.github/workflows/no-merge-commits.yml @@ -17,6 +17,6 @@ jobs: uses: actions/checkout@v4 - name: Run test - uses: NexusPHP/no-merge-commits@v1.2.0 + uses: NexusPHP/no-merge-commits@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} From c552c5fdafd1525e97368ef794604c30467eaab7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Sep 2023 07:16:06 +0900 Subject: [PATCH 107/401] fix: property visibility Controller may be extended. See https://codeigniter4.github.io/shield/customization/#extending-the-controllers --- src/Controllers/RegisterController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 469c23007..1e31ee41a 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -33,7 +33,7 @@ class RegisterController extends BaseController /** * Auth Table names */ - private array $tables; + protected array $tables; public function initController( RequestInterface $request, From 71d449ed4617a2687976dd0f1758c56bdf0915e4 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 12 Sep 2023 08:10:21 +0330 Subject: [PATCH 108/401] chore: remove not needed Routes from phpstan exclude --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b4186e33f..53fd0c593 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,7 +7,6 @@ parameters: bootstrapFiles: - vendor/codeigniter4/framework/system/Test/bootstrap.php excludePaths: - - src/Config/Routes.php - src/Views/* ignoreErrors: - '#Call to an undefined method CodeIgniter\\Database\\ConnectionInterface::[A-Za-z].+\(\)#' From c6862c245471e49f4babaf911544e528af2d8c7a Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Sep 2023 14:41:48 +0900 Subject: [PATCH 109/401] chore: fix workflow on section --- .github/workflows/deptrac.yml | 14 ++++++++++++++ .github/workflows/phpcsfixer.yml | 10 ++++++++++ .github/workflows/phpstan.yml | 14 ++++++++++++++ .github/workflows/phpunit.yml | 14 ++++++++++++++ .github/workflows/psalm.yml | 14 ++++++++++++++ .github/workflows/rector.yml | 14 ++++++++++++++ .github/workflows/unused.yml | 12 ++++++++++++ 7 files changed, 92 insertions(+) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index 0de9b55fe..ebaf4df1e 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -2,7 +2,21 @@ name: Deptrac on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'depfile.yaml' + - '.github/workflows/deptrac.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'depfile.yaml' + - '.github/workflows/deptrac.yml' jobs: deptrac: diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml index 84cbc39e7..ee1221ac2 100644 --- a/.github/workflows/phpcsfixer.yml +++ b/.github/workflows/phpcsfixer.yml @@ -2,7 +2,17 @@ name: PHPCSFixer on: pull_request: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' push: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' jobs: phpcsfixer: diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index fabe55856..58e2add51 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -2,7 +2,21 @@ name: PHPStan on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.github/workflows/phpstan.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.github/workflows/phpstan.yml' jobs: phpstan: diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 3d476af3b..9bcd5ec40 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -2,7 +2,21 @@ name: PHPUnit on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit.yml' jobs: phpunit: diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 472244417..53c76e7f9 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -2,7 +2,21 @@ name: Psalm on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' jobs: psalm: diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 88eec78c7..8c19b162d 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -2,7 +2,21 @@ name: Rector on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'rector.php' + - '.github/workflows/rector.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'rector.php' + - '.github/workflows/rector.yml' jobs: rector: diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index 13b155cce..1758dda62 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -2,7 +2,19 @@ name: Unused on: pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - '.github/workflows/unused.yml' push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - '.github/workflows/unused.yml' jobs: unused: From 00f111599d9db906cbfc7fdc14b7f4f1fe23d2e3 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 12 Sep 2023 16:27:17 +0800 Subject: [PATCH 110/401] Add phpstan-codeigniter --- composer.json | 4 +++- phpstan.neon.dist | 11 ++++++----- src/Config/Services.php | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index e431bff3e..49d033273 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,9 @@ "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", "firebase/php-jwt": "^6.4", - "rector/rector": "0.18.2" + "rector/rector": "0.18.2", + "codeigniter/phpstan-codeigniter": "^1.1", + "phpstan/extension-installer": "^1.3" }, "provide": { "codeigniter4/authentication-implementation": "1.0" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 53fd0c593..d3f585487 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -21,14 +21,15 @@ parameters: - src/Models/TokenLoginModel.php - src/Models/UserIdentityModel.php universalObjectCratesClasses: - - CodeIgniter\Entity - - CodeIgniter\Entity\Entity - Faker\Generator scanDirectories: - vendor/codeigniter4/framework/system/Helpers - vendor/codeigniter4/settings/src/Helpers dynamicConstantNames: - - APP_NAMESPACE - - CI_DEBUG - - ENVIRONMENT - CodeIgniter\CodeIgniter::CI_VERSION + codeigniter: + additionalConfigNamespaces: + - CodeIgniter\Settings\Config + - CodeIgniter\Shield\Config + additionalServices: + - CodeIgniter\Shield\Config\Services diff --git a/src/Config/Services.php b/src/Config/Services.php index 1e002b6a0..1a4647cf0 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -8,7 +8,7 @@ use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\JWTManager; use CodeIgniter\Shield\Authentication\Passwords; -use Config\Services as BaseService; +use CodeIgniter\Config\BaseService; class Services extends BaseService { From 6607df1b0faf977349a1c71348f5adb72bacd089 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 12 Sep 2023 16:28:23 +0800 Subject: [PATCH 111/401] Generate baseline --- phpstan-baseline.php | 30 ++++++++++++++++++++++++++++++ phpstan.neon.dist | 3 +++ 2 files changed, 33 insertions(+) create mode 100644 phpstan-baseline.php diff --git a/phpstan-baseline.php b/phpstan-baseline.php new file mode 100644 index 000000000..3f1ecd10b --- /dev/null +++ b/phpstan-baseline.php @@ -0,0 +1,30 @@ + '#^Call to function assert\\(\\) with false and \'Config Auth…\' will always evaluate to false\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/RegisterController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Instanceof between null and CodeIgniter\\\\Shield\\\\Models\\\\UserModel will always evaluate to false\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/RegisterController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$name of function model expects a valid class string, array\\|bool\\|float\\|int\\|object\\|string\\|null given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/RegisterController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CodeIgniter\\\\Shield\\\\Filters\\\\AuthRates\\:\\:before\\(\\) should return CodeIgniter\\\\HTTP\\\\RedirectResponse\\|void but returns CodeIgniter\\\\HTTP\\\\ResponseInterface\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Filters/AuthRates.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method CodeIgniter\\\\Shield\\\\Filters\\\\TokenAuth\\:\\:before\\(\\) should return CodeIgniter\\\\HTTP\\\\RedirectResponse\\|void but returns CodeIgniter\\\\HTTP\\\\ResponseInterface\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Filters/TokenAuth.php', +]; + +return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d3f585487..46da9629b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,3 +1,6 @@ +includes: + - phpstan-baseline.php + parameters: tmpDir: build/phpstan level: 5 From 5a5e2baec1ad2edc081fadbcd2ed932d02f49566 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 12 Sep 2023 16:29:24 +0800 Subject: [PATCH 112/401] Regenerate baseline --- phpstan-baseline.php | 105 +++++++++++++++++++++++++++++++++++++++++++ phpstan.neon.dist | 12 ----- 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 3f1ecd10b..7400fdd6f 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1,6 +1,24 @@ '#^Call to deprecated function random_string\\(\\)\\: +The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Actions/Email2FA.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to deprecated function random_string\\(\\)\\: +The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Actions/EmailActivator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to deprecated function random_string\\(\\)\\: +The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/MagicLinkController.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to function assert\\(\\) with false and \'Config Auth…\' will always evaluate to false\\.$#', 'count' => 1, @@ -16,6 +34,16 @@ 'count' => 1, 'path' => __DIR__ . '/src/Controllers/RegisterController.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to an undefined method CodeIgniter\\\\Database\\\\ConnectionInterface\\:\\:disableForeignKeyChecks\\(\\)\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to an undefined method CodeIgniter\\\\Database\\\\ConnectionInterface\\:\\:enableForeignKeyChecks\\(\\)\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CodeIgniter\\\\Shield\\\\Filters\\\\AuthRates\\:\\:before\\(\\) should return CodeIgniter\\\\HTTP\\\\RedirectResponse\\|void but returns CodeIgniter\\\\HTTP\\\\ResponseInterface\\.$#', 'count' => 1, @@ -26,5 +54,82 @@ 'count' => 2, 'path' => __DIR__ . '/src/Filters/TokenAuth.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to deprecated function random_string\\(\\)\\: +The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/TokenLoginModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/TokenLoginModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to deprecated function random_string\\(\\)\\: +The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Authentication/AuthHelperTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 6, + 'path' => __DIR__ . '/tests/Authentication/Filters/SessionFilterTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Authentication/HasAccessTokensTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 4, + 'path' => __DIR__ . '/tests/Authorization/AuthorizableTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Unit/UserModelTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to an undefined method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:getLastQuery\\(\\)\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$active on array\\|object\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$email on array\\|object\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', + 'count' => 4, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot access property \\$password_hash on array\\|object\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 46da9629b..976aa1e1a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,18 +11,6 @@ parameters: - vendor/codeigniter4/framework/system/Test/bootstrap.php excludePaths: - src/Views/* - ignoreErrors: - - '#Call to an undefined method CodeIgniter\\Database\\ConnectionInterface::[A-Za-z].+\(\)#' - - '#Cannot access property [\$a-z_]+ on (array|object)#' - - '#Call to an undefined method CodeIgniter\\Shield\\Models\\UserModel::getLastQuery\(\)#' - - - message: '#Call to deprecated function random_string\(\):#' - paths: - - src/Authentication/Actions/Email2FA.php - - src/Authentication/Actions/EmailActivator.php - - src/Controllers/MagicLinkController.php - - src/Models/TokenLoginModel.php - - src/Models/UserIdentityModel.php universalObjectCratesClasses: - Faker\Generator scanDirectories: From ad10ffcc1da4babd5f07122799d5f160020bd564 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 12 Sep 2023 16:30:45 +0800 Subject: [PATCH 113/401] Add to rector --- rector.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rector.php b/rector.php index 5bff4da9b..082ce9de7 100644 --- a/rector.php +++ b/rector.php @@ -64,9 +64,10 @@ realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', ]); - if (is_file(__DIR__ . '/phpstan.neon.dist')) { - $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon.dist'); - } + $rectorConfig->phpstanConfigs([ + __DIR__ . '/phpstan.neon.dist', + __DIR__ . '/vendor/codeigniter/phpstan-codeigniter/extension.neon', + ]); // Set the target version for refactoring $rectorConfig->phpVersion(PhpVersion::PHP_74); From 16f1eeecf97a1e7e9cf0645f37e3ed65bb826576 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 12 Sep 2023 16:31:57 +0800 Subject: [PATCH 114/401] Fix code style --- src/Config/Services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config/Services.php b/src/Config/Services.php index 1a4647cf0..42960ff7d 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -4,11 +4,11 @@ namespace CodeIgniter\Shield\Config; +use CodeIgniter\Config\BaseService; use CodeIgniter\Shield\Auth; use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\JWTManager; use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Config\BaseService; class Services extends BaseService { From d629cd43c7865e70b4016f3e1e95486fa0741e97 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 16:54:07 +0900 Subject: [PATCH 115/401] test: add test for registation with no email login --- tests/Controllers/RegisterTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Controllers/RegisterTest.php b/tests/Controllers/RegisterTest.php index 3b8e73afe..630c13b38 100644 --- a/tests/Controllers/RegisterTest.php +++ b/tests/Controllers/RegisterTest.php @@ -82,6 +82,16 @@ public function testRegisterActionSuccess(): void $this->assertTrue($user->active); } + public function testRegisterActionSuccessWithNoEmailLogin(): void + { + /** @var Auth $config */ + $config = config('Auth'); + // Use `username` for login + $config->validFields = ['username']; + + $this->testRegisterActionSuccess(); + } + public function testRegisterTooLongPasswordDefault(): void { $result = $this->withSession()->post('/register', [ From b18e7ea93dd1696605e5f525196c0daf00667bdc Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 13 Sep 2023 16:55:25 +0900 Subject: [PATCH 116/401] fix: cannot register when you remove email from $validFields CodeIgniter\Shield\Exceptions\LogicException : "$user->id" is null. You should not use the incomplete User object. --- src/Authentication/Passwords/ValidationRules.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Authentication/Passwords/ValidationRules.php b/src/Authentication/Passwords/ValidationRules.php index 087b15a30..2ad59c87d 100644 --- a/src/Authentication/Passwords/ValidationRules.php +++ b/src/Authentication/Passwords/ValidationRules.php @@ -102,10 +102,9 @@ protected function buildUserFromData(array $data = []): User */ protected function prepareValidFields(): array { - $config = config('Auth'); - $fields = array_merge($config->validFields, $config->personalFields); - $fields[] = 'password'; + $config = config('Auth'); + $fields = array_merge($config->validFields, $config->personalFields, ['email', 'password']); - return $fields; + return array_unique($fields); } } From b79072dd7b3fdf163cd9119321e9b1b8b0dacfb9 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 13 Sep 2023 12:33:59 +0330 Subject: [PATCH 117/401] chore: add conflict to smart-commenting --- .github/workflows/smart-commenting.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/smart-commenting.yml b/.github/workflows/smart-commenting.yml index 25b6c99ec..51ba57c83 100644 --- a/.github/workflows/smart-commenting.yml +++ b/.github/workflows/smart-commenting.yml @@ -43,3 +43,21 @@ jobs: See [unit testing][1] for more info. [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#unit-testing + + add-comment-for-conflict: + if: github.event.label.name == 'stale' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for resolving a merge conflict + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + We detected conflicts in your PR against the base branch :speak_no_evil: + You may want to sync :arrows_counterclockwise: your branch with upstream! + See [resolving a merge conflict on GitHub][1] or [resolving a merge conflict using the Git][2] for more info. + + [1]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github + [2]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line From 2cdec1803f90a53fcf7de54e635f7c247e6e2c45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:09:54 +0000 Subject: [PATCH 118/401] build(deps-dev): update rector/rector requirement from 0.18.2 to 0.18.3 Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/0.18.2...0.18.3) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 49d033273..11787a81e 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", "firebase/php-jwt": "^6.4", - "rector/rector": "0.18.2", + "rector/rector": "0.18.3", "codeigniter/phpstan-codeigniter": "^1.1", "phpstan/extension-installer": "^1.3" }, From 8e281bf86fb85c2f7abb68a295a4a5bd27b0d34d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 14 Sep 2023 06:43:09 +0330 Subject: [PATCH 119/401] remove github resolving conflict --- .github/workflows/smart-commenting.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smart-commenting.yml b/.github/workflows/smart-commenting.yml index 51ba57c83..a8d8690a7 100644 --- a/.github/workflows/smart-commenting.yml +++ b/.github/workflows/smart-commenting.yml @@ -57,7 +57,6 @@ jobs: body: | We detected conflicts in your PR against the base branch :speak_no_evil: You may want to sync :arrows_counterclockwise: your branch with upstream! - See [resolving a merge conflict on GitHub][1] or [resolving a merge conflict using the Git][2] for more info. + See [resolving a merge conflict using the Git][1] for more info. - [1]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github - [2]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line + [1]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line From 62001d27be565bc9fdb58ba5cc995ebd05670181 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 09:20:44 +0900 Subject: [PATCH 120/401] chore: composer require --dev phpstan/phpstan-strict-rules --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 11787a81e..cae646c7f 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "firebase/php-jwt": "^6.4", "rector/rector": "0.18.3", "codeigniter/phpstan-codeigniter": "^1.1", - "phpstan/extension-installer": "^1.3" + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5" }, "provide": { "codeigniter4/authentication-implementation": "1.0" From b8778dceb95bd21128323bdacbcfe14947392d4a Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 09:32:52 +0900 Subject: [PATCH 121/401] chore: enable some phpstan-strict-rules rules --- phpstan.neon.dist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 976aa1e1a..f415fc148 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,3 +24,9 @@ parameters: - CodeIgniter\Shield\Config additionalServices: - CodeIgniter\Shield\Config\Services + strictRules: + allRules: false + disallowedLooseComparison: true + booleansInConditions: true + disallowedConstructs: true + matchingInheritedMethodNames: true From c113d05796b41e49e8d05df7fbf30757d87cab24 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 09:34:57 +0900 Subject: [PATCH 122/401] chore: update phpstan-baseline vendor/bin/phpstan analyze --generate-baseline phpstan-baseline.php --- phpstan-baseline.php | 275 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 7400fdd6f..4a1a11d17 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1,18 +1,158 @@ '#^Call to function property_exists\\(\\) with CodeIgniter\\\\Shield\\\\Config\\\\Auth and \'userProvider\' will always evaluate to true\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Auth.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Auth.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in a ternary operator condition, CodeIgniter\\\\Shield\\\\Entities\\\\User\\|null given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Auth.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Actions/Email2FA.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authentication/Actions/Email2FA.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Actions/EmailActivator.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authentication.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 4, + 'path' => __DIR__ . '/src/Authentication/Authenticators/AccessTokens.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in &&, CodeIgniter\\\\I18n\\\\Time\\|null given on the left side\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/AccessTokens.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$credentials \\(array\\{token\\?\\: string\\}\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\Authenticators\\\\JWT\\:\\:attempt\\(\\) should be contravariant with parameter \\$credentials \\(array\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\AuthenticatorInterface\\:\\:attempt\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/JWT.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$credentials \\(array\\{token\\?\\: string\\}\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\Authenticators\\\\JWT\\:\\:check\\(\\) should be contravariant with parameter \\$credentials \\(array\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\AuthenticatorInterface\\:\\:check\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/JWT.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 3, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in an elseif condition, string\\|null given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in an if condition, CodeIgniter\\\\Shield\\\\Entities\\\\UserIdentity\\|null given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in an if condition, int\\|string\\|null given\\.$#', + 'count' => 3, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$credentials \\(array\\{email\\?\\: string, username\\?\\: string, password\\?\\: string\\}\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\Authenticators\\\\Session\\:\\:attempt\\(\\) should be contravariant with parameter \\$credentials \\(array\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\AuthenticatorInterface\\:\\:attempt\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$credentials \\(array\\{email\\?\\: string, username\\?\\: string, password\\?\\: string\\}\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\Authenticators\\\\Session\\:\\:check\\(\\) should be contravariant with parameter \\$credentials \\(array\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\AuthenticatorInterface\\:\\:check\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to method CodeIgniter\\\\Shield\\\\Result\\:\\:isOK\\(\\) with incorrect case\\: isOk$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Passwords.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Passwords.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Passwords/CompositionValidator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 5, + 'path' => __DIR__ . '/src/Authentication/Passwords/NothingPersonalValidator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to method CodeIgniter\\\\Shield\\\\Result\\:\\:isOK\\(\\) with incorrect case\\: isOk$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authentication/Passwords/ValidationRules.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Passwords/ValidationRules.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in &&, CodeIgniter\\\\Shield\\\\Entities\\\\User\\|null given on the right side\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Passwords/ValidationRules.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 3, + 'path' => __DIR__ . '/src/Authorization/Groups.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Return type \\(int\\|string\\|null\\) of method CodeIgniter\\\\Shield\\\\Collectors\\\\Auth\\:\\:getBadgeValue\\(\\) should be covariant with return type \\(int\\|null\\) of method CodeIgniter\\\\Debug\\\\Toolbar\\\\Collectors\\\\BaseCollector\\:\\:getBadgeValue\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Collectors/Auth.php', +]; +$ignoreErrors[] = [ + 'message' => '#^PHPDoc type array\\ of property CodeIgniter\\\\Shield\\\\Commands\\\\Generators\\\\UserModelGenerator\\:\\:\\$arguments is not the same as PHPDoc type array of overridden property CodeIgniter\\\\CLI\\\\BaseCommand\\:\\:\\$arguments\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Commands/Generators/UserModelGenerator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^PHPDoc type array\\ of property CodeIgniter\\\\Shield\\\\Commands\\\\Generators\\\\UserModelGenerator\\:\\:\\$options is not the same as PHPDoc type array of overridden property CodeIgniter\\\\CLI\\\\BaseCommand\\:\\:\\$options\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Commands/Generators/UserModelGenerator.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/ActionController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Instanceof between CodeIgniter\\\\Shield\\\\Authentication\\\\Actions\\\\ActionInterface and CodeIgniter\\\\Shield\\\\Authentication\\\\Actions\\\\ActionInterface will always evaluate to true\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/ActionController.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', @@ -44,11 +184,66 @@ 'count' => 1, 'path' => __DIR__ . '/src/Database/Migrations/2020-12-28-223112_create_auth_tables.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$value \\(bool\\|int\\|string\\) of method CodeIgniter\\\\Shield\\\\Entities\\\\Cast\\\\IntBoolCast\\:\\:set\\(\\) should be contravariant with parameter \\$value \\(array\\|bool\\|float\\|int\\|object\\|string\\|null\\) of method CodeIgniter\\\\Entity\\\\Cast\\\\BaseCast\\:\\:set\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/Cast/IntBoolCast.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$value \\(bool\\|int\\|string\\) of method CodeIgniter\\\\Shield\\\\Entities\\\\Cast\\\\IntBoolCast\\:\\:set\\(\\) should be contravariant with parameter \\$value \\(array\\|bool\\|float\\|int\\|object\\|string\\|null\\) of method CodeIgniter\\\\Entity\\\\Cast\\\\CastInterface\\:\\:set\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/Cast/IntBoolCast.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$value \\(int\\) of method CodeIgniter\\\\Shield\\\\Entities\\\\Cast\\\\IntBoolCast\\:\\:get\\(\\) should be contravariant with parameter \\$value \\(array\\|bool\\|float\\|int\\|object\\|string\\|null\\) of method CodeIgniter\\\\Entity\\\\Cast\\\\BaseCast\\:\\:get\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/Cast/IntBoolCast.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$value \\(int\\) of method CodeIgniter\\\\Shield\\\\Entities\\\\Cast\\\\IntBoolCast\\:\\:get\\(\\) should be contravariant with parameter \\$value \\(array\\|bool\\|float\\|int\\|object\\|string\\|null\\) of method CodeIgniter\\\\Entity\\\\Cast\\\\CastInterface\\:\\:get\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/Cast/IntBoolCast.php', +]; +$ignoreErrors[] = [ + 'message' => '#^PHPDoc type array\\ of property CodeIgniter\\\\Shield\\\\Entities\\\\Entity\\:\\:\\$castHandlers is not the same as PHPDoc type array\\ of overridden property CodeIgniter\\\\Entity\\\\Entity\\:\\:\\$castHandlers\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/Entity.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Entities/Group.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 8, + 'path' => __DIR__ . '/src/Entities/User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in a ternary operator condition, int\\<0, max\\> given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Entities/User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Filters/AbstractAuthFilter.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CodeIgniter\\\\Shield\\\\Filters\\\\AuthRates\\:\\:before\\(\\) should return CodeIgniter\\\\HTTP\\\\RedirectResponse\\|void but returns CodeIgniter\\\\HTTP\\\\ResponseInterface\\.$#', 'count' => 1, 'path' => __DIR__ . '/src/Filters/AuthRates.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to method CodeIgniter\\\\HTTP\\\\ResponseInterface\\:\\:setJSON\\(\\) with incorrect case\\: setJson$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Filters/TokenAuth.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Filters/TokenAuth.php', +]; $ignoreErrors[] = [ 'message' => '#^Method CodeIgniter\\\\Shield\\\\Filters\\\\TokenAuth\\:\\:before\\(\\) should return CodeIgniter\\\\HTTP\\\\RedirectResponse\\|void but returns CodeIgniter\\\\HTTP\\\\ResponseInterface\\.$#', 'count' => 2, @@ -76,21 +271,81 @@ 'count' => 1, 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$data \\(array\\|CodeIgniter\\\\Shield\\\\Entities\\\\User\\) of method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:insert\\(\\) should be contravariant with parameter \\$data \\(array\\|object\\|null\\) of method CodeIgniter\\\\Model\\:\\:insert\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#1 \\$data \\(array\\|CodeIgniter\\\\Shield\\\\Entities\\\\User\\) of method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:save\\(\\) should be contravariant with parameter \\$data \\(array\\|object\\) of method CodeIgniter\\\\BaseModel\\:\\:save\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#2 \\$data \\(array\\|CodeIgniter\\\\Shield\\\\Entities\\\\User\\) of method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:update\\(\\) should be contravariant with parameter \\$data \\(array\\|object\\|null\\) of method CodeIgniter\\\\Model\\:\\:update\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Return type \\(int\\|string\\|true\\) of method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:insert\\(\\) should be covariant with return type \\(int\\|object\\|string\\|false\\) of method CodeIgniter\\\\Model\\:\\:insert\\(\\)$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', 'count' => 2, 'path' => __DIR__ . '/tests/Authentication/AuthHelperTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', 'count' => 2, 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', + 'count' => 3, + 'path' => __DIR__ . '/tests/Authentication/Authenticators/JWTAuthenticatorTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', + 'count' => 9, + 'path' => __DIR__ . '/tests/Authentication/Authenticators/SessionAuthenticatorTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Authentication/Filters/AbstractFilterTestCase.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', 'count' => 6, 'path' => __DIR__ . '/tests/Authentication/Filters/SessionFilterTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Implicit array creation is not allowed \\- variable \\$users might not exist\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Authentication/ForcePasswordResetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Variable \\$users might not be defined\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Authentication/ForcePasswordResetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Entities\\\\\\\\AccessToken\' and CodeIgniter\\\\Shield\\\\Entities\\\\AccessToken will always evaluate to true\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Authentication/HasAccessTokensTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', 'count' => 2, @@ -101,6 +356,16 @@ 'count' => 4, 'path' => __DIR__ . '/tests/Authorization/AuthorizableTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/Language/AbstractTranslationTestCase.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertIsString\\(\\) with string will always evaluate to true\\.$#', + 'count' => 6, + 'path' => __DIR__ . '/tests/Unit/Authentication/JWT/JWTManagerTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', 'count' => 1, @@ -111,6 +376,11 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Unit/UserTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Entities\\\\\\\\UserIdentity\' and CodeIgniter\\\\Shield\\\\Entities\\\\UserIdentity will always evaluate to true\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/Unit/UserTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$active on array\\|object\\.$#', 'count' => 2, @@ -131,5 +401,10 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Unit/UserTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/tests/_support/Config/Registrar.php', +]; return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; From 3aaca031d1f3715d09fb67472673fbdedf593537 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 09:41:40 +0900 Subject: [PATCH 123/401] chore: add phpstan-strict-rules/rules.neon to phpstanConfigs() --- rector.php | 1 + 1 file changed, 1 insertion(+) diff --git a/rector.php b/rector.php index 082ce9de7..78ede225f 100644 --- a/rector.php +++ b/rector.php @@ -67,6 +67,7 @@ $rectorConfig->phpstanConfigs([ __DIR__ . '/phpstan.neon.dist', __DIR__ . '/vendor/codeigniter/phpstan-codeigniter/extension.neon', + __DIR__ . '/vendor/phpstan/phpstan-strict-rules/rules.neon', ]); // Set the target version for refactoring From 4a5b5aadc48435cafe4c10ac126c9c68b85fc007 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Sat, 16 Sep 2023 14:25:51 +0330 Subject: [PATCH 124/401] refactor: update phpstan baseline --- phpstan-baseline.php | 54 +++----------------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 4a1a11d17..2da1a018e 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1,4 +1,6 @@ - 1, 'path' => __DIR__ . '/src/Models/TokenLoginModel.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Models/TokenLoginModel.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', 'count' => 1, 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', -]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 2, @@ -296,21 +288,11 @@ 'count' => 1, 'path' => __DIR__ . '/src/Models/UserModel.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 2, - 'path' => __DIR__ . '/tests/Authentication/AuthHelperTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', 'count' => 2, 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 2, - 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', 'count' => 3, @@ -326,11 +308,6 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Authentication/Filters/AbstractFilterTestCase.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 6, - 'path' => __DIR__ . '/tests/Authentication/Filters/SessionFilterTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Implicit array creation is not allowed \\- variable \\$users might not exist\\.$#', 'count' => 1, @@ -346,16 +323,6 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Authentication/HasAccessTokensTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 2, - 'path' => __DIR__ . '/tests/Authentication/HasAccessTokensTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 4, - 'path' => __DIR__ . '/tests/Authorization/AuthorizableTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#', 'count' => 2, @@ -366,11 +333,6 @@ 'count' => 6, 'path' => __DIR__ . '/tests/Unit/Authentication/JWT/JWTManagerTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/tests/Unit/UserModelTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to an undefined method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:getLastQuery\\(\\)\\.$#', 'count' => 1, @@ -386,16 +348,6 @@ 'count' => 2, 'path' => __DIR__ . '/tests/Unit/UserTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$email on array\\|object\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/tests/Unit/UserTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$id on array\\|object\\.$#', - 'count' => 4, - 'path' => __DIR__ . '/tests/Unit/UserTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access property \\$password_hash on array\\|object\\.$#', 'count' => 1, From a63efac769a916c126dd7fbc4bb9b60f0c7e4690 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Sat, 16 Sep 2023 14:27:10 +0330 Subject: [PATCH 125/401] chore: update phpstan-codeigniter ver to 1.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cae646c7f..383ec4a9b 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "mockery/mockery": "^1.0", "firebase/php-jwt": "^6.4", "rector/rector": "0.18.3", - "codeigniter/phpstan-codeigniter": "^1.1", + "codeigniter/phpstan-codeigniter": "^1.2", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-strict-rules": "^1.5" }, From 3a03a5a169bbdcdfcfabaea4cf34ef34f3d95919 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Sat, 16 Sep 2023 14:28:08 +0330 Subject: [PATCH 126/401] refactor: fix phpstan errors --- tests/Unit/UserTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index 784800063..f2554fa62 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -209,6 +209,7 @@ public function testUpdateEmail(): void $users = model(UserModel::class); $users->save($this->user); + /** @var User $user */ $user = $users->find($this->user->id); $this->seeInDatabase($this->tables['identities'], [ @@ -250,6 +251,7 @@ public function testUpdatePasswordHash(): void $users = model(UserModel::class); $users->save($this->user); + /** @var User $user */ $user = $users->find($this->user->id); $this->seeInDatabase($this->tables['identities'], [ From 05c57b5367a7e8311b79ef55a3c313f5d4465524 Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Tue, 27 Dec 2022 09:11:50 +0100 Subject: [PATCH 127/401] Added spark user command to manage users --- src/Commands/User.php | 459 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 src/Commands/User.php diff --git a/src/Commands/User.php b/src/Commands/User.php new file mode 100644 index 000000000..e90c31e50 --- /dev/null +++ b/src/Commands/User.php @@ -0,0 +1,459 @@ +'; + + /** + * Command's Arguments + * + * @var array + */ + protected $arguments = [ + 'action' => 'Valid actions : create, activate, deactivate, changename, changeemail, delete, password, list, addgroup, removegroup', + ]; + + /** + * Command's Options + * + * @var array + */ + protected $options = [ + '-i' => 'User id', + '-u' => 'User name', + '-e' => 'User email', + '-nu' => 'New username', + '-ne' => 'New email', + '-g' => 'Group name', + ]; + + /** + * Validation rules for user fields + */ + private array $validation_rules = [ + 'username' => 'required|is_unique[users.username]', + 'email' => 'required|valid_email|is_unique[auth_identities.secret]', + 'password' => 'required|min_length[10]', + ]; + + /** + * Displays the help for the spark cli script itself. + */ + public function run(array $params): void + { + $action = CLI::getSegment(2); + if ($action && in_array($action, $this->valid_actions, true)) { + $userid = (int) CLI::getOption('i'); + $username = CLI::getOption('u'); + $email = CLI::getOption('e'); + $new_username = CLI::getOption('nu'); + $new_email = CLI::getOption('ne'); + $group = CLI::getOption('g'); + + switch ($action) { + case 'create': + $this->create($username, $email); + break; + + case 'activate': + $this->activate($username, $email); + break; + + case 'deactivate': + $this->deactivate($username, $email); + break; + + case 'changename': + $this->changename($username, $email, $new_username); + break; + + case 'changeemail': + $this->changeemail($username, $email, $new_email); + break; + + case 'delete': + $this->delete($userid, $username, $email); + break; + + case 'password': + $this->password($username, $email); + break; + + case 'list': + $this->list($username, $email); + break; + + case 'addgroup': + $this->addgroup($group, $username, $email); + break; + + case 'removegroup': + $this->removegroup($group, $username, $email); + break; + } + } + } + + /** + * Create a new user + * + * @param $username User name to create (optional) + * @param $email User email to create (optional) + */ + private function create(?string $username = null, ?string $email = null): void + { + $data = []; + + if (! $username) { + $username = CLI::prompt('Username', null, $this->validation_rules['username']); + } + $data['username'] = $username; + + if (! $email) { + $email = CLI::prompt('Email', null, $this->validation_rules['email']); + } + $data['email'] = $email; + + $password = CLI::prompt('Password', null, $this->validation_rules['password']); + $password_confirm = CLI::prompt('Password confirmation', null, $this->validation_rules['password']); + + if ($password !== $password_confirm) { + CLI::write("The passwords don't match", 'red'); + + exit; + } + $data['password'] = $password; + + // Run validation if the user has passed username and/or email via command line + $validation = Services::validation(); + $validation->setRules($this->validation_rules); + if (! $validation->run($data)) { + foreach ($validation->getErrors() as $message) { + CLI::write($message, 'red'); + } + CLI::write('User creation aborted', 'red'); + + exit; + } + + $users = model('CodeIgniter\Shield\Models\UserModel'); + $user = new \CodeIgniter\Shield\Entities\User($data); + $users->save($user); + CLI::write('User ' . $username . ' created', 'green'); + } + + /** + * Activate an existing user by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function activate(?string $username = null, ?string $email = null): void + { + $user = $this->findUser('Activate user', $username, $email); + $confirm = CLI::prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']); + if ($confirm === 'y') { + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $user->active = 1; + $userModel->save($user); + CLI::write('User ' . $user->username . ' activated', 'green'); + } else { + CLI::write('User ' . $user->username . ' activation cancelled', 'yellow'); + } + } + + /** + * Deactivate an existing user by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function deactivate(?string $username = null, ?string $email = null): void + { + $user = $this->findUser('Deactivate user', $username, $email); + $confirm = CLI::prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); + if ($confirm === 'y') { + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $user->active = 0; + $userModel->save($user); + CLI::write('User ' . $user->username . ' deactivated', 'green'); + } else { + CLI::write('User ' . $user->username . ' deactivation cancelled', 'yellow'); + } + } + + /** + * Change the name of an existing user by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + * @param $new_username User new name (optional) + */ + private function changename(?string $username = null, ?string $email = null, ?string $new_username = null): void + { + $validation = Services::validation(); + $validation->setRules([ + 'username' => 'required|is_unique[users.username]', + ]); + + $user = $this->findUser('Change username', $username, $email); + + if (! $new_username) { + $new_username = CLI::prompt('New username', null, $this->validation_rules['username']); + } else { + // Run validation if the user has passed username and/or email via command line + $validation = Services::validation(); + $validation->setRules([ + 'username' => $this->validation_rules['username'], + ]); + if (! $validation->run(['username' => $new_username])) { + foreach ($validation->getErrors() as $message) { + CLI::write($message, 'red'); + } + CLI::write('User name change aborted', 'red'); + + exit; + } + } + + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $old_username = $user->username; + $user->username = $new_username; + $userModel->save($user); + CLI::write('Username ' . $old_username . ' changed to ' . $new_username, 'green'); + } + + /** + * Change the email of an existing user by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + * @param $new_email User new email (optional) + */ + private function changeemail(?string $username = null, ?string $email = null, ?string $new_email = null): void + { + $user = $this->findUser('Change email', $username, $email); + + if (! $new_email) { + $new_email = CLI::prompt('New email', null, $this->validation_rules['email']); + } else { + // Run validation if the user has passed username and/or email via command line + $validation = Services::validation(); + $validation->setRules([ + 'email' => $this->validation_rules['email'], + ]); + if (! $validation->run(['email' => $new_email])) { + foreach ($validation->getErrors() as $message) { + CLI::write($message, 'red'); + } + CLI::write('User email change aborted', 'red'); + + exit; + } + } + + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $user->email = $new_email; + $userModel->save($user); + CLI::write('Email for the user : ' . $user->username . ' changed to ' . $new_email, 'green'); + } + + /** + * Delete an existing user by username or email + * + * @param $userid User id to delete (optional) + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void + { + if ($userid) { + $user = model('CodeIgniter\Shield\Models\UserModel')->findById($userid); + if (! $user) { + CLI::write("User doesn't exist", 'red'); + + exit; + } + } else { + $user = $this->findUser('Delete user', $username, $email); + } + + $confirm = CLI::prompt('Delete the user ' . $user->username . ' (' . $user->email . ') ?', ['y', 'n']); + if ($confirm === 'y') { + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel->delete($user->id, true); + CLI::write('User ' . $user->username . ' deleted', 'green'); + } else { + CLI::write('User ' . $user->username . ' deletion cancelled', 'yellow'); + } + } + + /** + * Change the password of an existing user by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function password($username = null, $email = null): void + { + $user = $this->findUser('Change user password', $username, $email); + + $confirm = CLI::prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); + if ($confirm === 'y') { + $password = CLI::prompt('Password', null, 'required'); + $password_confirm = CLI::prompt('Password confirmation', null, $this->validation_rules['password']); + + if ($password !== $password_confirm) { + CLI::write("The passwords don't match", 'red'); + + exit; + } + + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $user->password = $password; + $userModel->save($user); + + CLI::write('Password for the user ' . $user->username . ' set', 'green'); + } else { + CLI::write('Password setting for the user : ' . $user->username . ', cancelled', 'yellow'); + } + } + + /** + * List users searching by username or email + * + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function list($username = null, $email = null): void + { + $users = model('CodeIgniter\Shield\Models\UserModel'); + $users = $users->join('auth_identities', 'auth_identities.user_id = users.id'); + if ($username) { + $users = $users->like('username', $username); + } + if ($email) { + $users = $users->like('secret', $email); + } + + CLI::write("Id\tUser"); + + foreach ($users->findAll() as $user) { + CLI::write($user->id . "\t" . $user->username . ' (' . $user->secret . ')'); + } + } + + /** + * Add a user by username or email to a group + * + * @param $group Group to add user to + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function addgroup(string $group, $username = null, $email = null): void + { + $user = $this->findUser('Add user to group', $username, $email); + + $confirm = CLI::prompt('Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', ['y', 'n']); + if ($confirm === 'y') { + $user->addGroup($group); + CLI::write('User ' . $user->username . ' added to group ' . $group, 'green'); + } else { + CLI::write('Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', 'yellow'); + } + } + + /** + * Remove a user by username or email from a group + * + * @param $group Group to remove user from + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function removegroup(string $group, $username = null, $email = null): void + { + $user = $this->findUser('Remove user from group', $username, $email); + + $confirm = CLI::prompt('Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', ['y', 'n']); + if ($confirm === 'y') { + $user->removeGroup($group); + CLI::write('User ' . $user->username . ' removed from group ' . $group, 'green'); + } else { + CLI::write('Removal of the user: ' . $user->username . ' from the group: ' . $group . ' cancelled', 'yellow'); + } + } + + /** + * Find an existing user by username or email. Exit if a user is not found + * + * @param $question Initial question at user prompt + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) + */ + private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User + { + if (! $username && ! $email) { + $choice = CLI::prompt($question . ' by username or email ?', ['u', 'e']); + if ($choice === 'u') { + $username = CLI::prompt('Username', null, 'required'); + } elseif ($choice === 'e') { + $email = CLI::prompt('Email', null, 'required|valid_email'); + } + } + + $user = new \CodeIgniter\Shield\Entities\User(); + $users = model('CodeIgniter\Shield\Models\UserModel'); + $users = $users->join('auth_identities', 'auth_identities.user_id = users.id'); + + if ($username) { + $user = $users->where('username', $username)->first(); + } elseif ($email) { + $user = $users->where('secret', $email)->first(); + } + + if (! $user) { + CLI::write("User doesn't exist", 'red'); + + exit; + } + + return $user; + } +} From 744758a080a873941b9fbc0485a803f190b6ff84 Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Tue, 27 Dec 2022 17:30:07 +0100 Subject: [PATCH 128/401] Added warning on call without action and group option for group related actions --- src/Commands/User.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index e90c31e50..d97129d75 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -121,14 +121,27 @@ public function run(array $params): void break; case 'addgroup': - $this->addgroup($group, $username, $email); + if(!$group) { + CLI::write('Group option is missing', 'red'); + } + else { + $this->addgroup($group, $username, $email); + } break; case 'removegroup': - $this->removegroup($group, $username, $email); + if(!$group) { + CLI::write('Group option is missing', 'red'); + } + else { + $this->removegroup($group, $username, $email); + } break; } } + else { + CLI::write('Specify a valid action : ' . implode(',', $this->valid_actions), 'red'); + } } /** From 0e0c64dd4dfdd95d146ee1bc757df3490585390e Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Mon, 2 Jan 2023 09:57:59 +0100 Subject: [PATCH 129/401] Ask for group if not specified in group related actions --- src/Commands/User.php | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index d97129d75..855173ddd 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -121,25 +121,14 @@ public function run(array $params): void break; case 'addgroup': - if(!$group) { - CLI::write('Group option is missing', 'red'); - } - else { - $this->addgroup($group, $username, $email); - } + $this->addgroup($group, $username, $email); break; case 'removegroup': - if(!$group) { - CLI::write('Group option is missing', 'red'); - } - else { - $this->removegroup($group, $username, $email); - } + $this->removegroup($group, $username, $email); break; } - } - else { + } else { CLI::write('Specify a valid action : ' . implode(',', $this->valid_actions), 'red'); } } @@ -400,8 +389,12 @@ private function list($username = null, $email = null): void * @param $username User name to search for (optional) * @param $email User email to search for (optional) */ - private function addgroup(string $group, $username = null, $email = null): void + private function addgroup($group = null, $username = null, $email = null): void { + if (! $group) { + $group = CLI::prompt('Group', null, 'required'); + } + $user = $this->findUser('Add user to group', $username, $email); $confirm = CLI::prompt('Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', ['y', 'n']); @@ -420,8 +413,12 @@ private function addgroup(string $group, $username = null, $email = null): void * @param $username User name to search for (optional) * @param $email User email to search for (optional) */ - private function removegroup(string $group, $username = null, $email = null): void + private function removegroup($group = null, $username = null, $email = null): void { + if (! $group) { + $group = CLI::prompt('Group', null, 'required'); + } + $user = $this->findUser('Remove user from group', $username, $email); $confirm = CLI::prompt('Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', ['y', 'n']); From 438c8686a51f4c8714859264c21c90ff7463c30a Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Thu, 5 Jan 2023 10:17:40 +0100 Subject: [PATCH 130/401] Changed the options for passing new username (-n) and new email (-m) --- src/Commands/User.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 855173ddd..72926cf37 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -56,12 +56,12 @@ class User extends BaseCommand * @var array */ protected $options = [ - '-i' => 'User id', - '-u' => 'User name', - '-e' => 'User email', - '-nu' => 'New username', - '-ne' => 'New email', - '-g' => 'Group name', + '-i' => 'User id', + '-u' => 'User name', + '-e' => 'User email', + '-n' => 'New username', + '-m' => 'New email', + '-g' => 'Group name', ]; /** @@ -83,8 +83,8 @@ public function run(array $params): void $userid = (int) CLI::getOption('i'); $username = CLI::getOption('u'); $email = CLI::getOption('e'); - $new_username = CLI::getOption('nu'); - $new_email = CLI::getOption('ne'); + $new_username = CLI::getOption('n'); + $new_email = CLI::getOption('m'); $group = CLI::getOption('g'); switch ($action) { @@ -364,7 +364,7 @@ private function password($username = null, $email = null): void * @param $username User name to search for (optional) * @param $email User email to search for (optional) */ - private function list($username = null, $email = null): void + private function list(?string $username = null, ?string $email = null): void { $users = model('CodeIgniter\Shield\Models\UserModel'); $users = $users->join('auth_identities', 'auth_identities.user_id = users.id'); From 8504e0b46a9b16f36acacb805ac854f7cfe08f6c Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Fri, 31 Mar 2023 14:50:31 +0200 Subject: [PATCH 131/401] Improved readability of model loading --- src/Commands/User.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 72926cf37..dd3816a8b 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -175,9 +175,9 @@ private function create(?string $username = null, ?string $email = null): void exit; } - $users = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model('CodeIgniter\Shield\Models\UserModel'); $user = new \CodeIgniter\Shield\Entities\User($data); - $users->save($user); + $userModel->save($user); CLI::write('User ' . $username . ' created', 'green'); } @@ -306,8 +306,9 @@ private function changeemail(?string $username = null, ?string $email = null, ?s */ private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void { + $userModel = model('CodeIgniter\Shield\Models\UserModel'); if ($userid) { - $user = model('CodeIgniter\Shield\Models\UserModel')->findById($userid); + $user = $userModel->findById($userid); if (! $user) { CLI::write("User doesn't exist", 'red'); @@ -319,7 +320,6 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai $confirm = CLI::prompt('Delete the user ' . $user->username . ' (' . $user->email . ') ?', ['y', 'n']); if ($confirm === 'y') { - $userModel = model('CodeIgniter\Shield\Models\UserModel'); $userModel->delete($user->id, true); CLI::write('User ' . $user->username . ' deleted', 'green'); } else { @@ -366,8 +366,8 @@ private function password($username = null, $email = null): void */ private function list(?string $username = null, ?string $email = null): void { - $users = model('CodeIgniter\Shield\Models\UserModel'); - $users = $users->join('auth_identities', 'auth_identities.user_id = users.id'); + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); if ($username) { $users = $users->like('username', $username); } @@ -449,8 +449,8 @@ private function findUser($question = '', $username = null, $email = null): \Cod } $user = new \CodeIgniter\Shield\Entities\User(); - $users = model('CodeIgniter\Shield\Models\UserModel'); - $users = $users->join('auth_identities', 'auth_identities.user_id = users.id'); + $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); if ($username) { $user = $users->where('username', $username)->first(); From 9793acffdbb0c189c4da1803ca9d2fb598abe824 Mon Sep 17 00:00:00 2001 From: "roberto.gerola" Date: Fri, 31 Mar 2023 14:51:23 +0200 Subject: [PATCH 132/401] Improved readability of model loading --- src/Commands/User.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index dd3816a8b..112382f14 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -176,7 +176,7 @@ private function create(?string $username = null, ?string $email = null): void } $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $user = new \CodeIgniter\Shield\Entities\User($data); + $user = new \CodeIgniter\Shield\Entities\User($data); $userModel->save($user); CLI::write('User ' . $username . ' created', 'green'); } @@ -367,7 +367,7 @@ private function password($username = null, $email = null): void private function list(?string $username = null, ?string $email = null): void { $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); if ($username) { $users = $users->like('username', $username); } @@ -448,9 +448,9 @@ private function findUser($question = '', $username = null, $email = null): \Cod } } - $user = new \CodeIgniter\Shield\Entities\User(); + $user = new \CodeIgniter\Shield\Entities\User(); $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); if ($username) { $user = $users->where('username', $username)->first(); From 207721b6ea2e25522004917f8c89d334a0f8baef Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 13:47:09 +0900 Subject: [PATCH 133/401] docs: composer cs-fix --- src/Commands/User.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 112382f14..a5fda9211 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -137,7 +137,7 @@ public function run(array $params): void * Create a new user * * @param $username User name to create (optional) - * @param $email User email to create (optional) + * @param $email User email to create (optional) */ private function create(?string $username = null, ?string $email = null): void { @@ -185,7 +185,7 @@ private function create(?string $username = null, ?string $email = null): void * Activate an existing user by username or email * * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function activate(?string $username = null, ?string $email = null): void { @@ -205,7 +205,7 @@ private function activate(?string $username = null, ?string $email = null): void * Deactivate an existing user by username or email * * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function deactivate(?string $username = null, ?string $email = null): void { @@ -224,8 +224,8 @@ private function deactivate(?string $username = null, ?string $email = null): vo /** * Change the name of an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) * @param $new_username User new name (optional) */ private function changename(?string $username = null, ?string $email = null, ?string $new_username = null): void @@ -265,8 +265,8 @@ private function changename(?string $username = null, ?string $email = null, ?st /** * Change the email of an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $username User name to search for (optional) + * @param $email User email to search for (optional) * @param $new_email User new email (optional) */ private function changeemail(?string $username = null, ?string $email = null, ?string $new_email = null): void @@ -300,9 +300,9 @@ private function changeemail(?string $username = null, ?string $email = null, ?s /** * Delete an existing user by username or email * - * @param $userid User id to delete (optional) + * @param $userid User id to delete (optional) * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void { @@ -331,7 +331,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai * Change the password of an existing user by username or email * * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function password($username = null, $email = null): void { @@ -362,7 +362,7 @@ private function password($username = null, $email = null): void * List users searching by username or email * * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function list(?string $username = null, ?string $email = null): void { @@ -385,9 +385,9 @@ private function list(?string $username = null, ?string $email = null): void /** * Add a user by username or email to a group * - * @param $group Group to add user to + * @param $group Group to add user to * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function addgroup($group = null, $username = null, $email = null): void { @@ -409,9 +409,9 @@ private function addgroup($group = null, $username = null, $email = null): void /** * Remove a user by username or email from a group * - * @param $group Group to remove user from + * @param $group Group to remove user from * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function removegroup($group = null, $username = null, $email = null): void { @@ -435,7 +435,7 @@ private function removegroup($group = null, $username = null, $email = null): vo * * @param $question Initial question at user prompt * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param $email User email to search for (optional) */ private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User { From feca2ee7a4c84982315ae0c2dfdecef71dcfc4c1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 13:56:21 +0900 Subject: [PATCH 134/401] docs: fix @param --- src/Commands/User.php | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index a5fda9211..93f920c9a 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -136,8 +136,8 @@ public function run(array $params): void /** * Create a new user * - * @param $username User name to create (optional) - * @param $email User email to create (optional) + * @param string|null $username User name to create (optional) + * @param string|null $email User email to create (optional) */ private function create(?string $username = null, ?string $email = null): void { @@ -184,8 +184,8 @@ private function create(?string $username = null, ?string $email = null): void /** * Activate an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function activate(?string $username = null, ?string $email = null): void { @@ -204,8 +204,8 @@ private function activate(?string $username = null, ?string $email = null): void /** * Deactivate an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function deactivate(?string $username = null, ?string $email = null): void { @@ -224,9 +224,9 @@ private function deactivate(?string $username = null, ?string $email = null): vo /** * Change the name of an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) - * @param $new_username User new name (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) + * @param string|null $new_username User new name (optional) */ private function changename(?string $username = null, ?string $email = null, ?string $new_username = null): void { @@ -265,9 +265,9 @@ private function changename(?string $username = null, ?string $email = null, ?st /** * Change the email of an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) - * @param $new_email User new email (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) + * @param string|null $new_email User new email (optional) */ private function changeemail(?string $username = null, ?string $email = null, ?string $new_email = null): void { @@ -300,9 +300,9 @@ private function changeemail(?string $username = null, ?string $email = null, ?s /** * Delete an existing user by username or email * - * @param $userid User id to delete (optional) - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param int $userid User id to delete (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void { @@ -330,8 +330,8 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai /** * Change the password of an existing user by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function password($username = null, $email = null): void { @@ -361,8 +361,8 @@ private function password($username = null, $email = null): void /** * List users searching by username or email * - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function list(?string $username = null, ?string $email = null): void { @@ -385,9 +385,9 @@ private function list(?string $username = null, ?string $email = null): void /** * Add a user by username or email to a group * - * @param $group Group to add user to - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $group Group to add user to + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function addgroup($group = null, $username = null, $email = null): void { @@ -409,9 +409,9 @@ private function addgroup($group = null, $username = null, $email = null): void /** * Remove a user by username or email from a group * - * @param $group Group to remove user from - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string|null $group Group to remove user from + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function removegroup($group = null, $username = null, $email = null): void { @@ -433,9 +433,9 @@ private function removegroup($group = null, $username = null, $email = null): vo /** * Find an existing user by username or email. Exit if a user is not found * - * @param $question Initial question at user prompt - * @param $username User name to search for (optional) - * @param $email User email to search for (optional) + * @param string $question Initial question at user prompt + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) */ private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User { From b4f653c9140a61da94b0af8b6a186ac07a1d88cb Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 13:57:00 +0900 Subject: [PATCH 135/401] style: add line breaks --- src/Commands/User.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 93f920c9a..c3d13de9e 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -10,7 +10,10 @@ class User extends BaseCommand { - private array $valid_actions = ['create', 'activate', 'deactivate', 'changename', 'changeemail', 'delete', 'password', 'list', 'addgroup', 'removegroup']; + private array $valid_actions = [ + 'create', 'activate', 'deactivate', 'changename', 'changeemail', + 'delete', 'password', 'list', 'addgroup', 'removegroup', + ]; /** * The group the command is lumped under @@ -47,7 +50,7 @@ class User extends BaseCommand * @var array */ protected $arguments = [ - 'action' => 'Valid actions : create, activate, deactivate, changename, changeemail, delete, password, list, addgroup, removegroup', + 'action' => 'Valid actions: create, activate, deactivate, changename, changeemail, delete, password, list, addgroup, removegroup', ]; /** @@ -79,6 +82,7 @@ class User extends BaseCommand public function run(array $params): void { $action = CLI::getSegment(2); + if ($action && in_array($action, $this->valid_actions, true)) { $userid = (int) CLI::getOption('i'); $username = CLI::getOption('u'); From c187eb15d4188fbc9602aefc5dc6eb47dc3f07b7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 14:48:46 +0900 Subject: [PATCH 136/401] feat: improve help message and change options --- src/Commands/User.php | 66 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index c3d13de9e..258834fe9 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -42,7 +42,39 @@ class User extends BaseCommand * * @var string */ - protected $usage = 'shield:user '; + protected $usage = <<<'EOL' + shield:user options + + shield:user create -n newusername -e newuser@example.com + + shield:user activate -n username + shield:user activate -e user@example.com + + shield:user deactivate -n username + shield:user deactivate -e user@example.com + + shield:user changename -n username --new-name newusername + shield:user changename -e user@example.com --new-name newusername + + shield:user changeemail -n username --new-email newuseremail@example.com + shield:user changeemail -e user@example.com --new-email newuseremail@example.com + + shield:user delete -i 123 + shield:user delete -n username + shield:user delete -e user@example.com + + shield:user password -n username + shield:user password -e user@example.com + + shield:user list + shield:user list -n username -e user@example.com + + shield:user addgroup -n username -g mygroup + shield:user addgroup -e user@example.com -g mygroup + + shield:user removegroup -n username -g mygroup + shield:user removegroup -e user@example.com -g mygroup + EOL; /** * Command's Arguments @@ -50,7 +82,19 @@ class User extends BaseCommand * @var array */ protected $arguments = [ - 'action' => 'Valid actions: create, activate, deactivate, changename, changeemail, delete, password, list, addgroup, removegroup', + 'action' => <<<'EOL' + + create: Create a new user + activate: Activate a user + deactivate: Deactivate a user + changename: Change user name + changeemail: Change user email + delete: Delete a user + password: Change a user password + list: List users + addgroup: Add a user to a group + removegroup: Remove a user from a group + EOL, ]; /** @@ -59,12 +103,12 @@ class User extends BaseCommand * @var array */ protected $options = [ - '-i' => 'User id', - '-u' => 'User name', - '-e' => 'User email', - '-n' => 'New username', - '-m' => 'New email', - '-g' => 'Group name', + '-i' => 'User id', + '-n' => 'User name', + '-e' => 'User email', + '--new-name' => 'New username', + '--new-email' => 'New email', + '-g' => 'Group name', ]; /** @@ -85,10 +129,10 @@ public function run(array $params): void if ($action && in_array($action, $this->valid_actions, true)) { $userid = (int) CLI::getOption('i'); - $username = CLI::getOption('u'); + $username = CLI::getOption('n'); $email = CLI::getOption('e'); - $new_username = CLI::getOption('n'); - $new_email = CLI::getOption('m'); + $new_username = CLI::getOption('new-name'); + $new_email = CLI::getOption('new-email'); $group = CLI::getOption('g'); switch ($action) { From 5f32cba07be9095e5e2c454a92b5ece80f7e12ac Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 15:01:43 +0900 Subject: [PATCH 137/401] refactor: use ::class for model() --- src/Commands/User.php | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 258834fe9..2eb14a324 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -6,6 +6,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\Shield\Models\UserModel; use Config\Services; class User extends BaseCommand @@ -223,9 +224,11 @@ private function create(?string $username = null, ?string $email = null): void exit; } - $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $user = new \CodeIgniter\Shield\Entities\User($data); + $userModel = model(UserModel::class); + + $user = new \CodeIgniter\Shield\Entities\User($data); $userModel->save($user); + CLI::write('User ' . $username . ' created', 'green'); } @@ -240,9 +243,11 @@ private function activate(?string $username = null, ?string $email = null): void $user = $this->findUser('Activate user', $username, $email); $confirm = CLI::prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']); if ($confirm === 'y') { - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + $user->active = 1; $userModel->save($user); + CLI::write('User ' . $user->username . ' activated', 'green'); } else { CLI::write('User ' . $user->username . ' activation cancelled', 'yellow'); @@ -260,9 +265,11 @@ private function deactivate(?string $username = null, ?string $email = null): vo $user = $this->findUser('Deactivate user', $username, $email); $confirm = CLI::prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); if ($confirm === 'y') { - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + $user->active = 0; $userModel->save($user); + CLI::write('User ' . $user->username . ' deactivated', 'green'); } else { CLI::write('User ' . $user->username . ' deactivation cancelled', 'yellow'); @@ -303,10 +310,12 @@ private function changename(?string $username = null, ?string $email = null, ?st } } - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + $old_username = $user->username; $user->username = $new_username; $userModel->save($user); + CLI::write('Username ' . $old_username . ' changed to ' . $new_username, 'green'); } @@ -339,9 +348,11 @@ private function changeemail(?string $username = null, ?string $email = null, ?s } } - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + $user->email = $new_email; $userModel->save($user); + CLI::write('Email for the user : ' . $user->username . ' changed to ' . $new_email, 'green'); } @@ -354,7 +365,8 @@ private function changeemail(?string $username = null, ?string $email = null, ?s */ private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void { - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + if ($userid) { $user = $userModel->findById($userid); if (! $user) { @@ -396,7 +408,8 @@ private function password($username = null, $email = null): void exit; } - $userModel = model('CodeIgniter\Shield\Models\UserModel'); + $userModel = model(UserModel::class); + $user->password = $password; $userModel->save($user); @@ -414,8 +427,10 @@ private function password($username = null, $email = null): void */ private function list(?string $username = null, ?string $email = null): void { - $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + $userModel = model(UserModel::class); + + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + if ($username) { $users = $users->like('username', $username); } @@ -496,9 +511,11 @@ private function findUser($question = '', $username = null, $email = null): \Cod } } - $user = new \CodeIgniter\Shield\Entities\User(); - $userModel = model('CodeIgniter\Shield\Models\UserModel'); - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + $userModel = model(UserModel::class); + + $user = new \CodeIgniter\Shield\Entities\User(); + + $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); if ($username) { $user = $users->where('username', $username)->first(); From 010c235509841b93c4f2286bd6a56d58f444b1eb Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 15:13:01 +0900 Subject: [PATCH 138/401] fix: list command shows token secrets --- src/Commands/User.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 2eb14a324..4069b2b4a 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -6,6 +6,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Models\UserModel; use Config\Services; @@ -429,7 +430,9 @@ private function list(?string $username = null, ?string $email = null): void { $userModel = model(UserModel::class); - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); + $users = $userModel + ->join('auth_identities', 'auth_identities.user_id = users.id') + ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD); if ($username) { $users = $users->like('username', $username); From f6a451c91b02b4d81660807ebf345d4c06dfb5bd Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 15:24:03 +0900 Subject: [PATCH 139/401] refactor: use camelCase for variable names --- src/Commands/User.php | 84 +++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 4069b2b4a..8cceceee7 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -12,7 +12,7 @@ class User extends BaseCommand { - private array $valid_actions = [ + private array $validActions = [ 'create', 'activate', 'deactivate', 'changename', 'changeemail', 'delete', 'password', 'list', 'addgroup', 'removegroup', ]; @@ -116,7 +116,7 @@ class User extends BaseCommand /** * Validation rules for user fields */ - private array $validation_rules = [ + private array $validationRules = [ 'username' => 'required|is_unique[users.username]', 'email' => 'required|valid_email|is_unique[auth_identities.secret]', 'password' => 'required|min_length[10]', @@ -129,13 +129,13 @@ public function run(array $params): void { $action = CLI::getSegment(2); - if ($action && in_array($action, $this->valid_actions, true)) { - $userid = (int) CLI::getOption('i'); - $username = CLI::getOption('n'); - $email = CLI::getOption('e'); - $new_username = CLI::getOption('new-name'); - $new_email = CLI::getOption('new-email'); - $group = CLI::getOption('g'); + if ($action && in_array($action, $this->validActions, true)) { + $userid = (int) CLI::getOption('i'); + $username = CLI::getOption('n'); + $email = CLI::getOption('e'); + $newUsername = CLI::getOption('new-name'); + $newEmail = CLI::getOption('new-email'); + $group = CLI::getOption('g'); switch ($action) { case 'create': @@ -151,11 +151,11 @@ public function run(array $params): void break; case 'changename': - $this->changename($username, $email, $new_username); + $this->changename($username, $email, $newUsername); break; case 'changeemail': - $this->changeemail($username, $email, $new_email); + $this->changeemail($username, $email, $newEmail); break; case 'delete': @@ -179,7 +179,7 @@ public function run(array $params): void break; } } else { - CLI::write('Specify a valid action : ' . implode(',', $this->valid_actions), 'red'); + CLI::write('Specify a valid action : ' . implode(',', $this->validActions), 'red'); } } @@ -194,19 +194,19 @@ private function create(?string $username = null, ?string $email = null): void $data = []; if (! $username) { - $username = CLI::prompt('Username', null, $this->validation_rules['username']); + $username = CLI::prompt('Username', null, $this->validationRules['username']); } $data['username'] = $username; if (! $email) { - $email = CLI::prompt('Email', null, $this->validation_rules['email']); + $email = CLI::prompt('Email', null, $this->validationRules['email']); } $data['email'] = $email; - $password = CLI::prompt('Password', null, $this->validation_rules['password']); - $password_confirm = CLI::prompt('Password confirmation', null, $this->validation_rules['password']); + $password = CLI::prompt('Password', null, $this->validationRules['password']); + $passwordConfirm = CLI::prompt('Password confirmation', null, $this->validationRules['password']); - if ($password !== $password_confirm) { + if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); exit; @@ -215,7 +215,7 @@ private function create(?string $username = null, ?string $email = null): void // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); - $validation->setRules($this->validation_rules); + $validation->setRules($this->validationRules); if (! $validation->run($data)) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); @@ -280,11 +280,11 @@ private function deactivate(?string $username = null, ?string $email = null): vo /** * Change the name of an existing user by username or email * - * @param string|null $username User name to search for (optional) - * @param string|null $email User email to search for (optional) - * @param string|null $new_username User new name (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) + * @param string|null $newUsername User new name (optional) */ - private function changename(?string $username = null, ?string $email = null, ?string $new_username = null): void + private function changename(?string $username = null, ?string $email = null, ?string $newUsername = null): void { $validation = Services::validation(); $validation->setRules([ @@ -293,15 +293,15 @@ private function changename(?string $username = null, ?string $email = null, ?st $user = $this->findUser('Change username', $username, $email); - if (! $new_username) { - $new_username = CLI::prompt('New username', null, $this->validation_rules['username']); + if (! $newUsername) { + $newUsername = CLI::prompt('New username', null, $this->validationRules['username']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); $validation->setRules([ - 'username' => $this->validation_rules['username'], + 'username' => $this->validationRules['username'], ]); - if (! $validation->run(['username' => $new_username])) { + if (! $validation->run(['username' => $newUsername])) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); } @@ -313,33 +313,33 @@ private function changename(?string $username = null, ?string $email = null, ?st $userModel = model(UserModel::class); - $old_username = $user->username; - $user->username = $new_username; + $oldUsername = $user->username; + $user->username = $newUsername; $userModel->save($user); - CLI::write('Username ' . $old_username . ' changed to ' . $new_username, 'green'); + CLI::write('Username ' . $oldUsername . ' changed to ' . $newUsername, 'green'); } /** * Change the email of an existing user by username or email * - * @param string|null $username User name to search for (optional) - * @param string|null $email User email to search for (optional) - * @param string|null $new_email User new email (optional) + * @param string|null $username User name to search for (optional) + * @param string|null $email User email to search for (optional) + * @param string|null $newEmail User new email (optional) */ - private function changeemail(?string $username = null, ?string $email = null, ?string $new_email = null): void + private function changeemail(?string $username = null, ?string $email = null, ?string $newEmail = null): void { $user = $this->findUser('Change email', $username, $email); - if (! $new_email) { - $new_email = CLI::prompt('New email', null, $this->validation_rules['email']); + if (! $newEmail) { + $newEmail = CLI::prompt('New email', null, $this->validationRules['email']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); $validation->setRules([ - 'email' => $this->validation_rules['email'], + 'email' => $this->validationRules['email'], ]); - if (! $validation->run(['email' => $new_email])) { + if (! $validation->run(['email' => $newEmail])) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); } @@ -351,10 +351,10 @@ private function changeemail(?string $username = null, ?string $email = null, ?s $userModel = model(UserModel::class); - $user->email = $new_email; + $user->email = $newEmail; $userModel->save($user); - CLI::write('Email for the user : ' . $user->username . ' changed to ' . $new_email, 'green'); + CLI::write('Email for the user : ' . $user->username . ' changed to ' . $newEmail, 'green'); } /** @@ -400,10 +400,10 @@ private function password($username = null, $email = null): void $confirm = CLI::prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); if ($confirm === 'y') { - $password = CLI::prompt('Password', null, 'required'); - $password_confirm = CLI::prompt('Password confirmation', null, $this->validation_rules['password']); + $password = CLI::prompt('Password', null, 'required'); + $passwordConfirm = CLI::prompt('Password confirmation', null, $this->validationRules['password']); - if ($password !== $password_confirm) { + if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); exit; From 1ea6d4dd4ec3541446cd74fc9c2cabf5f49919c9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 14 Sep 2023 15:41:03 +0900 Subject: [PATCH 140/401] feat: remove space in message --- src/Commands/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 8cceceee7..45e604506 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -179,7 +179,7 @@ public function run(array $params): void break; } } else { - CLI::write('Specify a valid action : ' . implode(',', $this->validActions), 'red'); + CLI::write('Specify a valid action: ' . implode(',', $this->validActions), 'red'); } } From 3e99226f892e6e708cfc8e74de8ebdf3872b5ab0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 10:58:21 +0900 Subject: [PATCH 141/401] refactor: remove uneeded join() --- src/Commands/User.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 45e604506..a2195483a 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -6,7 +6,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Models\UserModel; use Config\Services; @@ -430,21 +429,17 @@ private function list(?string $username = null, ?string $email = null): void { $userModel = model(UserModel::class); - $users = $userModel - ->join('auth_identities', 'auth_identities.user_id = users.id') - ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD); - if ($username) { - $users = $users->like('username', $username); + $userModel->like('username', $username); } if ($email) { - $users = $users->like('secret', $email); + $userModel->like('secret', $email); } CLI::write("Id\tUser"); - foreach ($users->findAll() as $user) { - CLI::write($user->id . "\t" . $user->username . ' (' . $user->secret . ')'); + foreach ($userModel->findAll() as $user) { + CLI::write($user->id . "\t" . $user->username . ' (' . $user->email . ')'); } } From a0503669435fbbf20f23dce198314bcf6815d7b7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 11:39:51 +0900 Subject: [PATCH 142/401] refactor: use $params CLI::getOption() cannot use in testing now. --- src/Commands/User.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index a2195483a..834d65883 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -126,15 +126,15 @@ class User extends BaseCommand */ public function run(array $params): void { - $action = CLI::getSegment(2); + $action = $params[0] ?? null; if ($action && in_array($action, $this->validActions, true)) { - $userid = (int) CLI::getOption('i'); - $username = CLI::getOption('n'); - $email = CLI::getOption('e'); - $newUsername = CLI::getOption('new-name'); - $newEmail = CLI::getOption('new-email'); - $group = CLI::getOption('g'); + $userid = (int) ($params['i'] ?? 0); + $username = $params['n'] ?? null; + $email = $params['e'] ?? null; + $newUsername = $params['new-name'] ?? null; + $newEmail = $params['new-email'] ?? null; + $group = $params['g'] ?? null; switch ($action) { case 'create': From 4131da02018b392d1e835aef3361141c5c882f85 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 12:46:31 +0900 Subject: [PATCH 143/401] refactor: use early return and returns int as EXIT code --- src/Commands/User.php | 111 ++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 834d65883..bdd9cb8a4 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -124,62 +124,69 @@ class User extends BaseCommand /** * Displays the help for the spark cli script itself. */ - public function run(array $params): void + public function run(array $params): int { $action = $params[0] ?? null; - if ($action && in_array($action, $this->validActions, true)) { - $userid = (int) ($params['i'] ?? 0); - $username = $params['n'] ?? null; - $email = $params['e'] ?? null; - $newUsername = $params['new-name'] ?? null; - $newEmail = $params['new-email'] ?? null; - $group = $params['g'] ?? null; - - switch ($action) { - case 'create': - $this->create($username, $email); - break; - - case 'activate': - $this->activate($username, $email); - break; - - case 'deactivate': - $this->deactivate($username, $email); - break; - - case 'changename': - $this->changename($username, $email, $newUsername); - break; - - case 'changeemail': - $this->changeemail($username, $email, $newEmail); - break; - - case 'delete': - $this->delete($userid, $username, $email); - break; - - case 'password': - $this->password($username, $email); - break; - - case 'list': - $this->list($username, $email); - break; - - case 'addgroup': - $this->addgroup($group, $username, $email); - break; - - case 'removegroup': - $this->removegroup($group, $username, $email); - break; - } - } else { - CLI::write('Specify a valid action: ' . implode(',', $this->validActions), 'red'); + if ($action === null || ! in_array($action, $this->validActions, true)) { + CLI::write( + 'Specify a valid action: ' . implode(',', $this->validActions), + 'red' + ); + + return EXIT_ERROR; } + + $userid = (int) ($params['i'] ?? 0); + $username = $params['n'] ?? null; + $email = $params['e'] ?? null; + $newUsername = $params['new-name'] ?? null; + $newEmail = $params['new-email'] ?? null; + $group = $params['g'] ?? null; + + switch ($action) { + case 'create': + $this->create($username, $email); + break; + + case 'activate': + $this->activate($username, $email); + break; + + case 'deactivate': + $this->deactivate($username, $email); + break; + + case 'changename': + $this->changename($username, $email, $newUsername); + break; + + case 'changeemail': + $this->changeemail($username, $email, $newEmail); + break; + + case 'delete': + $this->delete($userid, $username, $email); + break; + + case 'password': + $this->password($username, $email); + break; + + case 'list': + $this->list($username, $email); + break; + + case 'addgroup': + $this->addgroup($group, $username, $email); + break; + + case 'removegroup': + $this->removegroup($group, $username, $email); + break; + } + + return EXIT_SUCCESS; } /** From 8eca6981228900541be2ee61ed442cd37e48ac24 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 12:50:56 +0900 Subject: [PATCH 144/401] refactor: replace exit() with RuntimeException --- src/Commands/User.php | 88 +++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index bdd9cb8a4..3a9373e7e 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -6,6 +6,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\Shield\Exceptions\RuntimeException; use CodeIgniter\Shield\Models\UserModel; use Config\Services; @@ -144,46 +145,50 @@ public function run(array $params): int $newEmail = $params['new-email'] ?? null; $group = $params['g'] ?? null; - switch ($action) { - case 'create': - $this->create($username, $email); - break; + try { + switch ($action) { + case 'create': + $this->create($username, $email); + break; - case 'activate': - $this->activate($username, $email); - break; + case 'activate': + $this->activate($username, $email); + break; - case 'deactivate': - $this->deactivate($username, $email); - break; + case 'deactivate': + $this->deactivate($username, $email); + break; - case 'changename': - $this->changename($username, $email, $newUsername); - break; + case 'changename': + $this->changename($username, $email, $newUsername); + break; - case 'changeemail': - $this->changeemail($username, $email, $newEmail); - break; + case 'changeemail': + $this->changeemail($username, $email, $newEmail); + break; - case 'delete': - $this->delete($userid, $username, $email); - break; + case 'delete': + $this->delete($userid, $username, $email); + break; - case 'password': - $this->password($username, $email); - break; + case 'password': + $this->password($username, $email); + break; - case 'list': - $this->list($username, $email); - break; + case 'list': + $this->list($username, $email); + break; - case 'addgroup': - $this->addgroup($group, $username, $email); - break; + case 'addgroup': + $this->addgroup($group, $username, $email); + break; - case 'removegroup': - $this->removegroup($group, $username, $email); - break; + case 'removegroup': + $this->removegroup($group, $username, $email); + break; + } + } catch (RuntimeException $e) { + return EXIT_ERROR; } return EXIT_SUCCESS; @@ -215,7 +220,7 @@ private function create(?string $username = null, ?string $email = null): void if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); - exit; + throw new RuntimeException("The passwords don't match"); } $data['password'] = $password; @@ -228,7 +233,7 @@ private function create(?string $username = null, ?string $email = null): void } CLI::write('User creation aborted', 'red'); - exit; + throw new RuntimeException('User creation aborted'); } $userModel = model(UserModel::class); @@ -247,8 +252,10 @@ private function create(?string $username = null, ?string $email = null): void */ private function activate(?string $username = null, ?string $email = null): void { - $user = $this->findUser('Activate user', $username, $email); + $user = $this->findUser('Activate user', $username, $email); + $confirm = CLI::prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']); + if ($confirm === 'y') { $userModel = model(UserModel::class); @@ -313,7 +320,7 @@ private function changename(?string $username = null, ?string $email = null, ?st } CLI::write('User name change aborted', 'red'); - exit; + throw new RuntimeException('User name change aborted'); } } @@ -351,7 +358,7 @@ private function changeemail(?string $username = null, ?string $email = null, ?s } CLI::write('User email change aborted', 'red'); - exit; + throw new RuntimeException('User email change aborted'); } } @@ -379,7 +386,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai if (! $user) { CLI::write("User doesn't exist", 'red'); - exit; + throw new RuntimeException("User doesn't exis"); } } else { $user = $this->findUser('Delete user', $username, $email); @@ -412,7 +419,7 @@ private function password($username = null, $email = null): void if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); - exit; + throw new RuntimeException("The passwords don't match"); } $userModel = model(UserModel::class); @@ -499,7 +506,7 @@ private function removegroup($group = null, $username = null, $email = null): vo } /** - * Find an existing user by username or email. Exit if a user is not found + * Find an existing user by username or email. * * @param string $question Initial question at user prompt * @param string|null $username User name to search for (optional) @@ -509,6 +516,7 @@ private function findUser($question = '', $username = null, $email = null): \Cod { if (! $username && ! $email) { $choice = CLI::prompt($question . ' by username or email ?', ['u', 'e']); + if ($choice === 'u') { $username = CLI::prompt('Username', null, 'required'); } elseif ($choice === 'e') { @@ -531,7 +539,7 @@ private function findUser($question = '', $username = null, $email = null): \Cod if (! $user) { CLI::write("User doesn't exist", 'red'); - exit; + throw new RuntimeException("User doesn't exist"); } return $user; From 705e141e44360ce764eb0526ba286a7eea17ff05 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:15:55 +0900 Subject: [PATCH 145/401] style: add empty lines --- src/Commands/User.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 3a9373e7e..e4854e340 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -227,10 +227,12 @@ private function create(?string $username = null, ?string $email = null): void // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); $validation->setRules($this->validationRules); + if (! $validation->run($data)) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); } + CLI::write('User creation aborted', 'red'); throw new RuntimeException('User creation aborted'); @@ -276,8 +278,10 @@ private function activate(?string $username = null, ?string $email = null): void */ private function deactivate(?string $username = null, ?string $email = null): void { - $user = $this->findUser('Deactivate user', $username, $email); + $user = $this->findUser('Deactivate user', $username, $email); + $confirm = CLI::prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); + if ($confirm === 'y') { $userModel = model(UserModel::class); @@ -314,10 +318,12 @@ private function changename(?string $username = null, ?string $email = null, ?st $validation->setRules([ 'username' => $this->validationRules['username'], ]); + if (! $validation->run(['username' => $newUsername])) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); } + CLI::write('User name change aborted', 'red'); throw new RuntimeException('User name change aborted'); @@ -352,6 +358,7 @@ private function changeemail(?string $username = null, ?string $email = null, ?s $validation->setRules([ 'email' => $this->validationRules['email'], ]); + if (! $validation->run(['email' => $newEmail])) { foreach ($validation->getErrors() as $message) { CLI::write($message, 'red'); @@ -383,6 +390,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai if ($userid) { $user = $userModel->findById($userid); + if (! $user) { CLI::write("User doesn't exist", 'red'); @@ -393,8 +401,10 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai } $confirm = CLI::prompt('Delete the user ' . $user->username . ' (' . $user->email . ') ?', ['y', 'n']); + if ($confirm === 'y') { $userModel->delete($user->id, true); + CLI::write('User ' . $user->username . ' deleted', 'green'); } else { CLI::write('User ' . $user->username . ' deletion cancelled', 'yellow'); @@ -412,6 +422,7 @@ private function password($username = null, $email = null): void $user = $this->findUser('Change user password', $username, $email); $confirm = CLI::prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); + if ($confirm === 'y') { $password = CLI::prompt('Password', null, 'required'); $passwordConfirm = CLI::prompt('Password confirmation', null, $this->validationRules['password']); @@ -473,8 +484,10 @@ private function addgroup($group = null, $username = null, $email = null): void $user = $this->findUser('Add user to group', $username, $email); $confirm = CLI::prompt('Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', ['y', 'n']); + if ($confirm === 'y') { $user->addGroup($group); + CLI::write('User ' . $user->username . ' added to group ' . $group, 'green'); } else { CLI::write('Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', 'yellow'); @@ -497,8 +510,10 @@ private function removegroup($group = null, $username = null, $email = null): vo $user = $this->findUser('Remove user from group', $username, $email); $confirm = CLI::prompt('Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', ['y', 'n']); + if ($confirm === 'y') { $user->removeGroup($group); + CLI::write('User ' . $user->username . ' removed from group ' . $group, 'green'); } else { CLI::write('Removal of the user: ' . $user->username . ' from the group: ' . $group . ' cancelled', 'yellow'); From fefa121bc8bbef9a2c1ece95fea589f07f9976c5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:18:09 +0900 Subject: [PATCH 146/401] style: break long lines --- src/Commands/User.php | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index e4854e340..f9d23e23f 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -215,7 +215,11 @@ private function create(?string $username = null, ?string $email = null): void $data['email'] = $email; $password = CLI::prompt('Password', null, $this->validationRules['password']); - $passwordConfirm = CLI::prompt('Password confirmation', null, $this->validationRules['password']); + $passwordConfirm = CLI::prompt( + 'Password confirmation', + null, + $this->validationRules['password'] + ); if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); @@ -301,8 +305,11 @@ private function deactivate(?string $username = null, ?string $email = null): vo * @param string|null $email User email to search for (optional) * @param string|null $newUsername User new name (optional) */ - private function changename(?string $username = null, ?string $email = null, ?string $newUsername = null): void - { + private function changename( + ?string $username = null, + ?string $email = null, + ?string $newUsername = null + ): void { $validation = Services::validation(); $validation->setRules([ 'username' => 'required|is_unique[users.username]', @@ -346,8 +353,11 @@ private function changename(?string $username = null, ?string $email = null, ?st * @param string|null $email User email to search for (optional) * @param string|null $newEmail User new email (optional) */ - private function changeemail(?string $username = null, ?string $email = null, ?string $newEmail = null): void - { + private function changeemail( + ?string $username = null, + ?string $email = null, + ?string $newEmail = null + ): void { $user = $this->findUser('Change email', $username, $email); if (! $newEmail) { @@ -400,7 +410,10 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai $user = $this->findUser('Delete user', $username, $email); } - $confirm = CLI::prompt('Delete the user ' . $user->username . ' (' . $user->email . ') ?', ['y', 'n']); + $confirm = CLI::prompt( + 'Delete the user ' . $user->username . ' (' . $user->email . ') ?', + ['y', 'n'] + ); if ($confirm === 'y') { $userModel->delete($user->id, true); @@ -483,14 +496,20 @@ private function addgroup($group = null, $username = null, $email = null): void $user = $this->findUser('Add user to group', $username, $email); - $confirm = CLI::prompt('Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', ['y', 'n']); + $confirm = CLI::prompt( + 'Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', + ['y', 'n'] + ); if ($confirm === 'y') { $user->addGroup($group); CLI::write('User ' . $user->username . ' added to group ' . $group, 'green'); } else { - CLI::write('Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', 'yellow'); + CLI::write( + 'Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', + 'yellow' + ); } } @@ -509,7 +528,10 @@ private function removegroup($group = null, $username = null, $email = null): vo $user = $this->findUser('Remove user from group', $username, $email); - $confirm = CLI::prompt('Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', ['y', 'n']); + $confirm = CLI::prompt( + 'Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', + ['y', 'n'] + ); if ($confirm === 'y') { $user->removeGroup($group); From f37598eb12a6f3e6e2325a6d7d91f082d0ebda14 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:25:26 +0900 Subject: [PATCH 147/401] fix: make if conditions strict --- src/Commands/User.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index f9d23e23f..86cbf4ba6 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -204,12 +204,12 @@ private function create(?string $username = null, ?string $email = null): void { $data = []; - if (! $username) { + if ($username === null) { $username = CLI::prompt('Username', null, $this->validationRules['username']); } $data['username'] = $username; - if (! $email) { + if ($email === null) { $email = CLI::prompt('Email', null, $this->validationRules['email']); } $data['email'] = $email; @@ -317,7 +317,7 @@ private function changename( $user = $this->findUser('Change username', $username, $email); - if (! $newUsername) { + if ($newUsername === null) { $newUsername = CLI::prompt('New username', null, $this->validationRules['username']); } else { // Run validation if the user has passed username and/or email via command line @@ -360,7 +360,7 @@ private function changeemail( ): void { $user = $this->findUser('Change email', $username, $email); - if (! $newEmail) { + if ($newEmail === null) { $newEmail = CLI::prompt('New email', null, $this->validationRules['email']); } else { // Run validation if the user has passed username and/or email via command line @@ -398,10 +398,10 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai { $userModel = model(UserModel::class); - if ($userid) { + if ($userid !== 0) { $user = $userModel->findById($userid); - if (! $user) { + if ($user === null) { CLI::write("User doesn't exist", 'red'); throw new RuntimeException("User doesn't exis"); @@ -467,10 +467,10 @@ private function list(?string $username = null, ?string $email = null): void { $userModel = model(UserModel::class); - if ($username) { + if ($username !== null) { $userModel->like('username', $username); } - if ($email) { + if ($email !== null) { $userModel->like('secret', $email); } @@ -490,7 +490,7 @@ private function list(?string $username = null, ?string $email = null): void */ private function addgroup($group = null, $username = null, $email = null): void { - if (! $group) { + if ($group === null) { $group = CLI::prompt('Group', null, 'required'); } @@ -522,7 +522,7 @@ private function addgroup($group = null, $username = null, $email = null): void */ private function removegroup($group = null, $username = null, $email = null): void { - if (! $group) { + if ($group === null) { $group = CLI::prompt('Group', null, 'required'); } @@ -551,7 +551,7 @@ private function removegroup($group = null, $username = null, $email = null): vo */ private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User { - if (! $username && ! $email) { + if ($username === null && $email === null) { $choice = CLI::prompt($question . ' by username or email ?', ['u', 'e']); if ($choice === 'u') { @@ -567,13 +567,13 @@ private function findUser($question = '', $username = null, $email = null): \Cod $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); - if ($username) { + if ($username !== null) { $user = $users->where('username', $username)->first(); - } elseif ($email) { + } elseif ($email !== null) { $user = $users->where('secret', $email)->first(); } - if (! $user) { + if ($user === null) { CLI::write("User doesn't exist", 'red'); throw new RuntimeException("User doesn't exist"); From 59c53d8a6d6fa1d3c231e25205cda6d50cc14ed6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:31:48 +0900 Subject: [PATCH 148/401] chore: add tests/Commands/UserTest.php to skip --- rector.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rector.php b/rector.php index 78ede225f..75cee59ba 100644 --- a/rector.php +++ b/rector.php @@ -101,11 +101,13 @@ __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', __DIR__ . '/tests/Controllers/LoginTest.php', __DIR__ . '/tests/Commands/SetupTest.php', + __DIR__ . '/tests/Commands/UserTest.php', ], RemoveUnusedPrivatePropertyRector::class => [ __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', __DIR__ . '/tests/Controllers/LoginTest.php', __DIR__ . '/tests/Commands/SetupTest.php', + __DIR__ . '/tests/Commands/UserTest.php', ], ]); From 4e087ac46718ec3e48ad24d88355365a52204e79 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:33:28 +0900 Subject: [PATCH 149/401] chore: sort file paths --- rector.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rector.php b/rector.php index 75cee59ba..db112f376 100644 --- a/rector.php +++ b/rector.php @@ -98,16 +98,16 @@ // Ignore tests that use CodeIgniter::CI_VERSION UnwrapFutureCompatibleIfPhpVersionRector::class => [ - __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', - __DIR__ . '/tests/Controllers/LoginTest.php', __DIR__ . '/tests/Commands/SetupTest.php', + __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', __DIR__ . '/tests/Commands/UserTest.php', + __DIR__ . '/tests/Controllers/LoginTest.php', ], RemoveUnusedPrivatePropertyRector::class => [ - __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', - __DIR__ . '/tests/Controllers/LoginTest.php', __DIR__ . '/tests/Commands/SetupTest.php', + __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', __DIR__ . '/tests/Commands/UserTest.php', + __DIR__ . '/tests/Controllers/LoginTest.php', ], ]); From 63f73fd5146ccd9b363c20e727384b5e36f65caf Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 15 Sep 2023 13:26:55 +0900 Subject: [PATCH 150/401] test: add tests --- tests/Commands/UserTest.php | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 tests/Commands/UserTest.php diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php new file mode 100644 index 000000000..89b0c44ac --- /dev/null +++ b/tests/Commands/UserTest.php @@ -0,0 +1,138 @@ +=')) { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + } else { + $this->markTestSkipped('Cannot write tests with CI 4.2.x'); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); + } + + private function setUserInput(string $input): void + { + // Register the PhpStreamWrapper. + PhpStreamWrapper::register(); + + PhpStreamWrapper::setContent($input); + } + + private function resetUserInput(): void + { + // Restore php protocol wrapper. + PhpStreamWrapper::restore(); + } + + public function testCreate(): void + { + $input = 'Secret Passw0rd!'; + $this->setUserInput($input); + + command('shield:user create -n user1 -e user1@example.com'); + + $this->resetUserInput(); + + $this->assertStringContainsString( + 'User user1 created', + CITestStreamFilter::$buffer + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user1@example.com']); + $this->seeInDatabase($this->tables['identities'], [ + 'user_id' => $user->id, + 'secret' => 'user1@example.com', + ]); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'active' => 0, + ]); + } + + public function testActivate(): void + { + // Create a user. + /** @var User $user */ + $user = fake(UserModel::class, ['username' => 'user2']); + $user->createEmailIdentity([ + 'email' => 'user2@example.com', + 'password' => 'secret123', + ]); + + $input = 'y'; + $this->setUserInput($input); + + command('shield:user activate -n user2'); + + $this->resetUserInput(); + + $this->assertStringContainsString( + 'User user2 activated', + CITestStreamFilter::$buffer + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user2@example.com']); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'active' => 1, + ]); + } + + public function testDeactivate(): void + { + // Create a user. + /** @var User $user */ + $user = fake(UserModel::class, ['username' => 'user3', 'active' => 1]); + $user->createEmailIdentity([ + 'email' => 'user3@example.com', + 'password' => 'secret123', + ]); + + $input = 'y'; + $this->setUserInput($input); + + command('shield:user deactivate -n user3'); + + $this->resetUserInput(); + + $this->assertStringContainsString( + 'User user3 deactivated', + CITestStreamFilter::$buffer + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user3@example.com']); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'active' => 0, + ]); + } +} From 4e040c5013323f1474c83363109ece6ad5d10018 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 09:26:29 +0900 Subject: [PATCH 151/401] refactor: add prompt() method for testablilty --- src/Commands/User.php | 52 +++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 86cbf4ba6..e95264704 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -194,6 +194,20 @@ public function run(array $params): int return EXIT_SUCCESS; } + /** + * Asks the user for input. + * + * @param string $field Output "field" question + * @param array|string $options String to a default value, array to a list of options (the first option will be the default value) + * @param array|string $validation Validation rules + * + * @return string The user input + */ + private function prompt(string $field, $options = null, $validation = null): string + { + return CLI::prompt($field, $options, $validation); + } + /** * Create a new user * @@ -205,17 +219,17 @@ private function create(?string $username = null, ?string $email = null): void $data = []; if ($username === null) { - $username = CLI::prompt('Username', null, $this->validationRules['username']); + $username = $this->prompt('Username', null, $this->validationRules['username']); } $data['username'] = $username; if ($email === null) { - $email = CLI::prompt('Email', null, $this->validationRules['email']); + $email = $this->prompt('Email', null, $this->validationRules['email']); } $data['email'] = $email; - $password = CLI::prompt('Password', null, $this->validationRules['password']); - $passwordConfirm = CLI::prompt( + $password = $this->prompt('Password', null, $this->validationRules['password']); + $passwordConfirm = $this->prompt( 'Password confirmation', null, $this->validationRules['password'] @@ -260,7 +274,7 @@ private function activate(?string $username = null, ?string $email = null): void { $user = $this->findUser('Activate user', $username, $email); - $confirm = CLI::prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']); + $confirm = $this->prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']); if ($confirm === 'y') { $userModel = model(UserModel::class); @@ -284,7 +298,7 @@ private function deactivate(?string $username = null, ?string $email = null): vo { $user = $this->findUser('Deactivate user', $username, $email); - $confirm = CLI::prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); + $confirm = $this->prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); if ($confirm === 'y') { $userModel = model(UserModel::class); @@ -318,7 +332,7 @@ private function changename( $user = $this->findUser('Change username', $username, $email); if ($newUsername === null) { - $newUsername = CLI::prompt('New username', null, $this->validationRules['username']); + $newUsername = $this->prompt('New username', null, $this->validationRules['username']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); @@ -361,7 +375,7 @@ private function changeemail( $user = $this->findUser('Change email', $username, $email); if ($newEmail === null) { - $newEmail = CLI::prompt('New email', null, $this->validationRules['email']); + $newEmail = $this->prompt('New email', null, $this->validationRules['email']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); @@ -410,7 +424,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai $user = $this->findUser('Delete user', $username, $email); } - $confirm = CLI::prompt( + $confirm = $this->prompt( 'Delete the user ' . $user->username . ' (' . $user->email . ') ?', ['y', 'n'] ); @@ -434,11 +448,11 @@ private function password($username = null, $email = null): void { $user = $this->findUser('Change user password', $username, $email); - $confirm = CLI::prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); + $confirm = $this->prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); if ($confirm === 'y') { - $password = CLI::prompt('Password', null, 'required'); - $passwordConfirm = CLI::prompt('Password confirmation', null, $this->validationRules['password']); + $password = $this->prompt('Password', null, 'required'); + $passwordConfirm = $this->prompt('Password confirmation', null, $this->validationRules['password']); if ($password !== $passwordConfirm) { CLI::write("The passwords don't match", 'red'); @@ -491,12 +505,12 @@ private function list(?string $username = null, ?string $email = null): void private function addgroup($group = null, $username = null, $email = null): void { if ($group === null) { - $group = CLI::prompt('Group', null, 'required'); + $group = $this->prompt('Group', null, 'required'); } $user = $this->findUser('Add user to group', $username, $email); - $confirm = CLI::prompt( + $confirm = $this->prompt( 'Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', ['y', 'n'] ); @@ -523,12 +537,12 @@ private function addgroup($group = null, $username = null, $email = null): void private function removegroup($group = null, $username = null, $email = null): void { if ($group === null) { - $group = CLI::prompt('Group', null, 'required'); + $group = $this->prompt('Group', null, 'required'); } $user = $this->findUser('Remove user from group', $username, $email); - $confirm = CLI::prompt( + $confirm = $this->prompt( 'Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', ['y', 'n'] ); @@ -552,12 +566,12 @@ private function removegroup($group = null, $username = null, $email = null): vo private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User { if ($username === null && $email === null) { - $choice = CLI::prompt($question . ' by username or email ?', ['u', 'e']); + $choice = $this->prompt($question . ' by username or email ?', ['u', 'e']); if ($choice === 'u') { - $username = CLI::prompt('Username', null, 'required'); + $username = $this->prompt('Username', null, 'required'); } elseif ($choice === 'e') { - $email = CLI::prompt('Email', null, 'required|valid_email'); + $email = $this->prompt('Email', null, 'required|valid_email'); } } From 3f9508522d3c40b5423cb3faa057bf5c26039718 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 09:41:08 +0900 Subject: [PATCH 152/401] feat: add InputOutput class for testablility --- src/Commands/User.php | 98 +++++++++++++++++++++--------- src/Commands/Utils/InputOutput.php | 35 +++++++++++ 2 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 src/Commands/Utils/InputOutput.php diff --git a/src/Commands/User.php b/src/Commands/User.php index e95264704..5568e4d59 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -6,13 +6,15 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Exceptions\RuntimeException; use CodeIgniter\Shield\Models\UserModel; use Config\Services; class User extends BaseCommand { - private array $validActions = [ + private static ?InputOutput $io = null; + private array $validActions = [ 'create', 'activate', 'deactivate', 'changename', 'changeemail', 'delete', 'password', 'list', 'addgroup', 'removegroup', ]; @@ -127,10 +129,12 @@ class User extends BaseCommand */ public function run(array $params): int { + $this->ensureInput(); + $action = $params[0] ?? null; if ($action === null || ! in_array($action, $this->validActions, true)) { - CLI::write( + $this->write( 'Specify a valid action: ' . implode(',', $this->validActions), 'red' ); @@ -205,7 +209,18 @@ public function run(array $params): int */ private function prompt(string $field, $options = null, $validation = null): string { - return CLI::prompt($field, $options, $validation); + return self::$io->prompt($field, $options, $validation); + } + + /** + * Outputs a string to the cli on its own line. + */ + private static function write( + string $text = '', + ?string $foreground = null, + ?string $background = null + ): void { + self::$io->write($text, $foreground, $background); } /** @@ -236,7 +251,7 @@ private function create(?string $username = null, ?string $email = null): void ); if ($password !== $passwordConfirm) { - CLI::write("The passwords don't match", 'red'); + $this->write("The passwords don't match", 'red'); throw new RuntimeException("The passwords don't match"); } @@ -248,10 +263,10 @@ private function create(?string $username = null, ?string $email = null): void if (! $validation->run($data)) { foreach ($validation->getErrors() as $message) { - CLI::write($message, 'red'); + $this->write($message, 'red'); } - CLI::write('User creation aborted', 'red'); + $this->write('User creation aborted', 'red'); throw new RuntimeException('User creation aborted'); } @@ -261,7 +276,7 @@ private function create(?string $username = null, ?string $email = null): void $user = new \CodeIgniter\Shield\Entities\User($data); $userModel->save($user); - CLI::write('User ' . $username . ' created', 'green'); + $this->write('User ' . $username . ' created', 'green'); } /** @@ -282,9 +297,9 @@ private function activate(?string $username = null, ?string $email = null): void $user->active = 1; $userModel->save($user); - CLI::write('User ' . $user->username . ' activated', 'green'); + $this->write('User ' . $user->username . ' activated', 'green'); } else { - CLI::write('User ' . $user->username . ' activation cancelled', 'yellow'); + $this->write('User ' . $user->username . ' activation cancelled', 'yellow'); } } @@ -306,9 +321,9 @@ private function deactivate(?string $username = null, ?string $email = null): vo $user->active = 0; $userModel->save($user); - CLI::write('User ' . $user->username . ' deactivated', 'green'); + $this->write('User ' . $user->username . ' deactivated', 'green'); } else { - CLI::write('User ' . $user->username . ' deactivation cancelled', 'yellow'); + $this->write('User ' . $user->username . ' deactivation cancelled', 'yellow'); } } @@ -342,10 +357,10 @@ private function changename( if (! $validation->run(['username' => $newUsername])) { foreach ($validation->getErrors() as $message) { - CLI::write($message, 'red'); + $this->write($message, 'red'); } - CLI::write('User name change aborted', 'red'); + $this->write('User name change aborted', 'red'); throw new RuntimeException('User name change aborted'); } @@ -357,7 +372,7 @@ private function changename( $user->username = $newUsername; $userModel->save($user); - CLI::write('Username ' . $oldUsername . ' changed to ' . $newUsername, 'green'); + $this->write('Username ' . $oldUsername . ' changed to ' . $newUsername, 'green'); } /** @@ -385,9 +400,9 @@ private function changeemail( if (! $validation->run(['email' => $newEmail])) { foreach ($validation->getErrors() as $message) { - CLI::write($message, 'red'); + $this->write($message, 'red'); } - CLI::write('User email change aborted', 'red'); + $this->write('User email change aborted', 'red'); throw new RuntimeException('User email change aborted'); } @@ -398,7 +413,7 @@ private function changeemail( $user->email = $newEmail; $userModel->save($user); - CLI::write('Email for the user : ' . $user->username . ' changed to ' . $newEmail, 'green'); + $this->write('Email for the user : ' . $user->username . ' changed to ' . $newEmail, 'green'); } /** @@ -416,7 +431,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai $user = $userModel->findById($userid); if ($user === null) { - CLI::write("User doesn't exist", 'red'); + $this->write("User doesn't exist", 'red'); throw new RuntimeException("User doesn't exis"); } @@ -432,9 +447,9 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai if ($confirm === 'y') { $userModel->delete($user->id, true); - CLI::write('User ' . $user->username . ' deleted', 'green'); + $this->write('User ' . $user->username . ' deleted', 'green'); } else { - CLI::write('User ' . $user->username . ' deletion cancelled', 'yellow'); + $this->write('User ' . $user->username . ' deletion cancelled', 'yellow'); } } @@ -455,7 +470,7 @@ private function password($username = null, $email = null): void $passwordConfirm = $this->prompt('Password confirmation', null, $this->validationRules['password']); if ($password !== $passwordConfirm) { - CLI::write("The passwords don't match", 'red'); + $this->write("The passwords don't match", 'red'); throw new RuntimeException("The passwords don't match"); } @@ -465,9 +480,9 @@ private function password($username = null, $email = null): void $user->password = $password; $userModel->save($user); - CLI::write('Password for the user ' . $user->username . ' set', 'green'); + $this->write('Password for the user ' . $user->username . ' set', 'green'); } else { - CLI::write('Password setting for the user : ' . $user->username . ', cancelled', 'yellow'); + $this->write('Password setting for the user : ' . $user->username . ', cancelled', 'yellow'); } } @@ -488,10 +503,10 @@ private function list(?string $username = null, ?string $email = null): void $userModel->like('secret', $email); } - CLI::write("Id\tUser"); + $this->write("Id\tUser"); foreach ($userModel->findAll() as $user) { - CLI::write($user->id . "\t" . $user->username . ' (' . $user->email . ')'); + $this->write($user->id . "\t" . $user->username . ' (' . $user->email . ')'); } } @@ -518,9 +533,9 @@ private function addgroup($group = null, $username = null, $email = null): void if ($confirm === 'y') { $user->addGroup($group); - CLI::write('User ' . $user->username . ' added to group ' . $group, 'green'); + $this->write('User ' . $user->username . ' added to group ' . $group, 'green'); } else { - CLI::write( + $this->write( 'Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', 'yellow' ); @@ -550,9 +565,9 @@ private function removegroup($group = null, $username = null, $email = null): vo if ($confirm === 'y') { $user->removeGroup($group); - CLI::write('User ' . $user->username . ' removed from group ' . $group, 'green'); + $this->write('User ' . $user->username . ' removed from group ' . $group, 'green'); } else { - CLI::write('Removal of the user: ' . $user->username . ' from the group: ' . $group . ' cancelled', 'yellow'); + $this->write('Removal of the user: ' . $user->username . ' from the group: ' . $group . ' cancelled', 'yellow'); } } @@ -588,11 +603,34 @@ private function findUser($question = '', $username = null, $email = null): \Cod } if ($user === null) { - CLI::write("User doesn't exist", 'red'); + $this->write("User doesn't exist", 'red'); throw new RuntimeException("User doesn't exist"); } return $user; } + + private function ensureInput(): void + { + if (self::$io === null) { + self::$io = new InputOutput(); + } + } + + /** + * @internal Testing purpose only + */ + public static function setInputOutput(InputOutput $io): void + { + self::$io = $io; + } + + /** + * @internal Testing purpose only + */ + public static function resetInputOutput(): void + { + self::$io = null; + } } diff --git a/src/Commands/Utils/InputOutput.php b/src/Commands/Utils/InputOutput.php new file mode 100644 index 000000000..5541eb7d3 --- /dev/null +++ b/src/Commands/Utils/InputOutput.php @@ -0,0 +1,35 @@ + Date: Sat, 16 Sep 2023 10:25:43 +0900 Subject: [PATCH 153/401] test: use MockInputOutput class --- tests/Commands/UserTest.php | 79 ++++++++---------------- tests/Commands/Utils/MockInputOutput.php | 56 +++++++++++++++++ 2 files changed, 83 insertions(+), 52 deletions(-) create mode 100644 tests/Commands/Utils/MockInputOutput.php diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 89b0c44ac..15283348f 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -4,11 +4,10 @@ namespace Tests\Commands; -use CodeIgniter\CodeIgniter; -use CodeIgniter\Shield\Entities\User; +use CodeIgniter\Shield\Commands\User; +use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Models\UserModel; -use CodeIgniter\Test\Filters\CITestStreamFilter; -use CodeIgniter\Test\PhpStreamWrapper; +use Tests\Commands\Utils\MockInputOutput; use Tests\Support\DatabaseTestCase; /** @@ -16,52 +15,22 @@ */ final class UserTest extends DatabaseTestCase { - protected function setUp(): void - { - parent::setUp(); - - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); - } else { - $this->markTestSkipped('Cannot write tests with CI 4.2.x'); - } - } - - protected function tearDown(): void - { - parent::tearDown(); - - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); - } - - private function setUserInput(string $input): void - { - // Register the PhpStreamWrapper. - PhpStreamWrapper::register(); - - PhpStreamWrapper::setContent($input); - } - - private function resetUserInput(): void - { - // Restore php protocol wrapper. - PhpStreamWrapper::restore(); - } - public function testCreate(): void { - $input = 'Secret Passw0rd!'; - $this->setUserInput($input); + $io = new MockInputOutput(); + $io->setInputs([ + 'Secret Passw0rd!', + 'Secret Passw0rd!', + ]); + User::setInputOutput($io); command('shield:user create -n user1 -e user1@example.com'); - $this->resetUserInput(); + User::resetInputOutput(); $this->assertStringContainsString( 'User user1 created', - CITestStreamFilter::$buffer + $io->getLastOutput() ); $users = model(UserModel::class); @@ -79,23 +48,26 @@ public function testCreate(): void public function testActivate(): void { // Create a user. - /** @var User $user */ + /** @var UserEntity $user */ $user = fake(UserModel::class, ['username' => 'user2']); $user->createEmailIdentity([ 'email' => 'user2@example.com', 'password' => 'secret123', ]); - $input = 'y'; - $this->setUserInput($input); + $io = new MockInputOutput(); + $io->setInputs([ + 'y', + ]); + User::setInputOutput($io); command('shield:user activate -n user2'); - $this->resetUserInput(); + User::resetInputOutput(); $this->assertStringContainsString( 'User user2 activated', - CITestStreamFilter::$buffer + $io->getLastOutput() ); $users = model(UserModel::class); @@ -109,23 +81,26 @@ public function testActivate(): void public function testDeactivate(): void { // Create a user. - /** @var User $user */ + /** @var UserEntity $user */ $user = fake(UserModel::class, ['username' => 'user3', 'active' => 1]); $user->createEmailIdentity([ 'email' => 'user3@example.com', 'password' => 'secret123', ]); - $input = 'y'; - $this->setUserInput($input); + $io = new MockInputOutput(); + $io->setInputs([ + 'y', + ]); + User::setInputOutput($io); command('shield:user deactivate -n user3'); - $this->resetUserInput(); + User::resetInputOutput(); $this->assertStringContainsString( 'User user3 deactivated', - CITestStreamFilter::$buffer + $io->getLastOutput() ); $users = model(UserModel::class); diff --git a/tests/Commands/Utils/MockInputOutput.php b/tests/Commands/Utils/MockInputOutput.php new file mode 100644 index 000000000..0aae584d2 --- /dev/null +++ b/tests/Commands/Utils/MockInputOutput.php @@ -0,0 +1,56 @@ +inputs = $inputs; + } + + public function getLastOutput(): string + { + return array_shift($this->outputs); + } + + public function prompt(string $field, $options = null, $validation = null): string + { + return array_shift($this->inputs); + } + + public function write( + string $text = '', + ?string $foreground = null, + ?string $background = null + ): void { + if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + } else { + CITestStreamFilter::$buffer = ''; + + $streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); + } + + CLI::write($text, $foreground, $background); + $this->outputs[] = CITestStreamFilter::$buffer; + + if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); + } else { + stream_filter_remove($streamFilter); + } + } +} From d2d10e754ca2b71b7207da2ffbec6b0a543c875c Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 10:27:48 +0900 Subject: [PATCH 154/401] test: extract tearDown() --- tests/Commands/UserTest.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 15283348f..c4435b8ac 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -15,6 +15,13 @@ */ final class UserTest extends DatabaseTestCase { + protected function tearDown(): void + { + parent::tearDown(); + + User::resetInputOutput(); + } + public function testCreate(): void { $io = new MockInputOutput(); @@ -26,8 +33,6 @@ public function testCreate(): void command('shield:user create -n user1 -e user1@example.com'); - User::resetInputOutput(); - $this->assertStringContainsString( 'User user1 created', $io->getLastOutput() @@ -63,8 +68,6 @@ public function testActivate(): void command('shield:user activate -n user2'); - User::resetInputOutput(); - $this->assertStringContainsString( 'User user2 activated', $io->getLastOutput() @@ -96,8 +99,6 @@ public function testDeactivate(): void command('shield:user deactivate -n user3'); - User::resetInputOutput(); - $this->assertStringContainsString( 'User user3 deactivated', $io->getLastOutput() From 5f6941f826a9ce31cb0d8d448fc5240d2b3e0540 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 10:33:31 +0900 Subject: [PATCH 155/401] test: extract setMockIo() method --- tests/Commands/UserTest.php | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index c4435b8ac..ad54e0572 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -5,6 +5,7 @@ namespace Tests\Commands; use CodeIgniter\Shield\Commands\User; +use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Models\UserModel; use Tests\Commands\Utils\MockInputOutput; @@ -15,6 +16,8 @@ */ final class UserTest extends DatabaseTestCase { + private ?InputOutput $io = null; + protected function tearDown(): void { parent::tearDown(); @@ -22,20 +25,31 @@ protected function tearDown(): void User::resetInputOutput(); } + /** + * Set MockInputOutput and user inputs. + * + * @param array $inputs User inputs + * @phpstan-param list $inputs + */ + private function setMockIo(array $inputs): void + { + $this->io = new MockInputOutput(); + $this->io->setInputs($inputs); + User::setInputOutput($this->io); + } + public function testCreate(): void { - $io = new MockInputOutput(); - $io->setInputs([ + $this->setMockIo([ 'Secret Passw0rd!', 'Secret Passw0rd!', ]); - User::setInputOutput($io); command('shield:user create -n user1 -e user1@example.com'); $this->assertStringContainsString( 'User user1 created', - $io->getLastOutput() + $this->io->getLastOutput() ); $users = model(UserModel::class); @@ -60,17 +74,13 @@ public function testActivate(): void 'password' => 'secret123', ]); - $io = new MockInputOutput(); - $io->setInputs([ - 'y', - ]); - User::setInputOutput($io); + $this->setMockIo(['y']); command('shield:user activate -n user2'); $this->assertStringContainsString( 'User user2 activated', - $io->getLastOutput() + $this->io->getLastOutput() ); $users = model(UserModel::class); @@ -91,17 +101,13 @@ public function testDeactivate(): void 'password' => 'secret123', ]); - $io = new MockInputOutput(); - $io->setInputs([ - 'y', - ]); - User::setInputOutput($io); + $this->setMockIo(['y']); command('shield:user deactivate -n user3'); $this->assertStringContainsString( 'User user3 deactivated', - $io->getLastOutput() + $this->io->getLastOutput() ); $users = model(UserModel::class); From 7bc77cde8081d29524b3929267d2232cb3e09999 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 10:42:53 +0900 Subject: [PATCH 156/401] test: extract createUser() method --- tests/Commands/UserTest.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index ad54e0572..f31e5eec9 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -64,12 +64,22 @@ public function testCreate(): void ]); } - public function testActivate(): void + private function createUser(array $userData): UserEntity { - // Create a user. /** @var UserEntity $user */ - $user = fake(UserModel::class, ['username' => 'user2']); + $user = fake(UserModel::class, ['username' => $userData['username']]); $user->createEmailIdentity([ + 'email' => $userData['email'], + 'password' => $userData['password'], + ]); + + return $user; + } + + public function testActivate(): void + { + $user = $this->createUser([ + 'username' => 'user2', 'email' => 'user2@example.com', 'password' => 'secret123', ]); @@ -93,10 +103,8 @@ public function testActivate(): void public function testDeactivate(): void { - // Create a user. - /** @var UserEntity $user */ - $user = fake(UserModel::class, ['username' => 'user3', 'active' => 1]); - $user->createEmailIdentity([ + $user = $this->createUser([ + 'username' => 'user3', 'email' => 'user3@example.com', 'password' => 'secret123', ]); From 9a6e82eadd76ab693275819c232768e18a79b078 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 10:45:53 +0900 Subject: [PATCH 157/401] test: fix incorrect test --- tests/Commands/UserTest.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index f31e5eec9..f695eacf9 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -64,6 +64,9 @@ public function testCreate(): void ]); } + /** + * Create an active user. + */ private function createUser(array $userData): UserEntity { /** @var UserEntity $user */ @@ -84,6 +87,10 @@ public function testActivate(): void 'password' => 'secret123', ]); + $user->deactivate(); + $users = model(UserModel::class); + $users->save($user); + $this->setMockIo(['y']); command('shield:user activate -n user2'); @@ -93,8 +100,7 @@ public function testActivate(): void $this->io->getLastOutput() ); - $users = model(UserModel::class); - $user = $users->findByCredentials(['email' => 'user2@example.com']); + $user = $users->findByCredentials(['email' => 'user2@example.com']); $this->seeInDatabase($this->tables['users'], [ 'id' => $user->id, 'active' => 1, @@ -103,7 +109,7 @@ public function testActivate(): void public function testDeactivate(): void { - $user = $this->createUser([ + $this->createUser([ 'username' => 'user3', 'email' => 'user3@example.com', 'password' => 'secret123', From f45555610cd3cb9304117a90197bcd23410e1d73 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 11:51:11 +0900 Subject: [PATCH 158/401] feat: improve messages --- src/Commands/User.php | 40 ++++++++++++++++++------------------- tests/Commands/UserTest.php | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 5568e4d59..337e69722 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -276,7 +276,7 @@ private function create(?string $username = null, ?string $email = null): void $user = new \CodeIgniter\Shield\Entities\User($data); $userModel->save($user); - $this->write('User ' . $username . ' created', 'green'); + $this->write('User "' . $username . '" created', 'green'); } /** @@ -297,9 +297,9 @@ private function activate(?string $username = null, ?string $email = null): void $user->active = 1; $userModel->save($user); - $this->write('User ' . $user->username . ' activated', 'green'); + $this->write('User "' . $user->username . '" activated', 'green'); } else { - $this->write('User ' . $user->username . ' activation cancelled', 'yellow'); + $this->write('User "' . $user->username . '" activation cancelled', 'yellow'); } } @@ -313,7 +313,7 @@ private function deactivate(?string $username = null, ?string $email = null): vo { $user = $this->findUser('Deactivate user', $username, $email); - $confirm = $this->prompt('Deactivate the user ' . $username . ' ?', ['y', 'n']); + $confirm = $this->prompt('Deactivate the user "' . $username . '" ?', ['y', 'n']); if ($confirm === 'y') { $userModel = model(UserModel::class); @@ -321,9 +321,9 @@ private function deactivate(?string $username = null, ?string $email = null): vo $user->active = 0; $userModel->save($user); - $this->write('User ' . $user->username . ' deactivated', 'green'); + $this->write('User "' . $user->username . '" deactivated', 'green'); } else { - $this->write('User ' . $user->username . ' deactivation cancelled', 'yellow'); + $this->write('User "' . $user->username . '" deactivation cancelled', 'yellow'); } } @@ -372,7 +372,7 @@ private function changename( $user->username = $newUsername; $userModel->save($user); - $this->write('Username ' . $oldUsername . ' changed to ' . $newUsername, 'green'); + $this->write('Username "' . $oldUsername . '" changed to "' . $newUsername . '"', 'green'); } /** @@ -413,7 +413,7 @@ private function changeemail( $user->email = $newEmail; $userModel->save($user); - $this->write('Email for the user : ' . $user->username . ' changed to ' . $newEmail, 'green'); + $this->write('Email for "' . $user->username . '" changed to ' . $newEmail, 'green'); } /** @@ -440,16 +440,16 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai } $confirm = $this->prompt( - 'Delete the user ' . $user->username . ' (' . $user->email . ') ?', + 'Delete the user "' . $user->username . '" (' . $user->email . ') ?', ['y', 'n'] ); if ($confirm === 'y') { $userModel->delete($user->id, true); - $this->write('User ' . $user->username . ' deleted', 'green'); + $this->write('User "' . $user->username . '" deleted', 'green'); } else { - $this->write('User ' . $user->username . ' deletion cancelled', 'yellow'); + $this->write('User "' . $user->username . '" deletion cancelled', 'yellow'); } } @@ -463,7 +463,7 @@ private function password($username = null, $email = null): void { $user = $this->findUser('Change user password', $username, $email); - $confirm = $this->prompt('Set the password for the user ' . $user->username . ' ?', ['y', 'n']); + $confirm = $this->prompt('Set the password for "' . $user->username . '" ?', ['y', 'n']); if ($confirm === 'y') { $password = $this->prompt('Password', null, 'required'); @@ -480,9 +480,9 @@ private function password($username = null, $email = null): void $user->password = $password; $userModel->save($user); - $this->write('Password for the user ' . $user->username . ' set', 'green'); + $this->write('Password for "' . $user->username . '" set', 'green'); } else { - $this->write('Password setting for the user : ' . $user->username . ', cancelled', 'yellow'); + $this->write('Password setting for "' . $user->username . '" cancelled', 'yellow'); } } @@ -526,17 +526,17 @@ private function addgroup($group = null, $username = null, $email = null): void $user = $this->findUser('Add user to group', $username, $email); $confirm = $this->prompt( - 'Add the user: ' . $user->username . ' to the group: ' . $group . ' ?', + 'Add the user "' . $user->username . '" to the group "' . $group . '" ?', ['y', 'n'] ); if ($confirm === 'y') { $user->addGroup($group); - $this->write('User ' . $user->username . ' added to group ' . $group, 'green'); + $this->write('User "' . $user->username . '" added to group "' . $group . '"', 'green'); } else { $this->write( - 'Addition of the user: ' . $user->username . ' to the group: ' . $group . ' cancelled', + 'Addition of the user "' . $user->username . '" to the group "' . $group . '" cancelled', 'yellow' ); } @@ -558,16 +558,16 @@ private function removegroup($group = null, $username = null, $email = null): vo $user = $this->findUser('Remove user from group', $username, $email); $confirm = $this->prompt( - 'Remove the user: ' . $user->username . ' fromt the group: ' . $group . ' ?', + 'Remove the user "' . $user->username . '" from the group "' . $group . '" ?', ['y', 'n'] ); if ($confirm === 'y') { $user->removeGroup($group); - $this->write('User ' . $user->username . ' removed from group ' . $group, 'green'); + $this->write('User "' . $user->username . '" removed from group "' . $group . '"', 'green'); } else { - $this->write('Removal of the user: ' . $user->username . ' from the group: ' . $group . ' cancelled', 'yellow'); + $this->write('Removal of the user "' . $user->username . '" from the group "' . $group . '" cancelled', 'yellow'); } } diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index f695eacf9..7e7489732 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -48,7 +48,7 @@ public function testCreate(): void command('shield:user create -n user1 -e user1@example.com'); $this->assertStringContainsString( - 'User user1 created', + 'User "user1" created', $this->io->getLastOutput() ); @@ -96,7 +96,7 @@ public function testActivate(): void command('shield:user activate -n user2'); $this->assertStringContainsString( - 'User user2 activated', + 'User "user2" activated', $this->io->getLastOutput() ); @@ -120,7 +120,7 @@ public function testDeactivate(): void command('shield:user deactivate -n user3'); $this->assertStringContainsString( - 'User user3 deactivated', + 'User "user3" deactivated', $this->io->getLastOutput() ); From bb2423b7e80bba6d6ea3de77ec17433f30f6f5f9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 11:51:47 +0900 Subject: [PATCH 159/401] test: add tests --- tests/Commands/UserTest.php | 170 +++++++++++++++++++++++ tests/Commands/Utils/MockInputOutput.php | 8 ++ 2 files changed, 178 insertions(+) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 7e7489732..0def03a2e 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -131,4 +131,174 @@ public function testDeactivate(): void 'active' => 0, ]); } + + public function testChangename(): void + { + $this->createUser([ + 'username' => 'user4', + 'email' => 'user4@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user changename -n user4 --new-name newuser4'); + + $this->assertStringContainsString( + 'Username "user4" changed to "newuser4"', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user4@example.com']); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'username' => 'newuser4', + ]); + } + + public function testChangeemail(): void + { + $this->createUser([ + 'username' => 'user5', + 'email' => 'user5@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user changeemail -n user5 --new-email newuser5@example.jp'); + + $this->assertStringContainsString( + 'Email for "user5" changed to newuser5@example.jp', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'newuser5@example.jp']); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'username' => 'user5', + ]); + } + + public function testDelete(): void + { + $this->createUser([ + 'username' => 'user6', + 'email' => 'user6@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user delete -n user6'); + + $this->assertStringContainsString( + 'User "user6" deleted', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user6@example.com']); + $this->assertNull($user); + } + + public function testPassword(): void + { + $this->createUser([ + 'username' => 'user7', + 'email' => 'user7@example.com', + 'password' => 'secret123', + ]); + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $oldPasswordHash = $user->password_hash; + + $this->setMockIo(['y', 'newpassword', 'newpassword']); + + command('shield:user password -n user7'); + + $this->assertStringContainsString( + 'Password for "user7" set', + $this->io->getLastOutput() + ); + + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $this->assertNotSame($oldPasswordHash, $user->password_hash); + } + + public function testList(): void + { + $this->createUser([ + 'username' => 'user8', + 'email' => 'user8@example.com', + 'password' => 'secret123', + ]); + $this->createUser([ + 'username' => 'user9', + 'email' => 'user9@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo([]); + + command('shield:user list'); + + $this->assertStringContainsString( + 'Id User +1 user8 (user8@example.com) +2 user9 (user9@example.com) +', + $this->io->getOutputs() + ); + } + + public function testAddgroup(): void + { + $this->createUser([ + 'username' => 'user10', + 'email' => 'user10@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user addgroup -n user10 -g admin'); + + $this->assertStringContainsString( + 'User "user10" added to group "admin"', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user10@example.com']); + $this->assertTrue($user->inGroup('admin')); + } + + public function testRemovegroup(): void + { + $this->createUser([ + 'username' => 'user11', + 'email' => 'user11@example.com', + 'password' => 'secret123', + ]); + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user11@example.com']); + $user->addGroup('admin'); + $this->assertTrue($user->inGroup('admin')); + + $this->setMockIo(['y']); + + command('shield:user removegroup -n user11 -g admin'); + + $this->assertStringContainsString( + 'User "user11" removed from group "admin"', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user11@example.com']); + $this->assertFalse($user->inGroup('admin')); + } } diff --git a/tests/Commands/Utils/MockInputOutput.php b/tests/Commands/Utils/MockInputOutput.php index 0aae584d2..2095d45a2 100644 --- a/tests/Commands/Utils/MockInputOutput.php +++ b/tests/Commands/Utils/MockInputOutput.php @@ -24,6 +24,14 @@ public function getLastOutput(): string return array_shift($this->outputs); } + /** + * Returns all outputs. + */ + public function getOutputs(): string + { + return implode('', $this->outputs); + } + public function prompt(string $field, $options = null, $validation = null): string { return array_shift($this->inputs); From 516a78e7acc948e3141e2f60d7d3dc8c67952c29 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 12:40:40 +0900 Subject: [PATCH 160/401] refactor: use alias UserEntity --- src/Commands/User.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 337e69722..c338ade42 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -5,8 +5,8 @@ namespace CodeIgniter\Shield\Commands; use CodeIgniter\CLI\BaseCommand; -use CodeIgniter\CLI\CLI; use CodeIgniter\Shield\Commands\Utils\InputOutput; +use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\RuntimeException; use CodeIgniter\Shield\Models\UserModel; use Config\Services; @@ -273,7 +273,7 @@ private function create(?string $username = null, ?string $email = null): void $userModel = model(UserModel::class); - $user = new \CodeIgniter\Shield\Entities\User($data); + $user = new UserEntity($data); $userModel->save($user); $this->write('User "' . $username . '" created', 'green'); @@ -578,7 +578,7 @@ private function removegroup($group = null, $username = null, $email = null): vo * @param string|null $username User name to search for (optional) * @param string|null $email User email to search for (optional) */ - private function findUser($question = '', $username = null, $email = null): \CodeIgniter\Shield\Entities\User + private function findUser($question = '', $username = null, $email = null): UserEntity { if ($username === null && $email === null) { $choice = $this->prompt($question . ' by username or email ?', ['u', 'e']); @@ -592,7 +592,7 @@ private function findUser($question = '', $username = null, $email = null): \Cod $userModel = model(UserModel::class); - $user = new \CodeIgniter\Shield\Entities\User(); + $user = new UserEntity(); $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); From fbd496aa909e362c30e20f5b5006304ee87bd212 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 12:55:20 +0900 Subject: [PATCH 161/401] fix: `list -e` causes error Unknown column 'secret' in 'where clause' --- src/Commands/User.php | 13 ++++++++++++- tests/Commands/UserTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index c338ade42..e1f118333 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -5,6 +5,7 @@ namespace CodeIgniter\Shield\Commands; use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\RuntimeException; @@ -495,6 +496,16 @@ private function password($username = null, $email = null): void private function list(?string $username = null, ?string $email = null): void { $userModel = model(UserModel::class); + $userModel + ->select('users.id as id, username, secret as email') + ->join('auth_identities', 'users.id = auth_identities.user_id', 'LEFT') + ->groupStart() + ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD) + ->orGroupStart() + ->where('auth_identities.type', null) + ->groupEnd() + ->groupEnd() + ->asArray(); if ($username !== null) { $userModel->like('username', $username); @@ -506,7 +517,7 @@ private function list(?string $username = null, ?string $email = null): void $this->write("Id\tUser"); foreach ($userModel->findAll() as $user) { - $this->write($user->id . "\t" . $user->username . ' (' . $user->email . ')'); + $this->write($user['id'] . "\t" . $user['username'] . ' (' . $user['email'] . ')'); } } diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 0def03a2e..02ea79ad0 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -254,6 +254,31 @@ public function testList(): void ); } + public function testListByEmail(): void + { + $this->createUser([ + 'username' => 'user8', + 'email' => 'user8@example.com', + 'password' => 'secret123', + ]); + $this->createUser([ + 'username' => 'user9', + 'email' => 'user9@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo([]); + + command('shield:user list -e user9@example.com'); + + $this->assertStringContainsString( + 'Id User +2 user9 (user9@example.com) +', + $this->io->getOutputs() + ); + } + public function testAddgroup(): void { $this->createUser([ From 902b6544452f3551512d65818e351b29ec23a9b7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 13:49:49 +0900 Subject: [PATCH 162/401] fix: findUser() does not work with search by email --- src/Commands/User.php | 21 ++++++++++++++------- tests/Commands/UserTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index e1f118333..ce46d7f9b 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -602,15 +602,22 @@ private function findUser($question = '', $username = null, $email = null): User } $userModel = model(UserModel::class); + $userModel + ->select('users.id as id, username, secret as email') + ->join('auth_identities', 'users.id = auth_identities.user_id', 'LEFT') + ->groupStart() + ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD) + ->orGroupStart() + ->where('auth_identities.type', null) + ->groupEnd() + ->groupEnd() + ->asArray(); - $user = new UserEntity(); - - $users = $userModel->join('auth_identities', 'auth_identities.user_id = users.id'); - + $user = null; if ($username !== null) { - $user = $users->where('username', $username)->first(); + $user = $userModel->where('username', $username)->first(); } elseif ($email !== null) { - $user = $users->where('secret', $email)->first(); + $user = $userModel->where('email', $email)->first(); } if ($user === null) { @@ -619,7 +626,7 @@ private function findUser($question = '', $username = null, $email = null): User throw new RuntimeException("User doesn't exist"); } - return $user; + return $userModel->findById($user['id']); } private function ensureInput(): void diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 02ea79ad0..684859a4f 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -228,6 +228,30 @@ public function testPassword(): void $this->assertNotSame($oldPasswordHash, $user->password_hash); } + public function testPasswordWithoutOptionsAndSpecifyEmail(): void + { + $this->createUser([ + 'username' => 'user7', + 'email' => 'user7@example.com', + 'password' => 'secret123', + ]); + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $oldPasswordHash = $user->password_hash; + + $this->setMockIo(['e', 'user7@example.com', 'y', 'newpassword', 'newpassword']); + + command('shield:user password'); + + $this->assertStringContainsString( + 'Password for "user7" set', + $this->io->getLastOutput() + ); + + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $this->assertNotSame($oldPasswordHash, $user->password_hash); + } + public function testList(): void { $this->createUser([ From 0605032992433ee72590bd5959e7df51ff67dbe4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 13:57:14 +0900 Subject: [PATCH 163/401] test: add test for addgroup cancel --- tests/Commands/UserTest.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 684859a4f..f97eae98d 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -325,6 +325,28 @@ public function testAddgroup(): void $this->assertTrue($user->inGroup('admin')); } + public function testAddgroupCancel(): void + { + $this->createUser([ + 'username' => 'user10', + 'email' => 'user10@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['n']); + + command('shield:user addgroup -n user10 -g admin'); + + $this->assertStringContainsString( + 'Addition of the user "user10" to the group "admin" cancelled', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user10@example.com']); + $this->assertFalse($user->inGroup('admin')); + } + public function testRemovegroup(): void { $this->createUser([ From eae7fe2db1f5e7303b17c425af35cff0a984813c Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:06:57 +0900 Subject: [PATCH 164/401] refactor: move MockInputOutput class --- {tests/Commands/Utils => src/Test}/MockInputOutput.php | 2 +- tests/Commands/UserTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {tests/Commands/Utils => src/Test}/MockInputOutput.php (97%) diff --git a/tests/Commands/Utils/MockInputOutput.php b/src/Test/MockInputOutput.php similarity index 97% rename from tests/Commands/Utils/MockInputOutput.php rename to src/Test/MockInputOutput.php index 2095d45a2..5e3b0dd25 100644 --- a/tests/Commands/Utils/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Commands\Utils; +namespace CodeIgniter\Shield\Test; use CodeIgniter\CLI\CLI; use CodeIgniter\CodeIgniter; diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index f97eae98d..3b71b04a9 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -8,7 +8,7 @@ use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Models\UserModel; -use Tests\Commands\Utils\MockInputOutput; +use CodeIgniter\Shield\Test\MockInputOutput; use Tests\Support\DatabaseTestCase; /** From 9520f0d265ca8228ce7c8d3a4892ee95ea20970e Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:07:15 +0900 Subject: [PATCH 165/401] test: fix property type --- tests/Commands/UserTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 3b71b04a9..03b8eff72 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -5,7 +5,6 @@ namespace Tests\Commands; use CodeIgniter\Shield\Commands\User; -use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Models\UserModel; use CodeIgniter\Shield\Test\MockInputOutput; @@ -16,7 +15,7 @@ */ final class UserTest extends DatabaseTestCase { - private ?InputOutput $io = null; + private ?MockInputOutput $io = null; protected function tearDown(): void { From def10ef662a7bd75e13b9e37b7b78980297dcabb Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:20:48 +0900 Subject: [PATCH 166/401] fix: findUser() does not work with MySQL Unknown column 'email' in 'where clause --- src/Commands/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index ce46d7f9b..b49428e60 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -603,7 +603,7 @@ private function findUser($question = '', $username = null, $email = null): User $userModel = model(UserModel::class); $userModel - ->select('users.id as id, username, secret as email') + ->select('users.id as id, username, secret') ->join('auth_identities', 'users.id = auth_identities.user_id', 'LEFT') ->groupStart() ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD) @@ -617,7 +617,7 @@ private function findUser($question = '', $username = null, $email = null): User if ($username !== null) { $user = $userModel->where('username', $username)->first(); } elseif ($email !== null) { - $user = $userModel->where('email', $email)->first(); + $user = $userModel->where('secret', $email)->first(); } if ($user === null) { From 3409b4c95688a24daf21334a79cdfdea01914528 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:26:01 +0900 Subject: [PATCH 167/401] chore: update rector.php --- rector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rector.php b/rector.php index db112f376..862da0f20 100644 --- a/rector.php +++ b/rector.php @@ -98,15 +98,15 @@ // Ignore tests that use CodeIgniter::CI_VERSION UnwrapFutureCompatibleIfPhpVersionRector::class => [ + __DIR__ . '/src/Test/MockInputOutput.php', __DIR__ . '/tests/Commands/SetupTest.php', __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', - __DIR__ . '/tests/Commands/UserTest.php', __DIR__ . '/tests/Controllers/LoginTest.php', ], RemoveUnusedPrivatePropertyRector::class => [ + __DIR__ . '/src/Test/MockInputOutput.php', __DIR__ . '/tests/Commands/SetupTest.php', __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', - __DIR__ . '/tests/Commands/UserTest.php', __DIR__ . '/tests/Controllers/LoginTest.php', ], ]); From cae4054e0a2fdfedeec57da59dc4ead23d37d50b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:28:50 +0900 Subject: [PATCH 168/401] fix: remove unneeded static keyword --- src/Commands/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index b49428e60..77e9e3e03 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -216,7 +216,7 @@ private function prompt(string $field, $options = null, $validation = null): str /** * Outputs a string to the cli on its own line. */ - private static function write( + private function write( string $text = '', ?string $foreground = null, ?string $background = null From d852821b4c01172faadb0f92a1f76c573579f9cb Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 14:46:08 +0900 Subject: [PATCH 169/401] fix: hard coded table names --- src/Commands/User.php | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 77e9e3e03..ab6bf316f 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -7,6 +7,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Commands\Utils\InputOutput; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\RuntimeException; use CodeIgniter\Shield\Models\UserModel; @@ -125,12 +126,20 @@ class User extends BaseCommand 'password' => 'required|min_length[10]', ]; + /** + * Auth Table names + * + * @var array + */ + private array $tables = []; + /** * Displays the help for the spark cli script itself. */ public function run(array $params): int { $this->ensureInput(); + $this->setTables(); $action = $params[0] ?? null; @@ -199,6 +208,13 @@ public function run(array $params): int return EXIT_SUCCESS; } + private function setTables(): void + { + /** @var Auth $config */ + $config = config('Auth'); + $this->tables = $config->tables; + } + /** * Asks the user for input. * @@ -497,12 +513,16 @@ private function list(?string $username = null, ?string $email = null): void { $userModel = model(UserModel::class); $userModel - ->select('users.id as id, username, secret as email') - ->join('auth_identities', 'users.id = auth_identities.user_id', 'LEFT') + ->select($this->tables['users'] . '.id as id, username, secret as email') + ->join( + $this->tables['identities'], + $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id', + 'LEFT' + ) ->groupStart() - ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD) + ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD) ->orGroupStart() - ->where('auth_identities.type', null) + ->where($this->tables['identities'] . '.type', null) ->groupEnd() ->groupEnd() ->asArray(); @@ -603,12 +623,16 @@ private function findUser($question = '', $username = null, $email = null): User $userModel = model(UserModel::class); $userModel - ->select('users.id as id, username, secret') - ->join('auth_identities', 'users.id = auth_identities.user_id', 'LEFT') + ->select($this->tables['users'] . '.id as id, username, secret') + ->join( + $this->tables['identities'], + $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id', + 'LEFT' + ) ->groupStart() - ->where('auth_identities.type', Session::ID_TYPE_EMAIL_PASSWORD) + ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD) ->orGroupStart() - ->where('auth_identities.type', null) + ->where($this->tables['identities'] . '.type', null) ->groupEnd() ->groupEnd() ->asArray(); From 97e428b26a3bf517e48e5b397e4ec0dc94ccca72 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 15:26:40 +0900 Subject: [PATCH 170/401] refactor: extract RegistrationValidationRules class --- src/Controllers/RegisterController.php | 45 ++------------- .../RegistrationValidationRules.php | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 src/Validation/RegistrationValidationRules.php diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 1e31ee41a..126ac3487 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -10,12 +10,11 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\ValidationException; use CodeIgniter\Shield\Models\UserModel; use CodeIgniter\Shield\Traits\Viewable; +use CodeIgniter\Shield\Validation\RegistrationValidationRules; use Psr\Log\LoggerInterface; /** @@ -30,11 +29,6 @@ class RegisterController extends BaseController protected $helpers = ['setting']; - /** - * Auth Table names - */ - protected array $tables; - public function initController( RequestInterface $request, ResponseInterface $response, @@ -45,10 +39,6 @@ public function initController( $response, $logger ); - - /** @var Auth $authConfig */ - $authConfig = config('Auth'); - $this->tables = $authConfig->tables; } /** @@ -175,37 +165,10 @@ protected function getUserEntity(): User * @return array|string>> * @phpstan-return array>> */ - protected function getValidationRules(): array + public function getValidationRules(): array { - $registrationUsernameRules = array_merge( - config('AuthSession')->usernameValidationRules, - [sprintf('is_unique[%s.username]', $this->tables['users'])] - ); - $registrationEmailRules = array_merge( - config('AuthSession')->emailValidationRules, - [sprintf('is_unique[%s.secret]', $this->tables['identities'])] - ); + $rules = new RegistrationValidationRules(); - return setting('Validation.registration') ?? [ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => $registrationUsernameRules, - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => $registrationEmailRules, - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ], - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', - ], - ]; + return $rules->get(); } } diff --git a/src/Validation/RegistrationValidationRules.php b/src/Validation/RegistrationValidationRules.php new file mode 100644 index 000000000..dbea47894 --- /dev/null +++ b/src/Validation/RegistrationValidationRules.php @@ -0,0 +1,57 @@ +tables = $authConfig->tables; + } + + public function get(): array + { + $registrationUsernameRules = array_merge( + config('AuthSession')->usernameValidationRules, + [sprintf('is_unique[%s.username]', $this->tables['users'])] + ); + $registrationEmailRules = array_merge( + config('AuthSession')->emailValidationRules, + [sprintf('is_unique[%s.secret]', $this->tables['identities'])] + ); + + return setting('Validation.registration') ?? [ + 'username' => [ + 'label' => 'Auth.username', + 'rules' => $registrationUsernameRules, + ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => $registrationEmailRules, + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes', + ], + ], + 'password_confirm' => [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ], + ]; + } +} From 3d01ba51d83a1988478372e089be0b2aaa556265 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 15:29:42 +0900 Subject: [PATCH 171/401] fix: hard coded validation rules --- src/Commands/User.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Commands/User.php b/src/Commands/User.php index ab6bf316f..390e61e08 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -11,6 +11,7 @@ use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\RuntimeException; use CodeIgniter\Shield\Models\UserModel; +use CodeIgniter\Shield\Validation\RegistrationValidationRules; use Config\Services; class User extends BaseCommand @@ -140,6 +141,7 @@ public function run(array $params): int { $this->ensureInput(); $this->setTables(); + $this->setValidationRules(); $action = $params[0] ?? null; @@ -215,6 +217,19 @@ private function setTables(): void $this->tables = $config->tables; } + private function setValidationRules(): void + { + $validationRules = new RegistrationValidationRules(); + + $rules = $validationRules->get(); + + $this->validationRules = [ + 'username' => $rules['username'], + 'email' => $rules['email'], + 'password' => $rules['password'], + ]; + } + /** * Asks the user for input. * From e63109283daa890376514343443b133da04f90ff Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 15:47:23 +0900 Subject: [PATCH 172/401] fix: incorrect return value --- src/Test/MockInputOutput.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 5e3b0dd25..583df0ccc 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -21,7 +21,7 @@ public function setInputs(array $inputs): void public function getLastOutput(): string { - return array_shift($this->outputs); + return array_pop($this->outputs); } /** From c5812d214a8828067a8465a401246f0e6a8d2b97 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 15:48:05 +0900 Subject: [PATCH 173/401] test: add tests --- tests/Commands/UserTest.php | 170 ++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 03b8eff72..8bccc635c 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -37,6 +37,18 @@ private function setMockIo(array $inputs): void User::setInputOutput($this->io); } + public function testNoAction(): void + { + $this->setMockIo([]); + + command('shield:user'); + + $this->assertStringContainsString( + 'Specify a valid action: create,activate,deactivate,changename,changeemail,delete,password,list,addgroup,removegroup', + $this->io->getLastOutput() + ); + } + public function testCreate(): void { $this->setMockIo([ @@ -63,6 +75,35 @@ public function testCreate(): void ]); } + public function testCreateNotUniqueName(): void + { + $user = $this->createUser([ + 'username' => 'user1', + 'email' => 'user1@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo([ + 'Secret Passw0rd!', + 'Secret Passw0rd!', + ]); + + command('shield:user create -n user1 -e userx@example.com'); + + $this->assertStringContainsString( + 'The Username field must contain a unique value.', + $this->io->getOutputs() + ); + $this->assertStringContainsString( + 'User creation aborted', + $this->io->getOutputs() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'userx@example.com']); + $this->assertNull($user); + } + /** * Create an active user. */ @@ -156,6 +197,35 @@ public function testChangename(): void ]); } + public function testChangenameInvalidName(): void + { + $this->createUser([ + 'username' => 'user4', + 'email' => 'user4@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user changename -n user4 --new-name 1'); + + $this->assertStringContainsString( + 'The Username field must be at least 3 characters in length.', + $this->io->getOutputs() + ); + $this->assertStringContainsString( + 'User name change aborted', + $this->io->getOutputs() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user4@example.com']); + $this->seeInDatabase($this->tables['users'], [ + 'id' => $user->id, + 'username' => 'user4', + ]); + } + public function testChangeemail(): void { $this->createUser([ @@ -181,6 +251,32 @@ public function testChangeemail(): void ]); } + public function testChangeemailInvalidEmail(): void + { + $this->createUser([ + 'username' => 'user5', + 'email' => 'user5@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user changeemail -n user5 --new-email invalid'); + + $this->assertStringContainsString( + 'The Email Address field must contain a valid email address.', + $this->io->getOutputs() + ); + $this->assertStringContainsString( + 'User email change aborted', + $this->io->getOutputs() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'invalid']); + $this->assertNull($user); + } + public function testDelete(): void { $this->createUser([ @@ -203,6 +299,28 @@ public function testDelete(): void $this->assertNull($user); } + public function testDeleteById(): void + { + $user = $this->createUser([ + 'username' => 'user6', + 'email' => 'user6@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user delete -i ' . $user->id); + + $this->assertStringContainsString( + 'User "user6" deleted', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user6@example.com']); + $this->assertNull($user); + } + public function testPassword(): void { $this->createUser([ @@ -251,6 +369,32 @@ public function testPasswordWithoutOptionsAndSpecifyEmail(): void $this->assertNotSame($oldPasswordHash, $user->password_hash); } + public function testPasswordNotMatch(): void + { + $this->createUser([ + 'username' => 'user7', + 'email' => 'user7@example.com', + 'password' => 'secret123', + ]); + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $oldPasswordHash = $user->password_hash; + + $this->setMockIo([ + 'u', 'user7', 'y', 'newpassword', 'badpassword', + ]); + + command('shield:user password'); + + $this->assertStringContainsString( + "The passwords don't match", + $this->io->getLastOutput() + ); + + $user = $users->findByCredentials(['email' => 'user7@example.com']); + $this->assertSame($oldPasswordHash, $user->password_hash); + } + public function testList(): void { $this->createUser([ @@ -371,4 +515,30 @@ public function testRemovegroup(): void $user = $users->findByCredentials(['email' => 'user11@example.com']); $this->assertFalse($user->inGroup('admin')); } + + public function testRemovegroupCancel(): void + { + $this->createUser([ + 'username' => 'user11', + 'email' => 'user11@example.com', + 'password' => 'secret123', + ]); + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user11@example.com']); + $user->addGroup('admin'); + $this->assertTrue($user->inGroup('admin')); + + $this->setMockIo(['n']); + + command('shield:user removegroup -n user11 -g admin'); + + $this->assertStringContainsString( + 'Removal of the user "user11" from the group "admin" cancelled', + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user11@example.com']); + $this->assertTrue($user->inGroup('admin')); + } } From 674a1927282c191fccbe3a188d3540ab76f175d1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 16:09:16 +0900 Subject: [PATCH 174/401] style: break long line --- tests/Commands/UserTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 8bccc635c..34a9b4382 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -356,7 +356,9 @@ public function testPasswordWithoutOptionsAndSpecifyEmail(): void $user = $users->findByCredentials(['email' => 'user7@example.com']); $oldPasswordHash = $user->password_hash; - $this->setMockIo(['e', 'user7@example.com', 'y', 'newpassword', 'newpassword']); + $this->setMockIo([ + 'e', 'user7@example.com', 'y', 'newpassword', 'newpassword', + ]); command('shield:user password'); From b3e278d9590c8cda13417b100b5f8a33087dd823 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 16:12:48 +0900 Subject: [PATCH 175/401] refactor: rename method name --- src/Commands/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 390e61e08..48fdb04ea 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -139,7 +139,7 @@ class User extends BaseCommand */ public function run(array $params): int { - $this->ensureInput(); + $this->ensureInputOutput(); $this->setTables(); $this->setValidationRules(); @@ -668,7 +668,7 @@ private function findUser($question = '', $username = null, $email = null): User return $userModel->findById($user['id']); } - private function ensureInput(): void + private function ensureInputOutput(): void { if (self::$io === null) { self::$io = new InputOutput(); From 45252996a29c781f1ccd9c7a129aab2aef9ad56b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 16:49:05 +0900 Subject: [PATCH 176/401] feat: add getFirstOutput() --- src/Test/MockInputOutput.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 583df0ccc..c0cebdfaf 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -24,6 +24,11 @@ public function getLastOutput(): string return array_pop($this->outputs); } + public function getFirstOutput(): string + { + return array_shift($this->outputs); + } + /** * Returns all outputs. */ From 6e9fb890101349b5b179a0cf05e18990abc325b2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 16:52:05 +0900 Subject: [PATCH 177/401] test: use getFirstOutput() --- tests/Commands/UserTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 34a9b4382..87ac3a1e8 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -60,7 +60,7 @@ public function testCreate(): void $this->assertStringContainsString( 'User "user1" created', - $this->io->getLastOutput() + $this->io->getFirstOutput() ); $users = model(UserModel::class); @@ -92,11 +92,11 @@ public function testCreateNotUniqueName(): void $this->assertStringContainsString( 'The Username field must contain a unique value.', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $this->assertStringContainsString( 'User creation aborted', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $users = model(UserModel::class); @@ -211,11 +211,11 @@ public function testChangenameInvalidName(): void $this->assertStringContainsString( 'The Username field must be at least 3 characters in length.', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $this->assertStringContainsString( 'User name change aborted', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $users = model(UserModel::class); @@ -265,11 +265,11 @@ public function testChangeemailInvalidEmail(): void $this->assertStringContainsString( 'The Email Address field must contain a valid email address.', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $this->assertStringContainsString( 'User email change aborted', - $this->io->getOutputs() + $this->io->getFirstOutput() ); $users = model(UserModel::class); From df0c5157468e32cfee92d3499c3058d001a5bf0f Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:08:17 +0900 Subject: [PATCH 178/401] test: add tests --- tests/Commands/UserTest.php | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 87ac3a1e8..668ef19e2 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -104,6 +104,31 @@ public function testCreateNotUniqueName(): void $this->assertNull($user); } + public function testCreatePasswordNotMatch(): void + { + $user = $this->createUser([ + 'username' => 'user1', + 'email' => 'user1@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo([ + 'password', + 'badpassword', + ]); + + command('shield:user create -n user1 -e userx@example.com'); + + $this->assertStringContainsString( + "The passwords don't match", + $this->io->getFirstOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'userx@example.com']); + $this->assertNull($user); + } + /** * Create an active user. */ @@ -321,6 +346,28 @@ public function testDeleteById(): void $this->assertNull($user); } + public function testDeleteUserNotExist(): void + { + $this->createUser([ + 'username' => 'user6', + 'email' => 'user6@example.com', + 'password' => 'secret123', + ]); + + $this->setMockIo(['y']); + + command('shield:user delete -n userx'); + + $this->assertStringContainsString( + "User doesn't exist", + $this->io->getLastOutput() + ); + + $users = model(UserModel::class); + $user = $users->findByCredentials(['email' => 'user6@example.com']); + $this->assertNotNull($user); + } + public function testPassword(): void { $this->createUser([ From cd13f0c90dd1d1722d4de48435e80bb0fd809c59 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:12:34 +0900 Subject: [PATCH 179/401] refactor: extract checkUserExists() method --- src/Commands/User.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 48fdb04ea..a0c2143a5 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -462,11 +462,7 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai if ($userid !== 0) { $user = $userModel->findById($userid); - if ($user === null) { - $this->write("User doesn't exist", 'red'); - - throw new RuntimeException("User doesn't exis"); - } + $this->checkUserExists($user); } else { $user = $this->findUser('Delete user', $username, $email); } @@ -485,6 +481,18 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai } } + /** + * @param UserEntity|null $user + */ + private function checkUserExists($user): void + { + if ($user === null) { + $this->write("User doesn't exist", 'red'); + + throw new RuntimeException("User doesn't exis"); + } + } + /** * Change the password of an existing user by username or email * @@ -659,11 +667,7 @@ private function findUser($question = '', $username = null, $email = null): User $user = $userModel->where('secret', $email)->first(); } - if ($user === null) { - $this->write("User doesn't exist", 'red'); - - throw new RuntimeException("User doesn't exist"); - } + $this->checkUserExists($user); return $userModel->findById($user['id']); } From 69a549761770baa5f370ea9b19a0e0fa78d1c72c Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:19:59 +0900 Subject: [PATCH 180/401] fix: remove unused $validationRules default value --- src/Commands/User.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index a0c2143a5..9ab81c29f 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -121,11 +121,7 @@ class User extends BaseCommand /** * Validation rules for user fields */ - private array $validationRules = [ - 'username' => 'required|is_unique[users.username]', - 'email' => 'required|valid_email|is_unique[auth_identities.secret]', - 'password' => 'required|min_length[10]', - ]; + private array $validationRules = []; /** * Auth Table names From 47fb504ee05a136ec2a7ef7ea4e6f825a18456a0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:26:57 +0900 Subject: [PATCH 181/401] fix: remove unneeded Validation::setRules() --- src/Commands/User.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 9ab81c29f..8574e9b91 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -367,11 +367,6 @@ private function changename( ?string $email = null, ?string $newUsername = null ): void { - $validation = Services::validation(); - $validation->setRules([ - 'username' => 'required|is_unique[users.username]', - ]); - $user = $this->findUser('Change username', $username, $email); if ($newUsername === null) { From 293d43f70c35f212288b6ace994d6856e8bde9d3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:28:51 +0900 Subject: [PATCH 182/401] fix: revert method visibility --- src/Controllers/RegisterController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 126ac3487..0e49e3851 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -165,7 +165,7 @@ protected function getUserEntity(): User * @return array|string>> * @phpstan-return array>> */ - public function getValidationRules(): array + protected function getValidationRules(): array { $rules = new RegistrationValidationRules(); From b3e13f6e09a693c2640bdeab340743775796d028 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:32:54 +0900 Subject: [PATCH 183/401] fix: add helper('setting') --- src/Validation/RegistrationValidationRules.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Validation/RegistrationValidationRules.php b/src/Validation/RegistrationValidationRules.php index dbea47894..d7755d439 100644 --- a/src/Validation/RegistrationValidationRules.php +++ b/src/Validation/RegistrationValidationRules.php @@ -32,6 +32,8 @@ public function get(): array [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); + helper('setting'); + return setting('Validation.registration') ?? [ 'username' => [ 'label' => 'Auth.username', From 6f578b1c60f8d8de3efeba7dc27679985663a690 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:37:27 +0900 Subject: [PATCH 184/401] fix: description of the command Co-authored-by: Pooya Parsa --- src/Commands/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 8574e9b91..82772e813 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -42,7 +42,7 @@ class User extends BaseCommand * * @var string */ - protected $description = 'Manage Shield users'; + protected $description = 'Manage Shield users.'; /** * Command's usage From a54e30f4d7fc17829124af1f5805117929199c63 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 17:53:44 +0900 Subject: [PATCH 185/401] fix: validation rules for prompt() --- src/Commands/User.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 82772e813..d7f3b4425 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -262,20 +262,20 @@ private function create(?string $username = null, ?string $email = null): void $data = []; if ($username === null) { - $username = $this->prompt('Username', null, $this->validationRules['username']); + $username = $this->prompt('Username', null, $this->validationRules['username']['rules']); } $data['username'] = $username; if ($email === null) { - $email = $this->prompt('Email', null, $this->validationRules['email']); + $email = $this->prompt('Email', null, $this->validationRules['email']['rules']); } $data['email'] = $email; - $password = $this->prompt('Password', null, $this->validationRules['password']); + $password = $this->prompt('Password', null, $this->validationRules['password']['rules']); $passwordConfirm = $this->prompt( 'Password confirmation', null, - $this->validationRules['password'] + $this->validationRules['password']['rules'] ); if ($password !== $passwordConfirm) { @@ -370,7 +370,7 @@ private function changename( $user = $this->findUser('Change username', $username, $email); if ($newUsername === null) { - $newUsername = $this->prompt('New username', null, $this->validationRules['username']); + $newUsername = $this->prompt('New username', null, $this->validationRules['username']['rules']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); @@ -413,7 +413,7 @@ private function changeemail( $user = $this->findUser('Change email', $username, $email); if ($newEmail === null) { - $newEmail = $this->prompt('New email', null, $this->validationRules['email']); + $newEmail = $this->prompt('New email', null, $this->validationRules['email']['rules']); } else { // Run validation if the user has passed username and/or email via command line $validation = Services::validation(); @@ -498,7 +498,7 @@ private function password($username = null, $email = null): void if ($confirm === 'y') { $password = $this->prompt('Password', null, 'required'); - $passwordConfirm = $this->prompt('Password confirmation', null, $this->validationRules['password']); + $passwordConfirm = $this->prompt('Password confirmation', null, $this->validationRules['password']['rules']); if ($password !== $passwordConfirm) { $this->write("The passwords don't match", 'red'); @@ -631,7 +631,7 @@ private function findUser($question = '', $username = null, $email = null): User if ($choice === 'u') { $username = $this->prompt('Username', null, 'required'); } elseif ($choice === 'e') { - $email = $this->prompt('Email', null, 'required|valid_email'); + $email = $this->prompt('Email', null, $this->validationRules['email']['rules']); } } From 1be4d394c1a73f25350ff96018712caf608c6b99 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 18:24:23 +0900 Subject: [PATCH 186/401] fix: remove strong_password rule that can't be used --- src/Commands/User.php | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index d7f3b4425..1c31c71db 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -219,6 +219,19 @@ private function setValidationRules(): void $rules = $validationRules->get(); + // Remove strong_password because it only supports use cases + // to check the user's own password. + $rules['password']['rules'] = str_replace( + '|strong_password[]', + '', + $rules['password']['rules'] + ); + + /** @var Auth $config */ + $config = config('Auth'); + + $rules['password']['rules'] .= '|min_length[' . $config->minimumPasswordLength . ']'; + $this->validationRules = [ 'username' => $rules['username'], 'email' => $rules['email'], @@ -271,7 +284,11 @@ private function create(?string $username = null, ?string $email = null): void } $data['email'] = $email; - $password = $this->prompt('Password', null, $this->validationRules['password']['rules']); + $password = $this->prompt( + 'Password', + null, + $this->validationRules['password']['rules'] + ); $passwordConfirm = $this->prompt( 'Password confirmation', null, @@ -497,8 +514,16 @@ private function password($username = null, $email = null): void $confirm = $this->prompt('Set the password for "' . $user->username . '" ?', ['y', 'n']); if ($confirm === 'y') { - $password = $this->prompt('Password', null, 'required'); - $passwordConfirm = $this->prompt('Password confirmation', null, $this->validationRules['password']['rules']); + $password = $this->prompt( + 'Password', + null, + $this->validationRules['password']['rules'] + ); + $passwordConfirm = $this->prompt( + 'Password confirmation', + null, + $this->validationRules['password']['rules'] + ); if ($password !== $passwordConfirm) { $this->write("The passwords don't match", 'red'); From ef6b59a5f3aea0bbd56c947349892ac46140559f Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 21:36:49 +0900 Subject: [PATCH 187/401] feat: run CLI::prompt() for testing validation --- src/Test/MockInputOutput.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index c0cebdfaf..79226d150 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -6,24 +6,35 @@ use CodeIgniter\CLI\CLI; use CodeIgniter\CodeIgniter; +use CodeIgniter\Log\Exceptions\LogException; use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Test\Filters\CITestStreamFilter; +use CodeIgniter\Test\PhpStreamWrapper; final class MockInputOutput extends InputOutput { private array $inputs = []; private array $outputs = []; + /** + * Sets user inputs. + */ public function setInputs(array $inputs): void { $this->inputs = $inputs; } + /** + * Takes the last output from the output array. + */ public function getLastOutput(): string { return array_pop($this->outputs); } + /** + * Takes the first output from the output array. + */ public function getFirstOutput(): string { return array_shift($this->outputs); @@ -39,7 +50,28 @@ public function getOutputs(): string public function prompt(string $field, $options = null, $validation = null): string { - return array_shift($this->inputs); + $input = array_shift($this->inputs); + + if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + PhpStreamWrapper::register(); + PhpStreamWrapper::setContent($input); + + $userInput = CLI::prompt($field, $options, $validation); + + PhpStreamWrapper::restore(); + + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); + + if ($input !== $userInput) { + throw new LogException($input . '!==' . $userInput); + } + } + + return $input; } public function write( From c78bcbf8dbaec5ac932874cce563079ba794f455 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 21:37:18 +0900 Subject: [PATCH 188/401] fix: incorrect validation rule for email --- src/Commands/User.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 1c31c71db..4aba33f95 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -656,7 +656,11 @@ private function findUser($question = '', $username = null, $email = null): User if ($choice === 'u') { $username = $this->prompt('Username', null, 'required'); } elseif ($choice === 'e') { - $email = $this->prompt('Email', null, $this->validationRules['email']['rules']); + $email = $this->prompt( + 'Email', + null, + 'required' + ); } } From 443f0e255c03860fc4b86eea3891e8ce8921bf10 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 21:50:42 +0900 Subject: [PATCH 189/401] fix: password validation rules manipulation The validation rules may be an array or string. --- src/Commands/User.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index 4aba33f95..938d1ac7d 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -219,18 +219,23 @@ private function setValidationRules(): void $rules = $validationRules->get(); - // Remove strong_password because it only supports use cases + // Remove `strong_password` because it only supports use cases // to check the user's own password. - $rules['password']['rules'] = str_replace( - '|strong_password[]', - '', - $rules['password']['rules'] - ); + $passwordRules = $rules['password']['rules']; + if (is_string($passwordRules)) { + $passwordRules = explode('|', $passwordRules); + } + if (($key = array_search('strong_password[]', $passwordRules, true)) !== false) { + unset($passwordRules[$key]); + } /** @var Auth $config */ $config = config('Auth'); - $rules['password']['rules'] .= '|min_length[' . $config->minimumPasswordLength . ']'; + // Add `min_length` + $passwordRules[] = 'min_length[' . $config->minimumPasswordLength . ']'; + + $rules['password']['rules'] = $passwordRules; $this->validationRules = [ 'username' => $rules['username'], From 774997bcb2de6c1642ef99bf2b67a2da7fb8bfef Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 22:02:40 +0900 Subject: [PATCH 190/401] refactor: add specific Exceptions RuntimeException is too wide. --- src/Commands/Exceptions/BadInputException.php | 11 +++++++++++ src/Commands/Exceptions/CancelException.php | 11 +++++++++++ src/Commands/User.php | 18 ++++++++++-------- src/Exceptions/UserNotFoundException.php | 9 +++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/Commands/Exceptions/BadInputException.php create mode 100644 src/Commands/Exceptions/CancelException.php create mode 100644 src/Exceptions/UserNotFoundException.php diff --git a/src/Commands/Exceptions/BadInputException.php b/src/Commands/Exceptions/BadInputException.php new file mode 100644 index 000000000..0316394e1 --- /dev/null +++ b/src/Commands/Exceptions/BadInputException.php @@ -0,0 +1,11 @@ +removegroup($group, $username, $email); break; } - } catch (RuntimeException $e) { + } catch (BadInputException|CancelException|UserNotFoundException $e) { return EXIT_ERROR; } @@ -303,7 +305,7 @@ private function create(?string $username = null, ?string $email = null): void if ($password !== $passwordConfirm) { $this->write("The passwords don't match", 'red'); - throw new RuntimeException("The passwords don't match"); + throw new BadInputException("The passwords don't match"); } $data['password'] = $password; @@ -318,7 +320,7 @@ private function create(?string $username = null, ?string $email = null): void $this->write('User creation aborted', 'red'); - throw new RuntimeException('User creation aborted'); + throw new CancelException('User creation aborted'); } $userModel = model(UserModel::class); @@ -407,7 +409,7 @@ private function changename( $this->write('User name change aborted', 'red'); - throw new RuntimeException('User name change aborted'); + throw new CancelException('User name change aborted'); } } @@ -449,7 +451,7 @@ private function changeemail( } $this->write('User email change aborted', 'red'); - throw new RuntimeException('User email change aborted'); + throw new CancelException('User email change aborted'); } } @@ -502,7 +504,7 @@ private function checkUserExists($user): void if ($user === null) { $this->write("User doesn't exist", 'red'); - throw new RuntimeException("User doesn't exis"); + throw new UserNotFoundException("User doesn't exist"); } } @@ -533,7 +535,7 @@ private function password($username = null, $email = null): void if ($password !== $passwordConfirm) { $this->write("The passwords don't match", 'red'); - throw new RuntimeException("The passwords don't match"); + throw new BadInputException("The passwords don't match"); } $userModel = model(UserModel::class); diff --git a/src/Exceptions/UserNotFoundException.php b/src/Exceptions/UserNotFoundException.php new file mode 100644 index 000000000..485f5c04e --- /dev/null +++ b/src/Exceptions/UserNotFoundException.php @@ -0,0 +1,9 @@ + Date: Sat, 16 Sep 2023 22:05:35 +0900 Subject: [PATCH 191/401] refactor: put $this->write() in one place --- src/Commands/User.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Commands/User.php b/src/Commands/User.php index db14bbfa1..c78b86e01 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -202,6 +202,8 @@ public function run(array $params): int break; } } catch (BadInputException|CancelException|UserNotFoundException $e) { + $this->write($e->getMessage(), 'red'); + return EXIT_ERROR; } @@ -303,8 +305,6 @@ private function create(?string $username = null, ?string $email = null): void ); if ($password !== $passwordConfirm) { - $this->write("The passwords don't match", 'red'); - throw new BadInputException("The passwords don't match"); } $data['password'] = $password; @@ -318,8 +318,6 @@ private function create(?string $username = null, ?string $email = null): void $this->write($message, 'red'); } - $this->write('User creation aborted', 'red'); - throw new CancelException('User creation aborted'); } @@ -407,8 +405,6 @@ private function changename( $this->write($message, 'red'); } - $this->write('User name change aborted', 'red'); - throw new CancelException('User name change aborted'); } } @@ -449,7 +445,6 @@ private function changeemail( foreach ($validation->getErrors() as $message) { $this->write($message, 'red'); } - $this->write('User email change aborted', 'red'); throw new CancelException('User email change aborted'); } @@ -502,8 +497,6 @@ private function delete(int $userid = 0, ?string $username = null, ?string $emai private function checkUserExists($user): void { if ($user === null) { - $this->write("User doesn't exist", 'red'); - throw new UserNotFoundException("User doesn't exist"); } } @@ -533,8 +526,6 @@ private function password($username = null, $email = null): void ); if ($password !== $passwordConfirm) { - $this->write("The passwords don't match", 'red'); - throw new BadInputException("The passwords don't match"); } From e546f27cca2cf4691a9effc9ea803da81fefaa59 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Sep 2023 22:15:28 +0900 Subject: [PATCH 192/401] fix: typo in Exception classname --- src/Test/MockInputOutput.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 79226d150..6829e25b5 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -6,8 +6,8 @@ use CodeIgniter\CLI\CLI; use CodeIgniter\CodeIgniter; -use CodeIgniter\Log\Exceptions\LogException; use CodeIgniter\Shield\Commands\Utils\InputOutput; +use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Test\Filters\CITestStreamFilter; use CodeIgniter\Test\PhpStreamWrapper; @@ -67,7 +67,7 @@ public function prompt(string $field, $options = null, $validation = null): stri CITestStreamFilter::removeErrorFilter(); if ($input !== $userInput) { - throw new LogException($input . '!==' . $userInput); + throw new LogicException($input . '!==' . $userInput); } } From f4344f5a19673b46087093e4b4eae1caa445652a Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Thu, 14 Sep 2023 11:41:50 +0800 Subject: [PATCH 193/401] add config.sort-packages --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 383ec4a9b..6a3565102 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,8 @@ "config": { "allow-plugins": { "phpstan/extension-installer": true - } + }, + "sort-packages": true }, "scripts": { "post-update-cmd": [ From 1dc568e8d3284b80d5833b34dab16b5bc872de67 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Thu, 14 Sep 2023 11:42:27 +0800 Subject: [PATCH 194/401] run composer normalize --- composer.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 6a3565102..c3759ba90 100644 --- a/composer.json +++ b/composer.json @@ -17,20 +17,27 @@ } ], "homepage": "https://github.com/codeigniter4/shield", + "support": { + "issues": "https://github.com/codeigniter4/shield/issues", + "forum": "https://github.com/codeigniter4/shield/discussions", + "source": "https://github.com/codeigniter4/shield", + "docs": "https://codeigniter4.github.io/shield/", + "slack": "https://codeigniterchat.slack.com" + }, "require": { "php": "^7.4.3 || ^8.0", "codeigniter4/settings": "^2.1" }, "require-dev": { + "codeigniter/phpstan-codeigniter": "^1.1", "codeigniter4/devkit": "^1.0", "codeigniter4/framework": "^4.2.7", + "firebase/php-jwt": "^6.4", "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", - "firebase/php-jwt": "^6.4", - "rector/rector": "0.18.3", - "codeigniter/phpstan-codeigniter": "^1.2", "phpstan/extension-installer": "^1.3", - "phpstan/phpstan-strict-rules": "^1.5" + "phpstan/phpstan-strict-rules": "^1.5", + "rector/rector": "0.18.3" }, "provide": { "codeigniter4/authentication-implementation": "1.0" @@ -45,12 +52,12 @@ "psr-4": { "CodeIgniter\\Shield\\": "src" }, - "exclude-from-classmap": [ - "**/Database/Migrations/**" - ], "files": [ "src/Helpers/auth_helper.php", "src/Helpers/email_helper.php" + ], + "exclude-from-classmap": [ + "**/Database/Migrations/**" ] }, "autoload-dev": { @@ -74,7 +81,6 @@ "psalm", "rector process --dry-run" ], - "sa": "@analyze", "ci": [ "Composer\\Config::disableProcessTimeout", "@cs", @@ -88,14 +94,8 @@ "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", + "sa": "@analyze", "style": "@cs-fix", "test": "phpunit" - }, - "support": { - "forum": "https://github.com/codeigniter4/shield/discussions", - "slack": "https://codeigniterchat.slack.com", - "source": "https://github.com/codeigniter4/shield", - "issues": "https://github.com/codeigniter4/shield/issues", - "docs": "https://codeigniter4.github.io/shield/" } } From 692d0a92bdeb627de2ca36ac54c3320a46c51003 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Mon, 18 Sep 2023 17:41:09 +0800 Subject: [PATCH 195/401] Bump phpstan-codeigniter version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c3759ba90..d2d9dfa9a 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "codeigniter4/settings": "^2.1" }, "require-dev": { - "codeigniter/phpstan-codeigniter": "^1.1", + "codeigniter/phpstan-codeigniter": "^1.3", "codeigniter4/devkit": "^1.0", "codeigniter4/framework": "^4.2.7", "firebase/php-jwt": "^6.4", From 804e44967349d069acc4315f356f94f19b03faf6 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Mon, 18 Sep 2023 17:42:12 +0800 Subject: [PATCH 196/401] Regenerate baseline --- phpstan-baseline.php | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 2da1a018e..2e880e9ff 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1,6 +1,4 @@ - 1, 'path' => __DIR__ . '/src/Models/UserIdentityModel.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Cannot unset offset \'email\' on array\\{username\\: string, status\\: string, status_message\\: string, active\\: bool, last_active\\: string, deleted_at\\: string\\}\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Cannot unset offset \'password_hash\' on array\\{username\\: string, status\\: string, status_message\\: string, active\\: bool, last_active\\: string, deleted_at\\: string\\}\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 2, 'path' => __DIR__ . '/src/Models/UserModel.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Offset \'email\' does not exist on array\\{username\\: string, status\\: string, status_message\\: string, active\\: bool, last_active\\: string, deleted_at\\: string\\}\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Offset \'password_hash\' does not exist on array\\{username\\: string, status\\: string, status_message\\: string, active\\: bool, last_active\\: string, deleted_at\\: string\\}\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$data \\(array\\|CodeIgniter\\\\Shield\\\\Entities\\\\User\\) of method CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:insert\\(\\) should be contravariant with parameter \\$data \\(array\\|object\\|null\\) of method CodeIgniter\\\\Model\\:\\:insert\\(\\)$#', 'count' => 1, @@ -343,16 +361,6 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Unit/UserTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$active on array\\|object\\.$#', - 'count' => 2, - 'path' => __DIR__ . '/tests/Unit/UserTest.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access property \\$password_hash on array\\|object\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/tests/Unit/UserTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 1, From 0c551dca92323b1f8188ad6eea273a4a3ff9656e Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 11 Aug 2023 17:45:36 -0500 Subject: [PATCH 197/401] Initial work on HMAC implementation. This replicates closely the Authorization Token implementation. --- .../Authenticators/HMAC_SHA256.php | 265 ++++++++++++++++++ src/Authentication/Traits/HasHMACTokens.php | 150 ++++++++++ src/Config/Auth.php | 2 + src/Entities/User.php | 2 + src/Filters/HmacAuth.php | 68 +++++ src/Models/UserIdentityModel.php | 142 +++++++++- 6 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 src/Authentication/Authenticators/HMAC_SHA256.php create mode 100644 src/Authentication/Traits/HasHMACTokens.php create mode 100644 src/Filters/HmacAuth.php diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php new file mode 100644 index 000000000..f79c22b10 --- /dev/null +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -0,0 +1,265 @@ +provider = $provider; + + $this->loginModel = model(TokenLoginModel::class); + } + + /** + * Attempts to authenticate a user with the given $credentials. + * Logs the user in with a successful check. + * + * @throws AuthenticationException + */ + public function attempt(array $credentials): Result + { + /** @var IncomingRequest $request */ + $request = service('request'); + + $ipAddress = $request->getIPAddress(); + $userAgent = (string) $request->getUserAgent(); + + $result = $this->check($credentials); + + if (! $result->isOK()) { + // Always record a login attempt, whether success or not. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_HMAC_TOKEN, + $credentials['token'] ?? '', + false, + $ipAddress, + $userAgent + ); + + return $result; + } + + $user = $result->extraInfo(); + + if ($user->isBanned()) { + $this->user = null; + + return new Result([ + 'success' => false, + 'reason' => $user->getBanMessage() ?? lang('Auth.bannedUser'), + ]); + } + + $user = $user->setAccessToken( + $user->getAccessToken($this->getBearerToken()) + ); + + $this->login($user); + + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_HMAC_TOKEN, + $credentials['token'] ?? '', + true, + $ipAddress, + $userAgent, + $this->user->id + ); + + return $result; + } + + /** + * Checks a user's $credentials to see if they match an + * existing user. + * + * In this case, $credentials has only a single valid value: token, + * which is the plain text token to return. + */ + public function check(array $credentials): Result + { + if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { + return new Result([ + 'success' => false, + 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), + ]); + } + + if (strpos($credentials['token'], 'HMAC-SHA256') === 0) { + $credentials['token'] = trim(substr($credentials['token'], 11)); + } + + // Extract UserToken and HMACSHA256 Signature from Authorization token + [$userToken, $signature] = preg_split('/:/', $credentials['token'], -1, PREG_SPLIT_NO_EMPTY); + + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + $token = $identityModel->getHMACTokenByKey($userToken); + + if ($token === null) { + return new Result([ + 'success' => false, + 'reason' => lang('Auth.badToken'), + ]); + } + + // Check signature... + $hash = hash_hmac('sha256', $credentials['body'], $token->secret2); + if ($hash !== $signature) { + return new Result([ + 'success' => false, + 'reason' => lang('Auth.badToken'), + ]); + } + + assert($token->last_used_at instanceof Time || $token->last_used_at === null); + + // Hasn't been used in a long time + if ( + $token->last_used_at + && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) + ) { + return new Result([ + 'success' => false, + 'reason' => lang('Auth.oldToken'), + ]); + } + + $token->last_used_at = Time::now()->format('Y-m-d H:i:s'); + + if ($token->hasChanged()) { + $identityModel->save($token); + } + + // Ensure the token is set as the current token + $user = $token->user(); + $user->setAccessToken($token); + + return new Result([ + 'success' => true, + 'extraInfo' => $user, + ]); + } + + /** + * Checks if the user is currently logged in. + * Since AccessToken usage is inherently stateless, + * it runs $this->attempt on each usage. + */ + public function loggedIn(): bool + { + if (! empty($this->user)) { + return true; + } + + /** @var IncomingRequest $request */ + $request = service('request'); + + return $this->attempt([ + 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']), + ])->isOK(); + } + + /** + * Logs the given user in by saving them to the class. + */ + public function login(User $user): void + { + $this->user = $user; + } + + /** + * Logs a user in based on their ID. + * + * @param int|string $userId + * + * @throws AuthenticationException + */ + public function loginById($userId): void + { + $user = $this->provider->findById($userId); + + if (empty($user)) { + throw AuthenticationException::forInvalidUser(); + } + + $user->setAccessToken( + $user->getAccessToken($this->getBearerToken()) + ); + + $this->login($user); + } + + /** + * Logs the current user out. + */ + public function logout(): void + { + $this->user = null; + } + + /** + * Returns the currently logged in user. + */ + public function getUser(): ?User + { + return $this->user; + } + + /** + * Returns the Bearer token from the Authorization header + */ + public function getBearerToken(): ?string + { + /** @var IncomingRequest $request */ + $request = service('request'); + + $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); + + if (empty($header)) { + return null; + } + + return trim(substr($header, 6)); // 'Bearer' + } + + /** + * Updates the user's last active date. + */ + public function recordActiveDate(): void + { + if (! $this->user instanceof User) { + throw new InvalidArgumentException( + __METHOD__ . '() requires logged in user before calling.' + ); + } + + $this->user->last_active = Time::now(); + + $this->provider->updateActiveDate($this->user); + } +} diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHMACTokens.php new file mode 100644 index 000000000..12973de6a --- /dev/null +++ b/src/Authentication/Traits/HasHMACTokens.php @@ -0,0 +1,150 @@ +generateHMACToken($this, $name, $scopes); + } + + /** + * Delete any HMAC tokens for the given raw token. + */ + public function revokeHMACToken(string $rawToken): void + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + $identityModel->revokeHMACToken($this, $rawToken); + } + + /** + * Revokes all HMAC tokens for this user. + */ + public function revokeAllHMACTokens(): void + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + $identityModel->revokeAllHMACTokens($this); + } + + /** + * Retrieves all personal HMAC tokens for this user. + * + * @return AccessToken[] + */ + public function hmacTokens(): array + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + return $identityModel->getAllHMACTokens($this); + } + + /** + * Given a raw token, it will locate it within the system. + */ + public function getHmacToken(?string $rawToken): ?AccessToken + { + if (empty($rawToken)) { + return null; + } + + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + return $identityModel->getHMACToken($this, $rawToken); + } + + /** + * Given the ID, returns the given access token. + */ + public function getHMACTokenById(int $id): ?AccessToken + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + return $identityModel->getHMACTokenById($id, $this); + } + + // /** + // * Determines whether the user's token grants permissions to $scope. + // * First checks against $this->activeToken, which is set during + // * authentication. If it hasn't been set, returns false. + // */ + // public function tokenCan(string $scope): bool + // { + // if (! $this->currentAccessToken() instanceof AccessToken) { + // return false; + // } + // + // return $this->currentAccessToken()->can($scope); + // } + // + // /** + // * Determines whether the user's token does NOT grant permissions to $scope. + // * First checks against $this->activeToken, which is set during + // * authentication. If it hasn't been set, returns true. + // */ + // public function tokenCant(string $scope): bool + // { + // if (! $this->currentAccessToken() instanceof AccessToken) { + // return true; + // } + // + // return $this->currentAccessToken()->cant($scope); + // } + + /** + * Returns the current HMAC token for the user. + */ + public function currentHMACToken(): ?AccessToken + { + return $this->currentAccessToken; + } + + /** + * Sets the current active token for this user. + * + * @return $this + */ + public function setHMACToken(?AccessToken $accessToken): self + { + $this->currentAccessToken = $accessToken; + + return $this; + } +} diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 792a8ba83..bf855ee85 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -8,6 +8,7 @@ use CodeIgniter\Shield\Authentication\Actions\ActionInterface; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; +use CodeIgniter\Shield\Authentication\Authenticators\HMAC_SHA256; use CodeIgniter\Shield\Authentication\Authenticators\JWT; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords\CompositionValidator; @@ -134,6 +135,7 @@ class Auth extends BaseConfig public array $authenticators = [ 'tokens' => AccessTokens::class, 'session' => Session::class, + 'hmac' => HMAC_SHA256::class, // 'jwt' => JWT::class, ]; diff --git a/src/Entities/User.php b/src/Entities/User.php index 2c966e424..1b8a326dd 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -8,6 +8,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Traits\HasAccessTokens; +use CodeIgniter\Shield\Authentication\Traits\HasHMACTokens; use CodeIgniter\Shield\Authorization\Traits\Authorizable; use CodeIgniter\Shield\Models\LoginModel; use CodeIgniter\Shield\Models\UserIdentityModel; @@ -28,6 +29,7 @@ class User extends Entity { use Authorizable; use HasAccessTokens; + use HasHMACTokens; use Resettable; use Activatable; use Bannable; diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php new file mode 100644 index 000000000..ad60486f8 --- /dev/null +++ b/src/Filters/HmacAuth.php @@ -0,0 +1,68 @@ +getCookie($sessionConfig->cookieName); + log_message('debug', 'Session Cooky: ' . print_r($sessionCooky, true)); + + $authenticator = auth('HMAC-SHA256')->getAuthenticator(); + + $result = $authenticator->attempt([ + 'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['tokens'] ?? 'Authorization'), + 'body' => file_get_contents('php://input'), + ]); + + if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->tokenCant($arguments[0]))) { + return service('response') + ->setStatusCode(Response::HTTP_UNAUTHORIZED) + ->setJson(['message' => lang('Auth.badToken')]); + } + + if (setting('Auth.recordActiveDate')) { + $authenticator->recordActiveDate(); + } + + // Block inactive users when Email Activation is enabled + $user = $authenticator->getUser(); + if ($user !== null && ! $user->isActivated()) { + $authenticator->logout(); + + return service('response') + ->setStatusCode(Response::HTTP_FORBIDDEN) + ->setJson(['message' => lang('Auth.activationBlocked')]); + } + + return $request; + + } + + /** + * {@inheritDoc} + */ + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void + { + + } +} diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index ee19be499..510de3476 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -6,6 +6,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; +use CodeIgniter\Shield\Authentication\Authenticators\HMAC_SHA256; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Entities\AccessToken; @@ -13,7 +14,9 @@ use CodeIgniter\Shield\Entities\UserIdentity; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Shield\Exceptions\ValidationException; +use Exception; use Faker\Generator; +use ReflectionException; class UserIdentityModel extends BaseModel { @@ -211,6 +214,143 @@ public function getAllAccessTokens(User $user): array ->findAll(); } + // HMAC + /** + * Find and Retrieve the HMAC AccessToken based on Token alone + * + * @return ?AccessToken + */ + public function getHMACTokenByKey(string $key): ?AccessToken + { + return $this + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('secret', $key) + ->asObject(AccessToken::class) + ->first(); + } + + /** + * Generates a new personal access token for the user. + * + * @param string $name Token name + * @param string[] $scopes Permissions the token grants + * + * @throws Exception + * @throws ReflectionException + */ + public function generateHMACToken(User $user, string $name, array $scopes = ['*']): AccessToken + { + $this->checkUserId($user); + + // helper('text'); + + $return = $this->insert([ + 'type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'user_id' => $user->id, + 'name' => $name, + 'secret' => bin2hex(random_bytes(16)), // Key + 'secret2' => bin2hex(random_bytes(16)), // Secret Key + 'extra' => serialize($scopes), + ]); + + $this->checkQueryReturn($return); + + /** @var AccessToken $token */ + return $this + ->asObject(AccessToken::class) + ->find($this->getInsertID()); + } + + /** + * Retrieve Token object for selected HMAC Token. + * Note: These tokens are not hashed as they are considered shared secrets. + * + * @param User $user User Object + * @param string $key HMAC Key String + * + * @return ?AccessToken + */ + public function getHMACToken(User $user, string $key): ?AccessToken + { + $this->checkUserId($user); + + return $this->where('user_id', $user->id) + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('secret', $key) + ->asObject(AccessToken::class) + ->first(); + } + + /** + * Given the ID, returns the given access token. + * + * @param int|string $id + * @param User $user User Object + * + * @return ?AccessToken + */ + public function getHMACTokenById($id, User $user): ?AccessToken + { + $this->checkUserId($user); + + return $this->where('user_id', $user->id) + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('id', $id) + ->asObject(AccessToken::class) + ->first(); + } + + /** + * Retrieve all HMAC tokes for users + * + * @param User $user User object + * + * @return AccessToken[] + */ + public function getAllHMACTokens(User $user): array + { + $this->checkUserId($user); + + return $this + ->where('user_id', $user->id) + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->orderBy($this->primaryKey) + ->asObject(AccessToken::class) + ->findAll(); + } + + /** + * Delete any HMAC tokens for the given key. + * + * @param User $user User object + * @param string $key HMAC Key + */ + public function revokeHMACToken(User $user, string $key): void + { + $this->checkUserId($user); + + $return = $this->where('user_id', $user->id) + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('secret', $key) + ->delete(); + + $this->checkQueryReturn($return); + } + + /** + * Revokes all access tokens for this user. + */ + public function revokeAllHMACTokens(User $user): void + { + $this->checkUserId($user); + + $return = $this->where('user_id', $user->id) + ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->delete(); + + $this->checkQueryReturn($return); + } + /** * Used by 'magic-link'. */ @@ -351,7 +491,7 @@ public function forceMultiplePasswordReset(array $userIds): void /** * Force global password reset. * This is useful for enforcing a password reset - * for ALL users incase of a security breach. + * for ALL users in case of a security breach. */ public function forceGlobalPasswordReset(): void { From f6341728f70037030b8f9b81a9f112edc489ae20 Mon Sep 17 00:00:00 2001 From: tswagger Date: Mon, 14 Aug 2023 21:01:18 -0500 Subject: [PATCH 198/401] Authenticator test pass --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d5c97a907..676c7bb2c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,11 @@ These are much like the access codes that GitHub uses, where they are unique to can have more than one. This can be used for API authentication of third-party users, and even for allowing access for a mobile application that you build. +### HMAC - SHA256 + +This is a slightly more complicated improvement on Access Codes/Tokens. The main advantage with HMAC is the shared Secret Key +is not passed in the request, but is instead used to create a hash signature of the request body. + ### JSON Web Tokens JWT or JSON Web Token is a compact and self-contained way of securely transmitting @@ -46,7 +51,7 @@ and authorization purposes in web applications. * Session-based authentication (traditional email/password with remember me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration -* Optional Email-based Two Factor Authentication after login +* Optional Email-based Two-Factor Authentication after login * Magic Login Links when a user forgets their password * Flexible groups-based access control (think roles, but more flexible) * Users can be granted additional permissions From 52b5d8be70bfa376ba07dcb6ba471707ea5425b4 Mon Sep 17 00:00:00 2001 From: tswagger Date: Mon, 14 Aug 2023 21:09:46 -0500 Subject: [PATCH 199/401] Authenticator test pass --- .../Authenticators/HMAC_SHA256.php | 71 +++++- src/Authentication/Traits/HasHMACTokens.php | 62 ++--- src/Filters/HmacAuth.php | 5 - .../Authenticators/HMACAuthenticatorTest.php | 238 ++++++++++++++++++ 4 files changed, 329 insertions(+), 47 deletions(-) create mode 100644 tests/Authentication/Authenticators/HMACAuthenticatorTest.php diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php index f79c22b10..125bd547c 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -74,8 +74,8 @@ public function attempt(array $credentials): Result ]); } - $user = $user->setAccessToken( - $user->getAccessToken($this->getBearerToken()) + $user = $user->setHMACToken( + $user->getHMACToken($this->getAuthKeyFromToken()) ); $this->login($user); @@ -108,12 +108,12 @@ public function check(array $credentials): Result ]); } - if (strpos($credentials['token'], 'HMAC-SHA256') === 0) { - $credentials['token'] = trim(substr($credentials['token'], 11)); + if (strpos($credentials['token'], 'Bearer') === 0) { + $credentials['token'] = trim(substr($credentials['token'], 6)); } // Extract UserToken and HMACSHA256 Signature from Authorization token - [$userToken, $signature] = preg_split('/:/', $credentials['token'], -1, PREG_SPLIT_NO_EMPTY); + [$userToken, $signature] = $this->getHMACAuthTokens($credentials['token']); /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); @@ -157,7 +157,7 @@ public function check(array $credentials): Result // Ensure the token is set as the current token $user = $token->user(); - $user->setAccessToken($token); + $user->setHMACToken($token); return new Result([ 'success' => true, @@ -207,8 +207,8 @@ public function loginById($userId): void throw AuthenticationException::forInvalidUser(); } - $user->setAccessToken( - $user->getAccessToken($this->getBearerToken()) + $user->setHMACToken( + $user->getHMACToken($this->getAuthKeyFromToken()) ); $this->login($user); @@ -223,7 +223,7 @@ public function logout(): void } /** - * Returns the currently logged in user. + * Returns the currently logged-in user. */ public function getUser(): ?User { @@ -231,9 +231,11 @@ public function getUser(): ?User } /** - * Returns the Bearer token from the Authorization header + * Returns the Full Authorization token from the Authorization header + * + * @return ?string */ - public function getBearerToken(): ?string + public function getFullAuthToken(): ?string { /** @var IncomingRequest $request */ $request = service('request'); @@ -244,7 +246,52 @@ public function getBearerToken(): ?string return null; } - return trim(substr($header, 6)); // 'Bearer' + return trim(substr($header, 11)); // 'HMAC-SHA256' + } + + /** + * Get Key and HMAC hash from Auth token + * + * @param ?string $fullToken Full Token + * + * @return ?array [key, hmacHash] + */ + public function getHMACAuthTokens(?string $fullToken = null): ?array + { + if (! isset($fullToken)) { + $fullToken = $this->getFullAuthToken(); + } + + if (isset($fullToken)) { + return preg_split('/:/', $fullToken, -1, PREG_SPLIT_NO_EMPTY); + } + + return null; + + } + + /** + * Retrieve the key from the Auth token + * + * @return ?string + */ + public function getAuthKeyFromToken(): ?string + { + [$key, $hmacHash] = $this->getHMACAuthTokens(); + + return $key; + } + + /** + * Retrieve the HMAC Hash from the Auth token + * + * @return ?string + */ + public function getHMACHashFromToken(): ?string + { + [$key, $hmacHash] = $this->getHMACAuthTokens(); + + return $hmacHash; } /** diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHMACTokens.php index 12973de6a..6f6fe6626 100644 --- a/src/Authentication/Traits/HasHMACTokens.php +++ b/src/Authentication/Traits/HasHMACTokens.php @@ -21,7 +21,7 @@ trait HasHMACTokens /** * The current access token for the user. */ - private ?AccessToken $currentAccessToken = null; + private ?AccessToken $currentHMACToken = null; /** * Generates a new personal HMAC token for this user. @@ -75,18 +75,18 @@ public function hmacTokens(): array } /** - * Given a raw token, it will locate it within the system. + * Given a secret Key, it will locate it within the system. */ - public function getHmacToken(?string $rawToken): ?AccessToken + public function getHmacToken(?string $secretKey): ?AccessToken { - if (empty($rawToken)) { + if (empty($secretKey)) { return null; } /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->getHMACToken($this, $rawToken); + return $identityModel->getHMACToken($this, $secretKey); } /** @@ -100,40 +100,42 @@ public function getHMACTokenById(int $id): ?AccessToken return $identityModel->getHMACTokenById($id, $this); } - // /** - // * Determines whether the user's token grants permissions to $scope. - // * First checks against $this->activeToken, which is set during - // * authentication. If it hasn't been set, returns false. - // */ - // public function tokenCan(string $scope): bool - // { - // if (! $this->currentAccessToken() instanceof AccessToken) { - // return false; + // Commented out as it collides with methods from CodeIgniter\Shield\Authentication\Traits\HasHMACTokens + + // /** + // * Determines whether the user's token grants permissions to $scope. + // * First checks against $this->activeToken, which is set during + // * authentication. If it hasn't been set, returns false. + // */ + // public function tokenCan(string $scope): bool + // { + // if (! $this->currentAccessToken() instanceof AccessToken) { + // return false; + // } + // + // return $this->currentAccessToken()->can($scope); // } // - // return $this->currentAccessToken()->can($scope); - // } + // /** + // * Determines whether the user's token does NOT grant permissions to $scope. + // * First checks against $this->activeToken, which is set during + // * authentication. If it hasn't been set, returns true. + // */ + // public function tokenCant(string $scope): bool + // { + // if (! $this->currentAccessToken() instanceof AccessToken) { + // return true; + // } // - // /** - // * Determines whether the user's token does NOT grant permissions to $scope. - // * First checks against $this->activeToken, which is set during - // * authentication. If it hasn't been set, returns true. - // */ - // public function tokenCant(string $scope): bool - // { - // if (! $this->currentAccessToken() instanceof AccessToken) { - // return true; + // return $this->currentAccessToken()->cant($scope); // } - // - // return $this->currentAccessToken()->cant($scope); - // } /** * Returns the current HMAC token for the user. */ public function currentHMACToken(): ?AccessToken { - return $this->currentAccessToken; + return $this->currentHMACToken; } /** @@ -143,7 +145,7 @@ public function currentHMACToken(): ?AccessToken */ public function setHMACToken(?AccessToken $accessToken): self { - $this->currentAccessToken = $accessToken; + $this->currentHMACToken = $accessToken; return $this; } diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index ad60486f8..6ffdc708c 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -22,11 +22,6 @@ class HmacAuth implements FilterInterface public function before(RequestInterface $request, $arguments = null) { - $sessionConfig = new \Config\Session(); - - $sessionCooky = $request->getCookie($sessionConfig->cookieName); - log_message('debug', 'Session Cooky: ' . print_r($sessionCooky, true)); - $authenticator = auth('HMAC-SHA256')->getAuthenticator(); $result = $authenticator->attempt([ diff --git a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php new file mode 100644 index 000000000..523c6885c --- /dev/null +++ b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php @@ -0,0 +1,238 @@ +setProvider(model(UserModel::class)); + + /** @var HMAC_SHA256 $authenticator */ + $authenticator = $auth->factory('hmac'); + $this->auth = $authenticator; + + Services::injectMock('events', new MockEvents()); + } + + public function testLogin(): void + { + $user = fake(UserModel::class); + + $this->auth->login($user); + + // Stores the user + $this->assertInstanceOf(User::class, $this->auth->getUser()); + $this->assertSame($user->id, $this->auth->getUser()->id); + } + + public function testLogout(): void + { + // this one's a little odd since it's stateless, but roll with it... + $user = fake(UserModel::class); + + $this->auth->login($user); + $this->assertNotNull($this->auth->getUser()); + + $this->auth->logout(); + $this->assertNull($this->auth->getUser()); + } + + public function testLoginByIdNoToken(): void + { + $user = fake(UserModel::class); + + $this->assertFalse($this->auth->loggedIn()); + + $this->auth->loginById($user->id); + + $this->assertTrue($this->auth->loggedIn()); + $this->assertNull($this->auth->getUser()->currentHMACToken()); + } + + public function testLoginByIdWithToken(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $this->setRequestHeader($rawToken); + + $this->auth->loginById($user->id); + + $this->assertTrue($this->auth->loggedIn()); + $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHMACToken()); + $this->assertSame($token->id, $this->auth->getUser()->currentHMACToken()->id); + } + + public function testLoginByIdWithMultipleTokens(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token1 = $user->generateHMACToken('foo'); + $user->generateHMACToken('bar'); + + $this->setRequestHeader($this->generateRawHeaderToken($token1->secret, $token1->secret2, 'bar')); + + $this->auth->loginById($user->id); + + $this->assertTrue($this->auth->loggedIn()); + $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHMACToken()); + $this->assertSame($token1->id, $this->auth->getUser()->currentHMACToken()->id); + } + + public function testCheckNoToken(): void + { + $result = $this->auth->check([]); + + $this->assertFalse($result->isOK()); + $this->assertSame(lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), $result->reason()); + } + + public function testCheckBadToken(): void + { + $result = $this->auth->check([ + 'token' => 'abc123:lasdkjflksjdflksjdf', + 'body' => 'bar', + ]); + + $this->assertFalse($result->isOK()); + $this->assertSame(lang('Auth.badToken'), $result->reason()); + } + + public function testCheckOldToken(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + /** @var UserIdentityModel $identities */ + $identities = model(UserIdentityModel::class); + $token = $user->generateHMACToken('foo'); + // CI 4.2 uses the Chicago timezone that has Daylight Saving Time, + // so subtracts 1 hour to make sure this test passes. + $token->last_used_at = Time::now()->subYears(1)->subHours(1)->subMinutes(1); + $identities->save($token); + + $result = $this->auth->check([ + 'token' => $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'), + 'body' => 'bar', + ]); + + $this->assertFalse($result->isOK()); + $this->assertSame(lang('Auth.oldToken'), $result->reason()); + } + + public function testCheckSuccess(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $this->seeInDatabase($this->tables['identities'], [ + 'user_id' => $user->id, + 'type' => 'hmac_sha256', + 'last_used_at' => null, + ]); + + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + + $result = $this->auth->check([ + 'token' => $rawToken, + 'body' => 'bar', + ]); + + $this->assertTrue($result->isOK()); + $this->assertInstanceOf(User::class, $result->extraInfo()); + $this->assertSame($user->id, $result->extraInfo()->id); + + $updatedToken = $result->extraInfo()->currentHMACToken(); + $this->assertNotEmpty($updatedToken->last_used_at); + + // Checking token in the same second does not throw "DataException : There is no data to update." + $this->auth->check(['token' => $rawToken, 'body' => 'bar']); + } + + public function testAttemptCannotFindUser(): void + { + $result = $this->auth->attempt([ + 'token' => 'abc123:lsakdjfljsdflkajsfd', + 'body' => 'bar', + ]); + + $this->assertInstanceOf(Result::class, $result); + $this->assertFalse($result->isOK()); + $this->assertSame(lang('Auth.badToken'), $result->reason()); + + // A login attempt should have always been recorded + $this->seeInDatabase($this->tables['token_logins'], [ + 'id_type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'identifier' => 'abc123:lsakdjfljsdflkajsfd', + 'success' => 0, + ]); + } + + public function testAttemptSuccess(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $this->setRequestHeader($rawToken); + + $result = $this->auth->attempt([ + 'token' => $rawToken, + 'body' => 'bar', + ]); + + $this->assertInstanceOf(Result::class, $result); + $this->assertTrue($result->isOK()); + + $foundUser = $result->extraInfo(); + $this->assertInstanceOf(User::class, $foundUser); + $this->assertSame($user->id, $foundUser->id); + $this->assertInstanceOf(AccessToken::class, $foundUser->currentHMACToken()); + $this->assertSame($token->token, $foundUser->currentHMACToken()->token); + + // A login attempt should have been recorded + $this->seeInDatabase($this->tables['token_logins'], [ + 'id_type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'identifier' => $rawToken, + 'success' => 1, + ]); + } + + protected function setRequestHeader(string $token): void + { + $request = service('request'); + $request->setHeader('Authorization', 'HMAC-SHA256 ' . $token); + } + + protected function generateRawHeaderToken(string $secret, string $secretKey, string $body): string + { + return $secret . ':' . hash_hmac('sha256', $body, $secretKey); + } +} From 1a7802efc7304362db7dcdbddf5bedbc7ddaf682 Mon Sep 17 00:00:00 2001 From: tswagger Date: Tue, 15 Aug 2023 14:03:34 -0500 Subject: [PATCH 200/401] Added HMAC Filter tests. Pass --- .../Authenticators/HMAC_SHA256.php | 6 +- src/Authentication/Traits/HasHMACTokens.php | 56 ++++---- src/Config/Auth.php | 2 + src/Config/Registrar.php | 2 + src/Filters/HmacAuth.php | 12 +- src/Result.php | 2 +- .../Authenticators/HMACAuthenticatorTest.php | 27 +++- .../Authentication/Filters/HMACFilterTest.php | 135 ++++++++++++++++++ 8 files changed, 202 insertions(+), 40 deletions(-) create mode 100644 tests/Authentication/Filters/HMACFilterTest.php diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php index 125bd547c..577c7266e 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -104,12 +104,12 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), + 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['hmac']]), ]); } - if (strpos($credentials['token'], 'Bearer') === 0) { - $credentials['token'] = trim(substr($credentials['token'], 6)); + if (strpos($credentials['token'], 'HMAC-SHA256') === 0) { + $credentials['token'] = trim(substr($credentials['token'], 11)); // HMAC-SHA256 } // Extract UserToken and HMACSHA256 Signature from Authorization token diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHMACTokens.php index 6f6fe6626..c30d69d4f 100644 --- a/src/Authentication/Traits/HasHMACTokens.php +++ b/src/Authentication/Traits/HasHMACTokens.php @@ -100,35 +100,33 @@ public function getHMACTokenById(int $id): ?AccessToken return $identityModel->getHMACTokenById($id, $this); } - // Commented out as it collides with methods from CodeIgniter\Shield\Authentication\Traits\HasHMACTokens - - // /** - // * Determines whether the user's token grants permissions to $scope. - // * First checks against $this->activeToken, which is set during - // * authentication. If it hasn't been set, returns false. - // */ - // public function tokenCan(string $scope): bool - // { - // if (! $this->currentAccessToken() instanceof AccessToken) { - // return false; - // } - // - // return $this->currentAccessToken()->can($scope); - // } - // - // /** - // * Determines whether the user's token does NOT grant permissions to $scope. - // * First checks against $this->activeToken, which is set during - // * authentication. If it hasn't been set, returns true. - // */ - // public function tokenCant(string $scope): bool - // { - // if (! $this->currentAccessToken() instanceof AccessToken) { - // return true; - // } - // - // return $this->currentAccessToken()->cant($scope); - // } + /** + * Determines whether the user's token grants permissions to $scope. + * First checks against $this->activeToken, which is set during + * authentication. If it hasn't been set, returns false. + */ + public function hmacTokenCan(string $scope): bool + { + if (! $this->currentHMACToken() instanceof AccessToken) { + return false; + } + + return $this->currentHMACToken()->can($scope); + } + + /** + * Determines whether the user's token does NOT grant permissions to $scope. + * First checks against $this->activeToken, which is set during + * authentication. If it hasn't been set, returns true. + */ + public function hmacTokenCant(string $scope): bool + { + if (! $this->currentHMACToken() instanceof AccessToken) { + return true; + } + + return $this->currentHMACToken()->cant($scope); + } /** * Returns the current HMAC token for the user. diff --git a/src/Config/Auth.php b/src/Config/Auth.php index bf855ee85..69cab44a8 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -149,6 +149,7 @@ class Auth extends BaseConfig */ public array $authenticatorHeader = [ 'tokens' => 'Authorization', + 'hmac' => 'Authorization', ]; /** @@ -183,6 +184,7 @@ class Auth extends BaseConfig public array $authenticationChain = [ 'session', 'tokens', + 'hmac', // 'jwt', ]; diff --git a/src/Config/Registrar.php b/src/Config/Registrar.php index 290b036d5..8c366e8cd 100644 --- a/src/Config/Registrar.php +++ b/src/Config/Registrar.php @@ -10,6 +10,7 @@ use CodeIgniter\Shield\Filters\ChainAuth; use CodeIgniter\Shield\Filters\ForcePasswordResetFilter; use CodeIgniter\Shield\Filters\GroupFilter; +use CodeIgniter\Shield\Filters\HmacAuth; use CodeIgniter\Shield\Filters\JWTAuth; use CodeIgniter\Shield\Filters\PermissionFilter; use CodeIgniter\Shield\Filters\SessionAuth; @@ -26,6 +27,7 @@ public static function Filters(): array 'aliases' => [ 'session' => SessionAuth::class, 'tokens' => TokenAuth::class, + 'hmac' => HmacAuth::class, 'chain' => ChainAuth::class, 'auth-rates' => AuthRates::class, 'group' => GroupFilter::class, diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index 6ffdc708c..4797bb09b 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -22,14 +22,16 @@ class HmacAuth implements FilterInterface public function before(RequestInterface $request, $arguments = null) { - $authenticator = auth('HMAC-SHA256')->getAuthenticator(); + $authenticator = auth('hmac')->getAuthenticator(); - $result = $authenticator->attempt([ - 'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['tokens'] ?? 'Authorization'), + $requestParams = [ + 'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['hmac'] ?? 'Authorization'), 'body' => file_get_contents('php://input'), - ]); + ]; - if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->tokenCant($arguments[0]))) { + $result = $authenticator->attempt($requestParams); + + if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->hmacTokenCant($arguments[0]))) { return service('response') ->setStatusCode(Response::HTTP_UNAUTHORIZED) ->setJson(['message' => lang('Auth.badToken')]); diff --git a/src/Result.php b/src/Result.php index fa0ce1b37..9a3f368ca 100644 --- a/src/Result.php +++ b/src/Result.php @@ -13,7 +13,7 @@ class Result /** * Provides a simple explanation of * the error that happened. - * Typically a single sentence. + * Typically, a single sentence. */ protected ?string $reason = null; diff --git a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php index 523c6885c..c19f700d7 100644 --- a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php @@ -111,10 +111,10 @@ public function testCheckNoToken(): void $result = $this->auth->check([]); $this->assertFalse($result->isOK()); - $this->assertSame(lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), $result->reason()); + $this->assertSame(lang('Auth.noToken', [config('Auth')->authenticatorHeader['hmac']]), $result->reason()); } - public function testCheckBadToken(): void + public function testCheckBadSignature(): void { $result = $this->auth->check([ 'token' => 'abc123:lasdkjflksjdflksjdf', @@ -176,6 +176,29 @@ public function testCheckSuccess(): void $this->auth->check(['token' => $rawToken, 'body' => 'bar']); } + public function testCheckBadToken(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $this->seeInDatabase($this->tables['identities'], [ + 'user_id' => $user->id, + 'type' => 'hmac_sha256', + 'last_used_at' => null, + ]); + + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'foobar'); + + $result = $this->auth->check([ + 'token' => $rawToken, + 'body' => 'bar', + ]); + + $this->assertfalse($result->isOK()); + $this->assertSame(lang('Auth.badToken'), $result->reason()); + } + public function testAttemptCannotFindUser(): void { $result = $this->auth->attempt([ diff --git a/tests/Authentication/Filters/HMACFilterTest.php b/tests/Authentication/Filters/HMACFilterTest.php new file mode 100644 index 000000000..5543e8592 --- /dev/null +++ b/tests/Authentication/Filters/HMACFilterTest.php @@ -0,0 +1,135 @@ +call('get', 'protected-route'); + + $result->assertStatus(401); + + $result = $this->get('open-route'); + $result->assertStatus(200); + $result->assertSee('Open'); + } + + public function testFilterSuccess(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, ''); + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $rawToken]) + ->get('protected-route'); + + $result->assertStatus(200); + $result->assertSee('Protected'); + + $this->assertSame($user->id, auth('hmac')->id()); + $this->assertSame($user->id, auth('hmac')->user()->id); + + // User should have the current token set. + $this->assertInstanceOf(AccessToken::class, auth('hmac')->user()->currentHMACToken()); + $this->assertSame($token->id, auth('hmac')->user()->currentHMACToken()->id); + } + + public function testFilterInvalidSignature(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar')]) + ->get('protected-route'); + + $result->assertStatus(401); + } + + public function testRecordActiveDate(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHMACToken('foo'); + + $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + ->get('protected-route'); + + // Last Active should be greater than 'updated_at' column + $this->assertGreaterThan(auth('hmac')->user()->updated_at, auth('hmac')->user()->last_active); + } + + public function testFiltersProtectsWithScopes(): void + { + /** @var User $user1 */ + $user1 = fake(UserModel::class); + $token1 = $user1->generateHMACToken('foo', ['users-read']); + /** @var User $user2 */ + $user2 = fake(UserModel::class); + $token2 = $user2->generateHMACToken('foo', ['users-write']); + + // User 1 should be able to access the route + $result1 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token1->secret, $token1->secret2, '')]) + ->get('protected-user-route'); + + $result1->assertStatus(200); + // Last Active should be greater than 'updated_at' column + $this->assertGreaterThan(auth('hmac')->user()->updated_at, auth('hmac')->user()->last_active); + + // User 2 should NOT be able to access the route + $result2 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token2->secret, $token2->secret2, '')]) + ->get('protected-user-route'); + + $result2->assertStatus(401); + } + + public function testBlocksInactiveUsers(): void + { + /** @var User $user */ + $user = fake(UserModel::class, ['active' => false]); + $token = $user->generateHMACToken('foo'); + + // Activation only required with email activation + setting('Auth.actions', ['register' => null]); + + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + ->get('protected-route'); + + $result->assertStatus(200); + $result->assertSee('Protected'); + + // Now require user activation and try again + setting('Auth.actions', ['register' => '\CodeIgniter\Shield\Authentication\Actions\EmailActivator']); + + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + ->get('protected-route'); + + $result->assertStatus(403); + + setting('Auth.actions', ['register' => null]); + } + + protected function generateRawHeaderToken(string $secret, string $secretKey, string $body): string + { + return $secret . ':' . hash_hmac('sha256', $body, $secretKey); + } +} From 1cd518f7ca58f0202a9ac5dfdce78146431e0844 Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 18 Aug 2023 15:25:20 -0500 Subject: [PATCH 201/401] Added documentation Cleaned up naming convention. --- docs/authentication.md | 138 ++++++++++++++++++ docs/guides/api_hmac_keys.md | 113 ++++++++++++++ .../Authenticators/HMAC_SHA256.php | 8 +- src/Authentication/Traits/HasHMACTokens.php | 40 ++--- src/Models/UserIdentityModel.php | 14 +- .../Authenticators/HMACAuthenticatorTest.php | 30 ++-- .../Authentication/Filters/HMACFilterTest.php | 16 +- 7 files changed, 305 insertions(+), 54 deletions(-) create mode 100644 docs/guides/api_hmac_keys.md diff --git a/docs/authentication.md b/docs/authentication.md index 3b177d3dc..6255adeb2 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -303,3 +303,141 @@ if ($user->tokenCant('forums.manage')) { // do something.... } ``` + +## HMAC SHA256 Token Authenticator + +The HMAC-SHA256 authenticator supports the use of revoke-able API keys without using OAuth. This provides +an alternative to a token that is passed in every request and instead uses a shared secret that is used to sign +the request in a secure manner. Like authorization tokens, these are commonly used to provide third-party developers +access to your API. These keys typically have a very long expiration time, often years. + +These are also suitable for use with mobile applications. In this case, the user would register/sign-in +with their email/password. The application would create a new access token for them, with a recognizable +name, like John's iPhone 12, and return it to the mobile application, where it is stored and used +in all future requests. + +> **NOTE:** for the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, +the term "Token" will be used to represent a set of API Keys (key and secretKey). + +### Usage +In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: + +``` +Authorization: HMAC-SHA256 : +``` + +The code to do this will look something like this: + +```php +generateHmacToken('Work Laptop'); +``` + +This creates the keys/tokens using a cryptographically secure random string. The keys opporate as shared keys. +This means they are stored as-is in the database. The method returns an instance of +`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is +the shared 'secredKey'. Both are required to when using this authentication method. + +**The plain text version of these keys should be displayed to the user immediately, so they can copy it for +their use.** It is recommended that after that only the 'key' field is displayed to a user. If a user loses the +'secretKey', they should be required to generate a new set of keys to use. + +```php +$token = $user->generateHmacToken('Work Laptop'); + +echo 'Key: ' . $token->secret; +echo 'SecretKey: ' . $token->secret2; +``` + +### Revoking HMAC Keys + +HMAC keys can be revoked through the `revokeHmacToken()` method. This takes the key as the only +argument. Revoking simply deletes the record from the database. + +```php +$user->revokeHmacToken($key); +``` + +You can revoke all HMAC Keys with the `revokeAllHmacTokens()` method. + +```php +$user->revokeAllHmacTokens(); +``` + +### Retrieving HMAC Keys + +The following methods are available to help you retrieve a user's HMAC keys: + +```php +// Retrieve a set of HMAC Token/Keys by key +$token = $user->getHmacToken($key); + +// Retrieve an HMAC token/keys by its database ID +$token = $user->getHmacTokenById($id); + +// Retrieve all HMAC tokens as an array of AccessToken instances. +$tokens = $user->hmacTokens(); +``` + +### HMAC Keys Lifetime + +HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. +This uses the same configuration value as AccessTokens. + +By default, this is set to 1 year. You can change this value by setting the `accessTokenLifetime` +value in the `Auth` config file. This is in seconds so that you can use the +[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) +that CodeIgniter provides. + +```php +public $unusedTokenLifetime = YEAR; +``` + +### HMAC Keys Scopes + +Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as +permissions the token grants to the user. Scopes are provided when the token is generated and +cannot be modified afterword. + +```php +$token = $user->gererateHMACToken('Work Laptop', ['posts.manage', 'forums.manage']); +``` + +By default, a user is granted a wildcard scope which provides access to all scopes. This is the +same as: + +```php +$token = $user->gererateHMACToken('Work Laptop', ['*']); +``` + +During authentication, the HMAC Keys the user used is stored on the user. Once authenticated, you +can use the `hmacTokenCan()` and `hmacTokenCant()` methods on the user to determine if they have access +to the specified scope. + +```php +if ($user->hmacTokenCan('posts.manage')) { + // do something.... +} + +if ($user->hmacTokenCant('forums.manage')) { + // do something.... +} +``` diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md new file mode 100644 index 000000000..5f430c900 --- /dev/null +++ b/docs/guides/api_hmac_keys.md @@ -0,0 +1,113 @@ +# Protecting an API with HMAC Keys + +> **Note** for the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, + the term "Token" will be used to represent a set of API Keys (key and secretKey). + +HMAC Keys can be used to authenticate users for your own site, or when allowing third-party developers to access your +API. When making requests using HMAC keys, the token should be included in the `Authorization` header as an +`HMAC-SHA256` token. + +> **Note** By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by + setting the `$authenticatorHeader['hmac']` value in the **app/Config/Auth.php** config file. + +Tokens are issued with the `generateHmacToken()` method on the user. This returns a +`CodeIgniter\Shield\Entities\AccessToken` instance. These shared keys are saved to the database in plain text. The +`AccessToken` object returned when you generate it will include a `secret` field which will be the `key` and a `secret2` +field that will be the `secretKey`. You should display the `secretKey` to your user once, so they have a chance to copy +it somewhere safe, as this is the only time you should reveal this key. + +The `generateHmacToken()` method requires a name for the token. These are free strings and are often used to identify +the user/device the token was generated from/for, like 'Johns MacBook Air'. + +```php +$routes->get('/hmac/token', static function() { + $token = auth()->user()->generateHmacToken(service('request')->getVar('token_name')); + + return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); +}); +``` + +You can access all the user's HMAC keys with the `hmacTokens()` method on that user. + +```php +$tokens = $user->hmacTokens(); +foreach($tokens as $token) { + // +} +``` + +### Usage +In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: + +``` +Authorization: HMAC-SHA256 : +``` + +The code to do this will look something like this: + +```php +generateHmacToken('token-name', ['users-read']); +return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); +``` + +> **Note** +> At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being +> correctly recognized. + +When handling incoming requests you can check if the token has been granted access to the scope with the `hmacTokenCan()` method. + +```php +if ($user->hmacTokenCan('users-read')) { + // +} +``` + +### Revoking Keys/Tokens + +Tokens can be revoked by deleting them from the database with the `revokeHmacToken($key)` or `revokeAllHmacTokens()` methods. + +```php +$user->revokeHmacToken($key); +$user->revokeAllHmacTokens(); +``` + +## Protecting Routes + +The first way to specify which routes are protected is to use the `hmac` controller filter. + +For example, to ensure it protects all routes under the `/api` route group, you would use the `$filters` setting +on **app/Config/Filters.php**. + +```php +public $filters = [ + 'tokens' => ['before' => ['api/*']], +]; +``` + +You can also specify the filter should run on one or more routes within the routes file itself: + +```php +$routes->group('api', ['filter' => 'tokens'], function($routes) { + // +}); +$routes->get('users', 'UserController::list', ['filter' => 'hmac:users-read']); +``` + +When the filter runs, it checks the `Authorization` header for a `HMAC-SHA256` value that has the computed token. It then +parses the raw token and looks it up the `key` portion in the database. Once found, it will rehash the body of the request +to validate the remainder of the Authorization raw token. If it passes the signature test it can determine the correct user, +which will then be available through an `auth()->user()` call. + +> **Note** +> Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked. diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php index 577c7266e..1ddc2a413 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -74,8 +74,8 @@ public function attempt(array $credentials): Result ]); } - $user = $user->setHMACToken( - $user->getHMACToken($this->getAuthKeyFromToken()) + $user = $user->setHmacToken( + $user->getHmacToken($this->getAuthKeyFromToken()) ); $this->login($user); @@ -157,7 +157,7 @@ public function check(array $credentials): Result // Ensure the token is set as the current token $user = $token->user(); - $user->setHMACToken($token); + $user->setHmacToken($token); return new Result([ 'success' => true, @@ -207,7 +207,7 @@ public function loginById($userId): void throw AuthenticationException::forInvalidUser(); } - $user->setHMACToken( + $user->setHmacToken( $user->getHMACToken($this->getAuthKeyFromToken()) ); diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHMACTokens.php index c30d69d4f..d6eb2df24 100644 --- a/src/Authentication/Traits/HasHMACTokens.php +++ b/src/Authentication/Traits/HasHMACTokens.php @@ -21,7 +21,7 @@ trait HasHMACTokens /** * The current access token for the user. */ - private ?AccessToken $currentHMACToken = null; + private ?AccessToken $currentHmacToken = null; /** * Generates a new personal HMAC token for this user. @@ -31,34 +31,34 @@ trait HasHMACTokens * * @throws ReflectionException */ - public function generateHMACToken(string $name, array $scopes = ['*']): AccessToken + public function generateHmacToken(string $name, array $scopes = ['*']): AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->generateHMACToken($this, $name, $scopes); + return $identityModel->generateHmacToken($this, $name, $scopes); } /** - * Delete any HMAC tokens for the given raw token. + * Delete any HMAC tokens for the given key. */ - public function revokeHMACToken(string $rawToken): void + public function revokeHmacToken(string $key): void { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - $identityModel->revokeHMACToken($this, $rawToken); + $identityModel->revokeHmacToken($this, $key); } /** * Revokes all HMAC tokens for this user. */ - public function revokeAllHMACTokens(): void + public function revokeAllHmacTokens(): void { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - $identityModel->revokeAllHMACTokens($this); + $identityModel->revokeAllHmacTokens($this); } /** @@ -71,7 +71,7 @@ public function hmacTokens(): array /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->getAllHMACTokens($this); + return $identityModel->getAllHmacTokens($this); } /** @@ -86,18 +86,18 @@ public function getHmacToken(?string $secretKey): ?AccessToken /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->getHMACToken($this, $secretKey); + return $identityModel->getHmacToken($this, $secretKey); } /** * Given the ID, returns the given access token. */ - public function getHMACTokenById(int $id): ?AccessToken + public function getHmacTokenById(int $id): ?AccessToken { /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->getHMACTokenById($id, $this); + return $identityModel->getHmacTokenById($id, $this); } /** @@ -107,11 +107,11 @@ public function getHMACTokenById(int $id): ?AccessToken */ public function hmacTokenCan(string $scope): bool { - if (! $this->currentHMACToken() instanceof AccessToken) { + if (! $this->currentHmacToken() instanceof AccessToken) { return false; } - return $this->currentHMACToken()->can($scope); + return $this->currentHmacToken()->can($scope); } /** @@ -121,19 +121,19 @@ public function hmacTokenCan(string $scope): bool */ public function hmacTokenCant(string $scope): bool { - if (! $this->currentHMACToken() instanceof AccessToken) { + if (! $this->currentHmacToken() instanceof AccessToken) { return true; } - return $this->currentHMACToken()->cant($scope); + return $this->currentHmacToken()->cant($scope); } /** * Returns the current HMAC token for the user. */ - public function currentHMACToken(): ?AccessToken + public function currentHmacToken(): ?AccessToken { - return $this->currentHMACToken; + return $this->currentHmacToken; } /** @@ -141,9 +141,9 @@ public function currentHMACToken(): ?AccessToken * * @return $this */ - public function setHMACToken(?AccessToken $accessToken): self + public function setHmacToken(?AccessToken $accessToken): self { - $this->currentHMACToken = $accessToken; + $this->currentHmacToken = $accessToken; return $this; } diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 510de3476..36d7a0671 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -220,7 +220,7 @@ public function getAllAccessTokens(User $user): array * * @return ?AccessToken */ - public function getHMACTokenByKey(string $key): ?AccessToken + public function getHmacTokenByKey(string $key): ?AccessToken { return $this ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) @@ -238,7 +238,7 @@ public function getHMACTokenByKey(string $key): ?AccessToken * @throws Exception * @throws ReflectionException */ - public function generateHMACToken(User $user, string $name, array $scopes = ['*']): AccessToken + public function generateHmacToken(User $user, string $name, array $scopes = ['*']): AccessToken { $this->checkUserId($user); @@ -270,7 +270,7 @@ public function generateHMACToken(User $user, string $name, array $scopes = ['*' * * @return ?AccessToken */ - public function getHMACToken(User $user, string $key): ?AccessToken + public function getHmacToken(User $user, string $key): ?AccessToken { $this->checkUserId($user); @@ -289,7 +289,7 @@ public function getHMACToken(User $user, string $key): ?AccessToken * * @return ?AccessToken */ - public function getHMACTokenById($id, User $user): ?AccessToken + public function getHmacTokenById($id, User $user): ?AccessToken { $this->checkUserId($user); @@ -307,7 +307,7 @@ public function getHMACTokenById($id, User $user): ?AccessToken * * @return AccessToken[] */ - public function getAllHMACTokens(User $user): array + public function getAllHmacTokens(User $user): array { $this->checkUserId($user); @@ -325,7 +325,7 @@ public function getAllHMACTokens(User $user): array * @param User $user User object * @param string $key HMAC Key */ - public function revokeHMACToken(User $user, string $key): void + public function revokeHmacToken(User $user, string $key): void { $this->checkUserId($user); @@ -340,7 +340,7 @@ public function revokeHMACToken(User $user, string $key): void /** * Revokes all access tokens for this user. */ - public function revokeAllHMACTokens(User $user): void + public function revokeAllHmacTokens(User $user): void { $this->checkUserId($user); diff --git a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php index c19f700d7..c8f7fb5ba 100644 --- a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HMACAuthenticatorTest.php @@ -71,14 +71,14 @@ public function testLoginByIdNoToken(): void $this->auth->loginById($user->id); $this->assertTrue($this->auth->loggedIn()); - $this->assertNull($this->auth->getUser()->currentHMACToken()); + $this->assertNull($this->auth->getUser()->currentHmacToken()); } public function testLoginByIdWithToken(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); $this->setRequestHeader($rawToken); @@ -86,24 +86,24 @@ public function testLoginByIdWithToken(): void $this->auth->loginById($user->id); $this->assertTrue($this->auth->loggedIn()); - $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHMACToken()); - $this->assertSame($token->id, $this->auth->getUser()->currentHMACToken()->id); + $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHmacToken()); + $this->assertSame($token->id, $this->auth->getUser()->currentHmacToken()->id); } public function testLoginByIdWithMultipleTokens(): void { /** @var User $user */ $user = fake(UserModel::class); - $token1 = $user->generateHMACToken('foo'); - $user->generateHMACToken('bar'); + $token1 = $user->generateHmacToken('foo'); + $user->generateHmacToken('bar'); $this->setRequestHeader($this->generateRawHeaderToken($token1->secret, $token1->secret2, 'bar')); $this->auth->loginById($user->id); $this->assertTrue($this->auth->loggedIn()); - $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHMACToken()); - $this->assertSame($token1->id, $this->auth->getUser()->currentHMACToken()->id); + $this->assertInstanceOf(AccessToken::class, $this->auth->getUser()->currentHmacToken()); + $this->assertSame($token1->id, $this->auth->getUser()->currentHmacToken()->id); } public function testCheckNoToken(): void @@ -131,7 +131,7 @@ public function testCheckOldToken(): void $user = fake(UserModel::class); /** @var UserIdentityModel $identities */ $identities = model(UserIdentityModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); // CI 4.2 uses the Chicago timezone that has Daylight Saving Time, // so subtracts 1 hour to make sure this test passes. $token->last_used_at = Time::now()->subYears(1)->subHours(1)->subMinutes(1); @@ -150,7 +150,7 @@ public function testCheckSuccess(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $this->seeInDatabase($this->tables['identities'], [ 'user_id' => $user->id, @@ -169,7 +169,7 @@ public function testCheckSuccess(): void $this->assertInstanceOf(User::class, $result->extraInfo()); $this->assertSame($user->id, $result->extraInfo()->id); - $updatedToken = $result->extraInfo()->currentHMACToken(); + $updatedToken = $result->extraInfo()->currentHmacToken(); $this->assertNotEmpty($updatedToken->last_used_at); // Checking token in the same second does not throw "DataException : There is no data to update." @@ -180,7 +180,7 @@ public function testCheckBadToken(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $this->seeInDatabase($this->tables['identities'], [ 'user_id' => $user->id, @@ -222,7 +222,7 @@ public function testAttemptSuccess(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); $this->setRequestHeader($rawToken); @@ -237,8 +237,8 @@ public function testAttemptSuccess(): void $foundUser = $result->extraInfo(); $this->assertInstanceOf(User::class, $foundUser); $this->assertSame($user->id, $foundUser->id); - $this->assertInstanceOf(AccessToken::class, $foundUser->currentHMACToken()); - $this->assertSame($token->token, $foundUser->currentHMACToken()->token); + $this->assertInstanceOf(AccessToken::class, $foundUser->currentHmacToken()); + $this->assertSame($token->token, $foundUser->currentHmacToken()->token); // A login attempt should have been recorded $this->seeInDatabase($this->tables['token_logins'], [ diff --git a/tests/Authentication/Filters/HMACFilterTest.php b/tests/Authentication/Filters/HMACFilterTest.php index 5543e8592..e46d340c4 100644 --- a/tests/Authentication/Filters/HMACFilterTest.php +++ b/tests/Authentication/Filters/HMACFilterTest.php @@ -36,7 +36,7 @@ public function testFilterSuccess(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, ''); $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $rawToken]) @@ -49,15 +49,15 @@ public function testFilterSuccess(): void $this->assertSame($user->id, auth('hmac')->user()->id); // User should have the current token set. - $this->assertInstanceOf(AccessToken::class, auth('hmac')->user()->currentHMACToken()); - $this->assertSame($token->id, auth('hmac')->user()->currentHMACToken()->id); + $this->assertInstanceOf(AccessToken::class, auth('hmac')->user()->currentHmacToken()); + $this->assertSame($token->id, auth('hmac')->user()->currentHmacToken()->id); } public function testFilterInvalidSignature(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar')]) ->get('protected-route'); @@ -69,7 +69,7 @@ public function testRecordActiveDate(): void { /** @var User $user */ $user = fake(UserModel::class); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) ->get('protected-route'); @@ -82,10 +82,10 @@ public function testFiltersProtectsWithScopes(): void { /** @var User $user1 */ $user1 = fake(UserModel::class); - $token1 = $user1->generateHMACToken('foo', ['users-read']); + $token1 = $user1->generateHmacToken('foo', ['users-read']); /** @var User $user2 */ $user2 = fake(UserModel::class); - $token2 = $user2->generateHMACToken('foo', ['users-write']); + $token2 = $user2->generateHmacToken('foo', ['users-write']); // User 1 should be able to access the route $result1 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token1->secret, $token1->secret2, '')]) @@ -106,7 +106,7 @@ public function testBlocksInactiveUsers(): void { /** @var User $user */ $user = fake(UserModel::class, ['active' => false]); - $token = $user->generateHMACToken('foo'); + $token = $user->generateHmacToken('foo'); // Activation only required with email activation setting('Auth.actions', ['register' => null]); From 91adb875db4ebd73b95c27a09eb29c5237105cc1 Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 23 Aug 2023 09:16:52 -0500 Subject: [PATCH 202/401] Added missing helper --- src/Filters/HmacAuth.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index 4797bb09b..b115b84ae 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -24,6 +24,8 @@ public function before(RequestInterface $request, $arguments = null) $authenticator = auth('hmac')->getAuthenticator(); + helper('setting'); + $requestParams = [ 'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['hmac'] ?? 'Authorization'), 'body' => file_get_contents('php://input'), From 50d70e23c9404f48a15bf52b8866523505d24497 Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 23 Aug 2023 11:33:20 -0500 Subject: [PATCH 203/401] Minor syntax fix --- src/Filters/HmacAuth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index b115b84ae..c952e601b 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -28,7 +28,7 @@ public function before(RequestInterface $request, $arguments = null) $requestParams = [ 'token' => $request->getHeaderLine(setting('Auth.authenticatorHeader')['hmac'] ?? 'Authorization'), - 'body' => file_get_contents('php://input'), + 'body' => $request->getBody() ?? '', ]; $result = $authenticator->attempt($requestParams); From 5be8b2cf0d2143b69b0422b1ef4233c6d889629e Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 23 Aug 2023 18:41:47 -0500 Subject: [PATCH 204/401] Codeing standards cleanup --- src/Authentication/Authenticators/HMAC_SHA256.php | 1 - src/Filters/HmacAuth.php | 3 --- src/Models/UserIdentityModel.php | 1 - 3 files changed, 5 deletions(-) diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php index 1ddc2a413..7aa169435 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -267,7 +267,6 @@ public function getHMACAuthTokens(?string $fullToken = null): ?array } return null; - } /** diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index c952e601b..087b4f67f 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -21,7 +21,6 @@ class HmacAuth implements FilterInterface */ public function before(RequestInterface $request, $arguments = null) { - $authenticator = auth('hmac')->getAuthenticator(); helper('setting'); @@ -54,7 +53,6 @@ public function before(RequestInterface $request, $arguments = null) } return $request; - } /** @@ -62,6 +60,5 @@ public function before(RequestInterface $request, $arguments = null) */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void { - } } diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 36d7a0671..20457a70b 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -255,7 +255,6 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' $this->checkQueryReturn($return); - /** @var AccessToken $token */ return $this ->asObject(AccessToken::class) ->find($this->getInsertID()); From 89f38a8c66ffc097610f74deb6bcb9b6d8de1f73 Mon Sep 17 00:00:00 2001 From: tswagger Date: Thu, 24 Aug 2023 15:06:40 -0500 Subject: [PATCH 205/401] Clarified Return statements --- src/Authentication/Authenticators/HMAC_SHA256.php | 8 ++++---- src/Models/UserIdentityModel.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HMAC_SHA256.php index 7aa169435..b09819a00 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HMAC_SHA256.php @@ -195,7 +195,7 @@ public function login(User $user): void /** * Logs a user in based on their ID. * - * @param int|string $userId + * @param int|string $userId User ID * * @throws AuthenticationException */ @@ -233,7 +233,7 @@ public function getUser(): ?User /** * Returns the Full Authorization token from the Authorization header * - * @return ?string + * @return ?string Trimmed Authorization Token from Header */ public function getFullAuthToken(): ?string { @@ -272,7 +272,7 @@ public function getHMACAuthTokens(?string $fullToken = null): ?array /** * Retrieve the key from the Auth token * - * @return ?string + * @return ?string HMAC token key */ public function getAuthKeyFromToken(): ?string { @@ -284,7 +284,7 @@ public function getAuthKeyFromToken(): ?string /** * Retrieve the HMAC Hash from the Auth token * - * @return ?string + * @return ?string HMAC signature */ public function getHMACHashFromToken(): ?string { diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 20457a70b..0ae5ecb36 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -218,7 +218,7 @@ public function getAllAccessTokens(User $user): array /** * Find and Retrieve the HMAC AccessToken based on Token alone * - * @return ?AccessToken + * @return ?AccessToken Full HMAC Access Token object */ public function getHmacTokenByKey(string $key): ?AccessToken { @@ -267,7 +267,7 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' * @param User $user User Object * @param string $key HMAC Key String * - * @return ?AccessToken + * @return ?AccessToken Full HMAC Access Token */ public function getHmacToken(User $user, string $key): ?AccessToken { @@ -286,7 +286,7 @@ public function getHmacToken(User $user, string $key): ?AccessToken * @param int|string $id * @param User $user User Object * - * @return ?AccessToken + * @return ?AccessToken Full HMAC Access Token */ public function getHmacTokenById($id, User $user): ?AccessToken { From 2adcdbddd1965d05daefeebe670d8173dc54d38d Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Mon, 28 Aug 2023 09:24:07 -0500 Subject: [PATCH 206/401] Update docs/guides/api_hmac_keys.md Co-authored-by: Pooya Parsa --- docs/guides/api_hmac_keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 5f430c900..278217b6b 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -91,7 +91,7 @@ on **app/Config/Filters.php**. ```php public $filters = [ - 'tokens' => ['before' => ['api/*']], + 'hmac' => ['before' => ['api/*']], ]; ``` From bbc2496b519bc744d26aeb9f8ebdd0e16ad0f453 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Mon, 28 Aug 2023 09:24:20 -0500 Subject: [PATCH 207/401] Update docs/guides/api_hmac_keys.md Co-authored-by: Pooya Parsa --- docs/guides/api_hmac_keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 278217b6b..df517c2d2 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -98,7 +98,7 @@ public $filters = [ You can also specify the filter should run on one or more routes within the routes file itself: ```php -$routes->group('api', ['filter' => 'tokens'], function($routes) { +$routes->group('api', ['filter' => 'hmac'], function($routes) { // }); $routes->get('users', 'UserController::list', ['filter' => 'hmac:users-read']); From 1243efcdf6c3e6791504f7801722d1df0565c0db Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 30 Aug 2023 14:14:39 -0500 Subject: [PATCH 208/401] Minor typo fix, clarification of key vs secretKey Signed-off-by: tswagger --- src/Authentication/Traits/HasHMACTokens.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHMACTokens.php index d6eb2df24..143955c39 100644 --- a/src/Authentication/Traits/HasHMACTokens.php +++ b/src/Authentication/Traits/HasHMACTokens.php @@ -75,18 +75,18 @@ public function hmacTokens(): array } /** - * Given a secret Key, it will locate it within the system. + * Given an HMAC Key, it will locate it within the system. */ - public function getHmacToken(?string $secretKey): ?AccessToken + public function getHmacToken(?string $key): ?AccessToken { - if (empty($secretKey)) { + if (empty($key)) { return null; } /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - return $identityModel->getHmacToken($this, $secretKey); + return $identityModel->getHmacToken($this, $key); } /** From 498e68574d35dea939ddfc24d5f5c43dda3fb470 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Wed, 30 Aug 2023 20:57:45 -0500 Subject: [PATCH 209/401] Update docs/authentication.md Co-authored-by: kenjis --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index 6255adeb2..b5220c392 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -316,7 +316,7 @@ with their email/password. The application would create a new access token for t name, like John's iPhone 12, and return it to the mobile application, where it is stored and used in all future requests. -> **NOTE:** for the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, +> **Note** For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, the term "Token" will be used to represent a set of API Keys (key and secretKey). ### Usage From ac61035dbe6138051ec477e650673a34bfaafdfe Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 30 Aug 2023 21:16:42 -0500 Subject: [PATCH 210/401] Renamed 'HMAC' in the code to a consistent 'Hmac' Signed-off-by: tswagger --- docs/authentication.md | 6 +++--- .../{HMAC_SHA256.php => HmacSha256.php} | 16 ++++++++-------- .../{HasHMACTokens.php => HasHmacTokens.php} | 4 ++-- src/Config/Auth.php | 4 ++-- src/Entities/User.php | 4 ++-- src/Models/UserIdentityModel.php | 16 ++++++++-------- ...ticatorTest.php => HmacAuthenticatorTest.php} | 12 ++++++------ .../{HMACFilterTest.php => HmacFilterTest.php} | 2 +- 8 files changed, 32 insertions(+), 32 deletions(-) rename src/Authentication/Authenticators/{HMAC_SHA256.php => HmacSha256.php} (94%) rename src/Authentication/Traits/{HasHMACTokens.php => HasHmacTokens.php} (98%) rename tests/Authentication/Authenticators/{HMACAuthenticatorTest.php => HmacAuthenticatorTest.php} (96%) rename tests/Authentication/Filters/{HMACFilterTest.php => HmacFilterTest.php} (98%) diff --git a/docs/authentication.md b/docs/authentication.md index b5220c392..fbe542854 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -336,7 +336,7 @@ header("Authorization: HMAC-SHA256 {$key}:" . hash_hmac('sha256', $requestBody, ### HMAC Keys/API Authentication Using HMAC keys requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or -use the `CodeIgniter\Shield\Authentication\Traits\HasHMACTokens` on your own user model. This trait +use the `CodeIgniter\Shield\Authentication\Traits\HasHmacTokens` on your own user model. This trait provides all the custom methods needed to implement HMAC keys in your application. The necessary database table, `auth_identities`, is created in Shield's only migration class, which must be run before first using any of the features of Shield. @@ -418,14 +418,14 @@ permissions the token grants to the user. Scopes are provided when the token is cannot be modified afterword. ```php -$token = $user->gererateHMACToken('Work Laptop', ['posts.manage', 'forums.manage']); +$token = $user->gererateHmacToken('Work Laptop', ['posts.manage', 'forums.manage']); ``` By default, a user is granted a wildcard scope which provides access to all scopes. This is the same as: ```php -$token = $user->gererateHMACToken('Work Laptop', ['*']); +$token = $user->gererateHmacToken('Work Laptop', ['*']); ``` During authentication, the HMAC Keys the user used is stored on the user. Once authenticated, you diff --git a/src/Authentication/Authenticators/HMAC_SHA256.php b/src/Authentication/Authenticators/HmacSha256.php similarity index 94% rename from src/Authentication/Authenticators/HMAC_SHA256.php rename to src/Authentication/Authenticators/HmacSha256.php index b09819a00..c6cbfb1da 100644 --- a/src/Authentication/Authenticators/HMAC_SHA256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -15,7 +15,7 @@ use CodeIgniter\Shield\Models\UserModel; use CodeIgniter\Shield\Result; -class HMAC_SHA256 implements AuthenticatorInterface +class HmacSha256 implements AuthenticatorInterface { public const ID_TYPE_HMAC_TOKEN = 'hmac_sha256'; @@ -113,12 +113,12 @@ public function check(array $credentials): Result } // Extract UserToken and HMACSHA256 Signature from Authorization token - [$userToken, $signature] = $this->getHMACAuthTokens($credentials['token']); + [$userToken, $signature] = $this->getHmacAuthTokens($credentials['token']); /** @var UserIdentityModel $identityModel */ $identityModel = model(UserIdentityModel::class); - $token = $identityModel->getHMACTokenByKey($userToken); + $token = $identityModel->getHmacTokenByKey($userToken); if ($token === null) { return new Result([ @@ -208,7 +208,7 @@ public function loginById($userId): void } $user->setHmacToken( - $user->getHMACToken($this->getAuthKeyFromToken()) + $user->getHmacToken($this->getAuthKeyFromToken()) ); $this->login($user); @@ -256,7 +256,7 @@ public function getFullAuthToken(): ?string * * @return ?array [key, hmacHash] */ - public function getHMACAuthTokens(?string $fullToken = null): ?array + public function getHmacAuthTokens(?string $fullToken = null): ?array { if (! isset($fullToken)) { $fullToken = $this->getFullAuthToken(); @@ -276,7 +276,7 @@ public function getHMACAuthTokens(?string $fullToken = null): ?array */ public function getAuthKeyFromToken(): ?string { - [$key, $hmacHash] = $this->getHMACAuthTokens(); + [$key, $hmacHash] = $this->getHmacAuthTokens(); return $key; } @@ -286,9 +286,9 @@ public function getAuthKeyFromToken(): ?string * * @return ?string HMAC signature */ - public function getHMACHashFromToken(): ?string + public function getHmacHashFromToken(): ?string { - [$key, $hmacHash] = $this->getHMACAuthTokens(); + [$key, $hmacHash] = $this->getHmacAuthTokens(); return $hmacHash; } diff --git a/src/Authentication/Traits/HasHMACTokens.php b/src/Authentication/Traits/HasHmacTokens.php similarity index 98% rename from src/Authentication/Traits/HasHMACTokens.php rename to src/Authentication/Traits/HasHmacTokens.php index 143955c39..f41accfcc 100644 --- a/src/Authentication/Traits/HasHMACTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -9,14 +9,14 @@ use ReflectionException; /** - * Trait HasHMACTokens + * Trait HasHmacTokens * * Provides functionality needed to generate, revoke, * and retrieve Personal Access Tokens. * * Intended to be used with User entities. */ -trait HasHMACTokens +trait HasHmacTokens { /** * The current access token for the user. diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 69cab44a8..0a3bfa374 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -8,7 +8,7 @@ use CodeIgniter\Shield\Authentication\Actions\ActionInterface; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; -use CodeIgniter\Shield\Authentication\Authenticators\HMAC_SHA256; +use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Authentication\Authenticators\JWT; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords\CompositionValidator; @@ -135,7 +135,7 @@ class Auth extends BaseConfig public array $authenticators = [ 'tokens' => AccessTokens::class, 'session' => Session::class, - 'hmac' => HMAC_SHA256::class, + 'hmac' => HmacSha256::class, // 'jwt' => JWT::class, ]; diff --git a/src/Entities/User.php b/src/Entities/User.php index 1b8a326dd..1aff561ab 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -8,7 +8,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Traits\HasAccessTokens; -use CodeIgniter\Shield\Authentication\Traits\HasHMACTokens; +use CodeIgniter\Shield\Authentication\Traits\HasHmacTokens; use CodeIgniter\Shield\Authorization\Traits\Authorizable; use CodeIgniter\Shield\Models\LoginModel; use CodeIgniter\Shield\Models\UserIdentityModel; @@ -29,7 +29,7 @@ class User extends Entity { use Authorizable; use HasAccessTokens; - use HasHMACTokens; + use HasHmacTokens; use Resettable; use Activatable; use Bannable; diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 0ae5ecb36..71274cd2e 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -6,7 +6,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; -use CodeIgniter\Shield\Authentication\Authenticators\HMAC_SHA256; +use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Entities\AccessToken; @@ -223,7 +223,7 @@ public function getAllAccessTokens(User $user): array public function getHmacTokenByKey(string $key): ?AccessToken { return $this - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->where('secret', $key) ->asObject(AccessToken::class) ->first(); @@ -245,7 +245,7 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' // helper('text'); $return = $this->insert([ - 'type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'type' => HmacSha256::ID_TYPE_HMAC_TOKEN, 'user_id' => $user->id, 'name' => $name, 'secret' => bin2hex(random_bytes(16)), // Key @@ -274,7 +274,7 @@ public function getHmacToken(User $user, string $key): ?AccessToken $this->checkUserId($user); return $this->where('user_id', $user->id) - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->where('secret', $key) ->asObject(AccessToken::class) ->first(); @@ -293,7 +293,7 @@ public function getHmacTokenById($id, User $user): ?AccessToken $this->checkUserId($user); return $this->where('user_id', $user->id) - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->where('id', $id) ->asObject(AccessToken::class) ->first(); @@ -312,7 +312,7 @@ public function getAllHmacTokens(User $user): array return $this ->where('user_id', $user->id) - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->orderBy($this->primaryKey) ->asObject(AccessToken::class) ->findAll(); @@ -329,7 +329,7 @@ public function revokeHmacToken(User $user, string $key): void $this->checkUserId($user); $return = $this->where('user_id', $user->id) - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->where('secret', $key) ->delete(); @@ -344,7 +344,7 @@ public function revokeAllHmacTokens(User $user): void $this->checkUserId($user); $return = $this->where('user_id', $user->id) - ->where('type', HMAC_SHA256::ID_TYPE_HMAC_TOKEN) + ->where('type', HmacSha256::ID_TYPE_HMAC_TOKEN) ->delete(); $this->checkQueryReturn($return); diff --git a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php similarity index 96% rename from tests/Authentication/Authenticators/HMACAuthenticatorTest.php rename to tests/Authentication/Authenticators/HmacAuthenticatorTest.php index c8f7fb5ba..fc0ede3d1 100644 --- a/tests/Authentication/Authenticators/HMACAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -6,7 +6,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authentication; -use CodeIgniter\Shield\Authentication\Authenticators\HMAC_SHA256; +use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; @@ -20,9 +20,9 @@ /** * @internal */ -final class HMACAuthenticatorTest extends DatabaseTestCase +final class HmacAuthenticatorTest extends DatabaseTestCase { - private HMAC_SHA256 $auth; + private HmacSha256 $auth; protected function setUp(): void { @@ -32,7 +32,7 @@ protected function setUp(): void $auth = new Authentication($config); $auth->setProvider(model(UserModel::class)); - /** @var HMAC_SHA256 $authenticator */ + /** @var HmacSha256 $authenticator */ $authenticator = $auth->factory('hmac'); $this->auth = $authenticator; @@ -212,7 +212,7 @@ public function testAttemptCannotFindUser(): void // A login attempt should have always been recorded $this->seeInDatabase($this->tables['token_logins'], [ - 'id_type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'id_type' => HmacSha256::ID_TYPE_HMAC_TOKEN, 'identifier' => 'abc123:lsakdjfljsdflkajsfd', 'success' => 0, ]); @@ -242,7 +242,7 @@ public function testAttemptSuccess(): void // A login attempt should have been recorded $this->seeInDatabase($this->tables['token_logins'], [ - 'id_type' => HMAC_SHA256::ID_TYPE_HMAC_TOKEN, + 'id_type' => HmacSha256::ID_TYPE_HMAC_TOKEN, 'identifier' => $rawToken, 'success' => 1, ]); diff --git a/tests/Authentication/Filters/HMACFilterTest.php b/tests/Authentication/Filters/HmacFilterTest.php similarity index 98% rename from tests/Authentication/Filters/HMACFilterTest.php rename to tests/Authentication/Filters/HmacFilterTest.php index e46d340c4..4a7acb9bc 100644 --- a/tests/Authentication/Filters/HMACFilterTest.php +++ b/tests/Authentication/Filters/HmacFilterTest.php @@ -13,7 +13,7 @@ /** * @internal */ -final class HMACFilterTest extends AbstractFilterTestCase +final class HmacFilterTest extends AbstractFilterTestCase { use DatabaseTestTrait; From c8058f734b7d88268eb1e1a6e4bdc4652efb0ab4 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:32:54 -0500 Subject: [PATCH 211/401] Update docs/authentication.md Co-authored-by: John Paul E. Balandan, CPA --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index fbe542854..c4ad34abf 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -306,7 +306,7 @@ if ($user->tokenCant('forums.manage')) { ## HMAC SHA256 Token Authenticator -The HMAC-SHA256 authenticator supports the use of revoke-able API keys without using OAuth. This provides +The HMAC-SHA256 authenticator supports the use of revocable API keys without using OAuth. This provides an alternative to a token that is passed in every request and instead uses a shared secret that is used to sign the request in a secure manner. Like authorization tokens, these are commonly used to provide third-party developers access to your API. These keys typically have a very long expiration time, often years. From 755a99c50a55601d3ca68b2ecec3e4029f1780de Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:33:06 -0500 Subject: [PATCH 212/401] Update docs/authentication.md Co-authored-by: John Paul E. Balandan, CPA --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index c4ad34abf..c8032f46e 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -323,7 +323,7 @@ the term "Token" will be used to represent a set of API Keys (key and secretKey) In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: ``` -Authorization: HMAC-SHA256 : +Authorization: HMAC-SHA256 : ``` The code to do this will look something like this: From 19d7cea5ac067bb5b6e03ee9d4b99d3aeec74970 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:40:06 -0500 Subject: [PATCH 213/401] Update docs/authentication.md Co-authored-by: John Paul E. Balandan, CPA --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index c8032f46e..921dcaf9c 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -351,7 +351,7 @@ differentiate between multiple tokens. $token = $user->generateHmacToken('Work Laptop'); ``` -This creates the keys/tokens using a cryptographically secure random string. The keys opporate as shared keys. +This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys. This means they are stored as-is in the database. The method returns an instance of `CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is the shared 'secredKey'. Both are required to when using this authentication method. From 72b9a70176cdd504bea66739f7fcdf2ad648ccfd Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:40:16 -0500 Subject: [PATCH 214/401] Update docs/authentication.md Co-authored-by: John Paul E. Balandan, CPA --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index 921dcaf9c..d47ba4afc 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -354,7 +354,7 @@ $token = $user->generateHmacToken('Work Laptop'); This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys. This means they are stored as-is in the database. The method returns an instance of `CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is -the shared 'secredKey'. Both are required to when using this authentication method. +the shared 'secretKey'. Both are required to when using this authentication method. **The plain text version of these keys should be displayed to the user immediately, so they can copy it for their use.** It is recommended that after that only the 'key' field is displayed to a user. If a user loses the From daee37140b7b65e301bcd9e156600ee6c17462fa Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:40:27 -0500 Subject: [PATCH 215/401] Update docs/guides/api_hmac_keys.md Co-authored-by: John Paul E. Balandan, CPA --- docs/guides/api_hmac_keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index df517c2d2..9b801392c 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -20,7 +20,7 @@ The `generateHmacToken()` method requires a name for the token. These are free s the user/device the token was generated from/for, like 'Johns MacBook Air'. ```php -$routes->get('/hmac/token', static function() { +$routes->get('/hmac/token', static function () { $token = auth()->user()->generateHmacToken(service('request')->getVar('token_name')); return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); From 8990cef1932c54405572d79957021fff7c84bcb3 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Thu, 31 Aug 2023 08:40:38 -0500 Subject: [PATCH 216/401] Update docs/guides/api_hmac_keys.md Co-authored-by: John Paul E. Balandan, CPA --- docs/guides/api_hmac_keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 9b801392c..d749eef2c 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -31,7 +31,7 @@ You can access all the user's HMAC keys with the `hmacTokens()` method on that u ```php $tokens = $user->hmacTokens(); -foreach($tokens as $token) { +foreach ($tokens as $token) { // } ``` From 5f56a55e8c83f4ddf6f1868c5e968ba08b7e445f Mon Sep 17 00:00:00 2001 From: tswagger Date: Thu, 31 Aug 2023 09:24:38 -0500 Subject: [PATCH 217/401] Added ToC entries. Signed-off-by: tswagger --- docs/authentication.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index d47ba4afc..3e71aa96a 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -20,6 +20,13 @@ - [Retrieving Access Tokens](#retrieving-access-tokens) - [Access Token Lifetime](#access-token-lifetime) - [Access Token Scopes](#access-token-scopes) + - [HMAC SHA256 Token Authenticator](#hmac-sha256-token-authenticator) + - [HMAC Keys/API Authentication](#hmac-keysapi-authentication) + - [Generating HMAC Access Keys](#generating-hmac-access-keys) + - [Revoking HMAC Keys](#revoking-hmac-keys) + - [Retrieving HMAC Keys](#retrieving-hmac-keys) + - [HMAC Keys Lifetime](#hmac-keys-lifetime) + - [HMAC Keys Scopes](#hmac-keys-scopes) Authentication is the process of determining that a visitor actually belongs to your website, and identifying them. Shield provides a flexible and secure authentication system for your @@ -316,8 +323,8 @@ with their email/password. The application would create a new access token for t name, like John's iPhone 12, and return it to the mobile application, where it is stored and used in all future requests. -> **Note** For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, -the term "Token" will be used to represent a set of API Keys (key and secretKey). +> **Note** For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, +> the term "Token" will be used to represent a set of API Keys (key and secretKey). ### Usage In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: @@ -341,7 +348,7 @@ provides all the custom methods needed to implement HMAC keys in your applicatio database table, `auth_identities`, is created in Shield's only migration class, which must be run before first using any of the features of Shield. -### Generating Access Keys +### Generating HMAC Access Keys Access keys/tokens are created through the `generateHmacToken()` method on the user. This takes a name to give to the token as the first argument. The name is used to display it to the user, so they can From 4b452b9163c52cb84ea3a26ed13e2971bade6ce7 Mon Sep 17 00:00:00 2001 From: tswagger Date: Thu, 31 Aug 2023 12:28:46 -0500 Subject: [PATCH 218/401] Added CURLRequest example Signed-off-by: tswagger --- docs/authentication.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/authentication.md b/docs/authentication.md index 3e71aa96a..5c022c1c3 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -45,6 +45,7 @@ public $authenticators = [ // alias => classname 'session' => Session::class, 'tokens' => AccessTokens::class, + 'hmac' => HmacSha256::class, ]; ``` @@ -337,8 +338,27 @@ The code to do this will look something like this: ```php setHeader('Authorization', "HMAC-SHA256 {$key}:{$hashValue}") + ->setBody($requestBody) + ->request('POST', 'https://example.com/api'); +``` ### HMAC Keys/API Authentication From 547e4559feb41ab784f91d49410538977409cb42 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Tue, 5 Sep 2023 08:56:55 -0500 Subject: [PATCH 219/401] Update docs/guides/api_hmac_keys.md Co-authored-by: kenjis --- docs/guides/api_hmac_keys.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index d749eef2c..8a6053ede 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -1,6 +1,7 @@ # Protecting an API with HMAC Keys -> **Note** for the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, +> **Note** +> For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, the term "Token" will be used to represent a set of API Keys (key and secretKey). HMAC Keys can be used to authenticate users for your own site, or when allowing third-party developers to access your From 65853f7721aad1c478726d3673a1fc1a15f25ba0 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Wed, 6 Sep 2023 12:10:29 -0500 Subject: [PATCH 220/401] Update docs/guides/api_hmac_keys.md Co-authored-by: kenjis --- docs/guides/api_hmac_keys.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 8a6053ede..327cf4ff4 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -8,8 +8,9 @@ HMAC Keys can be used to authenticate users for your own site, or when allowing API. When making requests using HMAC keys, the token should be included in the `Authorization` header as an `HMAC-SHA256` token. -> **Note** By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by - setting the `$authenticatorHeader['hmac']` value in the **app/Config/Auth.php** config file. +> **Note** +> By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by +> setting the `$authenticatorHeader['hmac']` value in the **app/Config/Auth.php** config file. Tokens are issued with the `generateHmacToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. These shared keys are saved to the database in plain text. The From 9f9506dc21c056ac344df2de10005c3574ae5f04 Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 6 Sep 2023 12:37:35 -0500 Subject: [PATCH 221/401] Improved HMAC Docs Signed-off-by: tswagger --- docs/authentication.md | 13 +++++++------ docs/guides/api_hmac_keys.md | 6 +++--- mkdocs.yml | 1 + src/Models/UserIdentityModel.php | 2 -- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index 5c022c1c3..7a5f026e1 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -272,7 +272,7 @@ $tokens = $user->accessTokens(); ### Access Token Lifetime Tokens will expire after a specified amount of time has passed since they have been used. -By default, this is set to 1 year. You can change this value by setting the `accessTokenLifetime` +By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` value in the `Auth` config file. This is in seconds so that you can use the [time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) that CodeIgniter provides. @@ -324,10 +324,12 @@ with their email/password. The application would create a new access token for t name, like John's iPhone 12, and return it to the mobile application, where it is stored and used in all future requests. -> **Note** For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, +> **Note** +> For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, > the term "Token" will be used to represent a set of API Keys (key and secretKey). ### Usage + In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: ``` @@ -337,10 +339,9 @@ Authorization: HMAC-SHA256 : The code to do this will look something like this: ```php -hmacTokens(); HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. This uses the same configuration value as AccessTokens. -By default, this is set to 1 year. You can change this value by setting the `accessTokenLifetime` +By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` value in the `Auth` config file. This is in seconds so that you can use the [time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) that CodeIgniter provides. diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 327cf4ff4..3f9732814 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -1,6 +1,6 @@ # Protecting an API with HMAC Keys -> **Note** +> **Note** > For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, the term "Token" will be used to represent a set of API Keys (key and secretKey). @@ -8,7 +8,7 @@ HMAC Keys can be used to authenticate users for your own site, or when allowing API. When making requests using HMAC keys, the token should be included in the `Authorization` header as an `HMAC-SHA256` token. -> **Note** +> **Note** > By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by > setting the `$authenticatorHeader['hmac']` value in the **app/Config/Auth.php** config file. @@ -39,6 +39,7 @@ foreach ($tokens as $token) { ``` ### Usage + In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: ``` @@ -48,7 +49,6 @@ Authorization: HMAC-SHA256 : The code to do this will look something like this: ```php -checkUserId($user); - // helper('text'); - $return = $this->insert([ 'type' => HmacSha256::ID_TYPE_HMAC_TOKEN, 'user_id' => $user->id, From 7791f8736a82155b870c60314c285c11c3c3f992 Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 6 Sep 2023 14:44:25 -0500 Subject: [PATCH 222/401] Updated phpdpd to suppress errors from HMAC Addition Signed-off-by: tswagger --- .github/workflows/phpcpd.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpcpd.yml b/.github/workflows/phpcpd.yml index f7063e715..2ffa76d70 100644 --- a/.github/workflows/phpcpd.yml +++ b/.github/workflows/phpcpd.yml @@ -33,4 +33,4 @@ jobs: coverage: none - name: Detect duplicate code - run: phpcpd src/ tests/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php + run: phpcpd src/ tests/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php --exclude tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php diff --git a/composer.json b/composer.json index c3759ba90..57d6dba92 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ ], "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff", "cs-fix": "php-cs-fixer fix --ansi --verbose --diff", - "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php", + "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", "sa": "@analyze", From b5d7fa16b7623317bf8cec8b4baf3afb6c81c451 Mon Sep 17 00:00:00 2001 From: tswagger Date: Tue, 12 Sep 2023 12:09:32 -0500 Subject: [PATCH 223/401] Updated login recording to match JWT Authorization Added AuthToken config as a separate config for Token/HMAC auth from JWT Updated test to reflect logging adjustment change. Signed-off-by: tswagger --- .../Authenticators/HmacSha256.php | 53 +++++++++++++------ src/Config/AuthToken.php | 24 +++++++++ .../Authenticators/HmacAuthenticatorTest.php | 2 + 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/Config/AuthToken.php diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index c6cbfb1da..27e85c886 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -8,6 +8,8 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; +use CodeIgniter\Shield\Config\Auth; +use CodeIgniter\Shield\Config\AuthToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; use CodeIgniter\Shield\Models\TokenLoginModel; @@ -42,6 +44,8 @@ public function __construct(UserModel $provider) */ public function attempt(array $credentials): Result { + $config = config(AuthToken::class); + /** @var IncomingRequest $request */ $request = service('request'); @@ -51,14 +55,16 @@ public function attempt(array $credentials): Result $result = $this->check($credentials); if (! $result->isOK()) { - // Always record a login attempt, whether success or not. - $this->loginModel->recordLoginAttempt( - self::ID_TYPE_HMAC_TOKEN, - $credentials['token'] ?? '', - false, - $ipAddress, - $userAgent - ); + if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) { + // Record all failed login attempts. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_HMAC_TOKEN, + $credentials['token'] ?? '', + false, + $ipAddress, + $userAgent + ); + } return $result; } @@ -66,6 +72,18 @@ public function attempt(array $credentials): Result $user = $result->extraInfo(); if ($user->isBanned()) { + if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) { + // Record a banned login attempt. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_HMAC_TOKEN, + $credentials['token'] ?? '', + false, + $ipAddress, + $userAgent, + $user->id + ); + } + $this->user = null; return new Result([ @@ -80,14 +98,17 @@ public function attempt(array $credentials): Result $this->login($user); - $this->loginModel->recordLoginAttempt( - self::ID_TYPE_HMAC_TOKEN, - $credentials['token'] ?? '', - true, - $ipAddress, - $userAgent, - $this->user->id - ); + if ($config->recordLoginAttempt === Auth::RECORD_LOGIN_ATTEMPT_ALL) { + // Record a successful login attempt. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_HMAC_TOKEN, + $credentials['token'] ?? '', + true, + $ipAddress, + $userAgent, + $this->user->id + ); + } return $result; } diff --git a/src/Config/AuthToken.php b/src/Config/AuthToken.php new file mode 100644 index 000000000..3f879e196 --- /dev/null +++ b/src/Config/AuthToken.php @@ -0,0 +1,24 @@ +setProvider(model(UserModel::class)); + Config('AuthToken')->recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_ALL; + /** @var HmacSha256 $authenticator */ $authenticator = $auth->factory('hmac'); $this->auth = $authenticator; From d8e4262d912aec0c6423a476da3fa26ea990ff1c Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Wed, 13 Sep 2023 10:08:42 -0500 Subject: [PATCH 224/401] Update src/Authentication/Authenticators/HmacSha256.php Co-authored-by: kenjis --- src/Authentication/Authenticators/HmacSha256.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index 27e85c886..bbd457497 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -44,7 +44,7 @@ public function __construct(UserModel $provider) */ public function attempt(array $credentials): Result { - $config = config(AuthToken::class); + $config = config('AuthToken'); /** @var IncomingRequest $request */ $request = service('request'); From 6ac224ebc5dff62ffbf973bb8ec953f84e47113f Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 13 Sep 2023 10:43:39 -0500 Subject: [PATCH 225/401] Added HMAC References to installation documentation Signed-off-by: tswagger --- docs/install.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 74f5b3af4..cbd34bb11 100644 --- a/docs/install.md +++ b/docs/install.md @@ -108,7 +108,7 @@ Require it with an explicit version constraint allowing its desired stability. There are a few setup items to do before you can start using Shield in your project. -1. Copy the **Auth.php** and **AuthGroups.php** from **vendor/codeigniter4/shield/src/Config/** into your project's config folder and update the namespace to `Config`. You will also need to have these classes extend the original classes. See the example below. These files contain all the settings, group, and permission information for your application and will need to be modified to meet the needs of your site. +1. Copy the **Auth.php**, **AuthGroups.php**, and **AuthToken.php** from **vendor/codeigniter4/shield/src/Config/** into your project's config folder and update the namespace to `Config`. You will also need to have these classes extend the original classes. See the example below. These files contain all the settings, group, and permission information for your application and will need to be modified to meet the needs of your site. ```php // new file - app/Config/Auth.php @@ -204,6 +204,7 @@ public $aliases = [ // ... 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, + 'hmac' => \CodeIgniter\Shield\Filters\HmacAuth::class, 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, @@ -218,6 +219,7 @@ Filters | Description session and tokens | The `Session` and `AccessTokens` authenticators, respectively. chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). +HMAC | The `HMAC` authenticator. SEE [HMAC Authentication](./guides/api_hmac_keys.md). auth-rates | Provides a good basis for rate limiting of auth-related routes. group | Checks if the user is in one of the groups passed in. permission | Checks if the user has the passed permissions. From 08e0b8ef19f1ed15ea83e9a86ea51df78ce20694 Mon Sep 17 00:00:00 2001 From: tswagger Date: Wed, 13 Sep 2023 11:00:48 -0500 Subject: [PATCH 226/401] Cleaned up table formatting in markdown Signed-off-by: tswagger --- docs/install.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/install.md b/docs/install.md index cbd34bb11..98a9505a7 100644 --- a/docs/install.md +++ b/docs/install.md @@ -214,16 +214,16 @@ public $aliases = [ ]; ``` -Filters | Description ---- | --- -session and tokens | The `Session` and `AccessTokens` authenticators, respectively. -chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. -jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). -HMAC | The `HMAC` authenticator. SEE [HMAC Authentication](./guides/api_hmac_keys.md). -auth-rates | Provides a good basis for rate limiting of auth-related routes. -group | Checks if the user is in one of the groups passed in. -permission | Checks if the user has the passed permissions. -force-reset | Checks if the user requires a password reset. +| Filters | Description | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| session and tokens | The `Session` and `AccessTokens` authenticators, respectively. | +| chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | +| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | +| hmac | The `HMAC` authenticator. SEE [HMAC Authentication](./guides/api_hmac_keys.md). | +| auth-rates | Provides a good basis for rate limiting of auth-related routes. | +| group | Checks if the user is in one of the groups passed in. | +| permission | Checks if the user has the passed permissions. | +| force-reset | Checks if the user requires a password reset. | These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). From bbcf8b5a399ea65accca6a6624db8809fe8ecc33 Mon Sep 17 00:00:00 2001 From: tswagger Date: Thu, 14 Sep 2023 08:27:47 -0500 Subject: [PATCH 227/401] Updated byte size for HMAC Secret Key Signed-off-by: tswagger --- src/Authentication/Authenticators/HmacSha256.php | 1 - src/Config/AuthToken.php | 13 ++++++++++++- src/Models/UserIdentityModel.php | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index bbd457497..4bd409a59 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -9,7 +9,6 @@ use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; use CodeIgniter\Shield\Config\Auth; -use CodeIgniter\Shield\Config\AuthToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; use CodeIgniter\Shield\Models\TokenLoginModel; diff --git a/src/Config/AuthToken.php b/src/Config/AuthToken.php index 3f879e196..2d8254471 100644 --- a/src/Config/AuthToken.php +++ b/src/Config/AuthToken.php @@ -4,10 +4,12 @@ namespace CodeIgniter\Shield\Config; +use CodeIgniter\Config\BaseConfig; + /** * Authenticator Configuration for Token Auth and HMAC Auth */ -class AuthToken +class AuthToken extends BaseConfig { /** * -------------------------------------------------------------------- @@ -21,4 +23,13 @@ class AuthToken * - Auth::RECORD_LOGIN_ATTEMPT_ALL */ public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE; + + /** + * -------------------------------------------------------------------- + * HMAC secret key byte size + * -------------------------------------------------------------------- + * Specify in integer the desired byte size of the + * HMAC SHA256 byte size + */ + public int $hmacSecretKeyByteSize = 32; } diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index e81cb8533..45c5ee4f3 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -247,7 +247,7 @@ public function generateHmacToken(User $user, string $name, array $scopes = ['*' 'user_id' => $user->id, 'name' => $name, 'secret' => bin2hex(random_bytes(16)), // Key - 'secret2' => bin2hex(random_bytes(16)), // Secret Key + 'secret2' => bin2hex(random_bytes(config('AuthToken')->hmacSecretKeyByteSize)), // Secret Key 'extra' => serialize($scopes), ]); From f47891a5cc944d5b5795f2eb65bfc9da1248f76c Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Fri, 15 Sep 2023 14:27:33 -0500 Subject: [PATCH 228/401] Update tests/Authentication/Authenticators/HmacAuthenticatorTest.php Co-authored-by: Pooya Parsa --- tests/Authentication/Authenticators/HmacAuthenticatorTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index cfb4a5139..71f10b8e3 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -43,6 +43,7 @@ protected function setUp(): void public function testLogin(): void { + /** @var User $user */ $user = fake(UserModel::class); $this->auth->login($user); From 10147cf87b275b36ad618b643743e8a31c0b4a82 Mon Sep 17 00:00:00 2001 From: Tim Swagger Date: Fri, 15 Sep 2023 14:27:42 -0500 Subject: [PATCH 229/401] Update tests/Authentication/Authenticators/HmacAuthenticatorTest.php Co-authored-by: Pooya Parsa --- tests/Authentication/Authenticators/HmacAuthenticatorTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index 71f10b8e3..bbd2b1a83 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -67,6 +67,7 @@ public function testLogout(): void public function testLoginByIdNoToken(): void { + /** @var User $user */ $user = fake(UserModel::class); $this->assertFalse($this->auth->loggedIn()); From 2eb3588e8355d53a7817bb2f843f3a61a0cd7921 Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 15 Sep 2023 16:48:51 -0500 Subject: [PATCH 230/401] Initial fix to PHPStan errors Signed-off-by: tswagger --- src/Authentication/Authenticators/HmacSha256.php | 10 +++++----- src/Authentication/Traits/HasHmacTokens.php | 2 +- src/Filters/HmacAuth.php | 6 +++--- .../Authenticators/HmacAuthenticatorTest.php | 7 ++----- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index 4bd409a59..1722bb3c0 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -121,7 +121,7 @@ public function attempt(array $credentials): Result */ public function check(array $credentials): Result { - if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { + if (! array_key_exists('token', $credentials) || $credentials['token'] === '') { return new Result([ 'success' => false, 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['hmac']]), @@ -160,7 +160,7 @@ public function check(array $credentials): Result // Hasn't been used in a long time if ( - $token->last_used_at + isset($token->last_used_at) && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) ) { return new Result([ @@ -192,7 +192,7 @@ public function check(array $credentials): Result */ public function loggedIn(): bool { - if (! empty($this->user)) { + if (isset($this->user)) { return true; } @@ -223,7 +223,7 @@ public function loginById($userId): void { $user = $this->provider->findById($userId); - if (empty($user)) { + if ($user === null) { throw AuthenticationException::forInvalidUser(); } @@ -262,7 +262,7 @@ public function getFullAuthToken(): ?string $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); - if (empty($header)) { + if ($header === '') { return null; } diff --git a/src/Authentication/Traits/HasHmacTokens.php b/src/Authentication/Traits/HasHmacTokens.php index f41accfcc..435a19bd2 100644 --- a/src/Authentication/Traits/HasHmacTokens.php +++ b/src/Authentication/Traits/HasHmacTokens.php @@ -79,7 +79,7 @@ public function hmacTokens(): array */ public function getHmacToken(?string $key): ?AccessToken { - if (empty($key)) { + if (! isset($key) || $key === '') { return null; } diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index 087b4f67f..c67c6ef97 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -32,10 +32,10 @@ public function before(RequestInterface $request, $arguments = null) $result = $authenticator->attempt($requestParams); - if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->hmacTokenCant($arguments[0]))) { + if (! $result->isOK() || ($arguments !== null && count($arguments) > 0 && $result->extraInfo()->hmacTokenCant($arguments[0]))) { return service('response') ->setStatusCode(Response::HTTP_UNAUTHORIZED) - ->setJson(['message' => lang('Auth.badToken')]); + ->setJSON(['message' => lang('Auth.badToken')]); } if (setting('Auth.recordActiveDate')) { @@ -49,7 +49,7 @@ public function before(RequestInterface $request, $arguments = null) return service('response') ->setStatusCode(Response::HTTP_FORBIDDEN) - ->setJson(['message' => lang('Auth.activationBlocked')]); + ->setJSON(['message' => lang('Auth.activationBlocked')]); } return $request; diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index bbd2b1a83..940cfadd4 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -12,7 +12,6 @@ use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; -use CodeIgniter\Shield\Result; use CodeIgniter\Test\Mock\MockEvents; use Config\Services; use Tests\Support\DatabaseTestCase; @@ -32,7 +31,7 @@ protected function setUp(): void $auth = new Authentication($config); $auth->setProvider(model(UserModel::class)); - Config('AuthToken')->recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_ALL; + config('AuthToken')->recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_ALL; /** @var HmacSha256 $authenticator */ $authenticator = $auth->factory('hmac'); @@ -199,7 +198,7 @@ public function testCheckBadToken(): void 'body' => 'bar', ]); - $this->assertfalse($result->isOK()); + $this->assertFalse($result->isOK()); $this->assertSame(lang('Auth.badToken'), $result->reason()); } @@ -210,7 +209,6 @@ public function testAttemptCannotFindUser(): void 'body' => 'bar', ]); - $this->assertInstanceOf(Result::class, $result); $this->assertFalse($result->isOK()); $this->assertSame(lang('Auth.badToken'), $result->reason()); @@ -235,7 +233,6 @@ public function testAttemptSuccess(): void 'body' => 'bar', ]); - $this->assertInstanceOf(Result::class, $result); $this->assertTrue($result->isOK()); $foundUser = $result->extraInfo(); From 37c9f82d5cc313e06e2de3c2fa1af539cd55f88d Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 15 Sep 2023 18:22:14 -0500 Subject: [PATCH 231/401] Syntax adjustment Signed-off-by: tswagger --- src/Filters/HmacAuth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filters/HmacAuth.php b/src/Filters/HmacAuth.php index c67c6ef97..f655d0264 100644 --- a/src/Filters/HmacAuth.php +++ b/src/Filters/HmacAuth.php @@ -32,7 +32,7 @@ public function before(RequestInterface $request, $arguments = null) $result = $authenticator->attempt($requestParams); - if (! $result->isOK() || ($arguments !== null && count($arguments) > 0 && $result->extraInfo()->hmacTokenCant($arguments[0]))) { + if (! $result->isOK() || ($arguments !== null && $arguments !== [] && $result->extraInfo()->hmacTokenCant($arguments[0]))) { return service('response') ->setStatusCode(Response::HTTP_UNAUTHORIZED) ->setJSON(['message' => lang('Auth.badToken')]); From 58b604238a1082b12ed83b4a6a6de229790dafba Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 15 Sep 2023 19:18:42 -0500 Subject: [PATCH 232/401] Added additional test Signed-off-by: tswagger --- tests/Authentication/HasHmacTokensTest.php | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 tests/Authentication/HasHmacTokensTest.php diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php new file mode 100644 index 000000000..35f0b7153 --- /dev/null +++ b/tests/Authentication/HasHmacTokensTest.php @@ -0,0 +1,151 @@ +user = fake(UserModel::class); + $this->db->table($this->tables['identities'])->truncate(); + } + + public function testGenerateHmacToken(): void + { + $token = $this->user->generateHmacToken('foo'); + + $this->assertSame('foo', $token->name); + $this->assertNull($token->expires); + + $this->assertIsString($token->secret); + $this->assertIsString($token->secret2); + + // All scopes are assigned by default via wildcard + $this->assertSame(['*'], $token->scopes); + } + + public function testHmacTokens(): void + { + // Should return empty array when none exist + $this->assertSame([], $this->user->accessTokens()); + + // Give the user a couple of access tokens + /** @var AccessToken $token1 */ + $token1 = fake( + UserIdentityModel::class, + ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key1', 'secret2' => 'secretKey1'] + ); + + /** @var AccessToken $token2 */ + $token2 = fake( + UserIdentityModel::class, + ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key2', 'secret2' => 'secretKey2'] + ); + + /** @var AccessToken[] $tokens */ + $tokens = $this->user->hmacTokens(); + + $this->assertCount(2, $tokens); + $this->assertSame($token1->id, $tokens[0]->id); + $this->assertSame($token1->secret, $tokens[0]->secret); // Key + $this->assertSame($token1->secret2, $tokens[0]->secret2); // Secret Key + $this->assertSame($token2->id, $tokens[1]->id); + $this->assertSame($token2->secret, $tokens[1]->secret); + $this->assertSame($token2->secret2, $tokens[1]->secret2); + } + + public function testGetHmacToken(): void + { + // Should return null when not found + $this->assertNull($this->user->getHmacToken('foo')); + + $token = $this->user->generateHmacToken('foo'); + + $found = $this->user->getHmacToken($token->secret); + + $this->assertInstanceOf(AccessToken::class, $found); + $this->assertSame($token->id, $found->id); + $this->assertSame($token->secret, $found->secret); // Key + $this->assertSame($token->secret2, $found->secret2); // Secret Key + } + + public function testGetHmacTokenById(): void + { + // Should return null when not found + $this->assertNull($this->user->getHmacTokenById(123)); + + $token = $this->user->generateHmacToken('foo'); + $found = $this->user->getHmacTokenById($token->id); + + $this->assertInstanceOf(AccessToken::class, $found); + $this->assertSame($token->id, $found->id); + $this->assertSame($token->secret, $found->secret); // Key + $this->assertSame($token->secret2, $found->secret2); // Secret Key + } + + public function testRevokeHmacToken(): void + { + $token = $this->user->generateHmacToken('foo'); + + $this->assertCount(1, $this->user->hmacTokens()); + + $this->user->revokeHmacToken($token->secret); + + $this->assertCount(0, $this->user->hmacTokens()); + } + + public function testRevokeAllHmacTokens(): void + { + $this->user->generateHmacToken('foo'); + $this->user->generateHmacToken('foo'); + + $this->assertCount(2, $this->user->hmacTokens()); + + $this->user->revokeAllHmacTokens(); + + $this->assertCount(0, $this->user->hmacTokens()); + } + + public function testHmacTokenCanNoTokenSet(): void + { + $this->assertFalse($this->user->hmacTokenCan('foo')); + } + + public function testHmacTokenCanBasics(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $this->user->setHmacToken($token); + + $this->assertTrue($this->user->hmacTokenCan('foo:bar')); + $this->assertFalse($this->user->hmacTokenCan('foo:baz')); + } + + public function testHmacTokenCantNoTokenSet(): void + { + $this->assertTrue($this->user->hmacTokenCant('foo')); + } + + public function testHmacTokenCant(): void + { + $token = $this->user->generateHmacToken('foo', ['foo:bar']); + $this->user->setHmacToken($token); + + $this->assertFalse($this->user->hmacTokenCant('foo:bar')); + $this->assertTrue($this->user->hmacTokenCant('foo:baz')); + } +} From 76baf8063b6c09760fbab230e04eccdf691721ef Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 15 Sep 2023 21:36:37 -0500 Subject: [PATCH 233/401] Added additional tests Signed-off-by: tswagger --- .../Authenticators/HmacSha256.php | 22 ++++---- .../Authenticators/HmacAuthenticatorTest.php | 55 +++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index 1722bb3c0..3e6d7a260 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -92,7 +92,7 @@ public function attempt(array $credentials): Result } $user = $user->setHmacToken( - $user->getHmacToken($this->getAuthKeyFromToken()) + $user->getHmacToken($this->getHmacKeyFromToken()) ); $this->login($user); @@ -228,7 +228,7 @@ public function loginById($userId): void } $user->setHmacToken( - $user->getHmacToken($this->getAuthKeyFromToken()) + $user->getHmacToken($this->getHmacKeyFromToken()) ); $this->login($user); @@ -251,16 +251,16 @@ public function getUser(): ?User } /** - * Returns the Full Authorization token from the Authorization header + * Returns the Full HMAC Authorization token from the Authorization header * * @return ?string Trimmed Authorization Token from Header */ - public function getFullAuthToken(): ?string + public function getFullHmacToken(): ?string { /** @var IncomingRequest $request */ $request = service('request'); - $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); + $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['hmac']); if ($header === '') { return null; @@ -279,7 +279,7 @@ public function getFullAuthToken(): ?string public function getHmacAuthTokens(?string $fullToken = null): ?array { if (! isset($fullToken)) { - $fullToken = $this->getFullAuthToken(); + $fullToken = $this->getFullHmacToken(); } if (isset($fullToken)) { @@ -294,9 +294,9 @@ public function getHmacAuthTokens(?string $fullToken = null): ?array * * @return ?string HMAC token key */ - public function getAuthKeyFromToken(): ?string + public function getHmacKeyFromToken(): ?string { - [$key, $hmacHash] = $this->getHmacAuthTokens(); + [$key, $secretKey] = $this->getHmacAuthTokens(); return $key; } @@ -304,13 +304,13 @@ public function getAuthKeyFromToken(): ?string /** * Retrieve the HMAC Hash from the Auth token * - * @return ?string HMAC signature + * @return ?string HMAC Hash */ public function getHmacHashFromToken(): ?string { - [$key, $hmacHash] = $this->getHmacAuthTokens(); + [$key, $hash] = $this->getHmacAuthTokens(); - return $hmacHash; + return $hash; } /** diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index 940cfadd4..34d0a2c38 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -6,6 +6,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authentication; +use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\Authenticators\HmacSha256; use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\AccessToken; @@ -77,6 +78,23 @@ public function testLoginByIdNoToken(): void $this->assertNull($this->auth->getUser()->currentHmacToken()); } + public function testLoginByIdBadId(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + + $this->assertFalse($this->auth->loggedIn()); + + try { + $this->auth->loginById(0); + } catch (AuthenticationException $e) { + // Failed login + } + + $this->assertFalse($this->auth->loggedIn()); + $this->assertNull($this->auth->getUser()); + } + public function testLoginByIdWithToken(): void { /** @var User $user */ @@ -247,6 +265,43 @@ public function testAttemptSuccess(): void 'identifier' => $rawToken, 'success' => 1, ]); + + // Check get key Method + $key = $this->auth->getHmacKeyFromToken(); + $this->assertSame($token->secret, $key); + + // Check get hash method + [, $hash] = explode(':', $rawToken); + $secretKey = $this->auth->getHmacHashFromToken(); + $this->assertSame($hash, $secretKey); + } + + public function testAttemptBanned(): void + { + /** @var User $user */ + $user = fake(UserModel::class); + $user->ban('Test ban.'); + + $token = $user->generateHmacToken('foo'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $this->setRequestHeader($rawToken); + + $result = $this->auth->attempt([ + 'token' => $rawToken, + 'body' => 'bar', + ]); + + $this->assertFalse($result->isOK()); + + $foundUser = $result->extraInfo(); + $this->assertNull($foundUser); + + // A login attempt should have been recorded + $this->seeInDatabase($this->tables['token_logins'], [ + 'id_type' => HmacSha256::ID_TYPE_HMAC_TOKEN, + 'identifier' => $rawToken, + 'success' => 0, + ]); } protected function setRequestHeader(string $token): void From f57f45fee96e6f637681e33a0b2abd2ae65705b5 Mon Sep 17 00:00:00 2001 From: tswagger Date: Fri, 15 Sep 2023 22:04:33 -0500 Subject: [PATCH 234/401] Fix to test Signed-off-by: tswagger --- tests/Authentication/Authenticators/HmacAuthenticatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index 34d0a2c38..ffb3e6c6d 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -81,7 +81,7 @@ public function testLoginByIdNoToken(): void public function testLoginByIdBadId(): void { /** @var User $user */ - $user = fake(UserModel::class); + fake(UserModel::class); $this->assertFalse($this->auth->loggedIn()); From 968997a730ae2ea9a18f3882ff374d3174a284d9 Mon Sep 17 00:00:00 2001 From: tswagger Date: Sat, 16 Sep 2023 06:29:25 -0500 Subject: [PATCH 235/401] Removed redundant comment Signed-off-by: tswagger --- tests/Authentication/Authenticators/HmacAuthenticatorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index ffb3e6c6d..9ea22d19b 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -80,7 +80,6 @@ public function testLoginByIdNoToken(): void public function testLoginByIdBadId(): void { - /** @var User $user */ fake(UserModel::class); $this->assertFalse($this->auth->loggedIn()); From a1b64dba0175f23e2b96592fcd1f6a920da89ea7 Mon Sep 17 00:00:00 2001 From: tswagger Date: Mon, 18 Sep 2023 09:07:05 -0500 Subject: [PATCH 236/401] Added config copy to Setup script Signed-off-by: tswagger --- src/Commands/Setup.php | 13 +++++++++++++ tests/Commands/SetupTest.php | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index c9ced3efd..ce052b714 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -83,6 +83,7 @@ private function publishConfig(): void { $this->publishConfigAuth(); $this->publishConfigAuthGroups(); + $this->publishConfigAuthToken(); $this->setupHelper(); $this->setupRoutes(); @@ -131,6 +132,18 @@ private function publishConfigAuthGroups(): void $this->copyAndReplace($file, $replaces); } + private function publishConfigAuthToken(): void + { + $file = 'Config/AuthToken.php'; + $replaces = [ + 'namespace CodeIgniter\Shield\Config' => 'namespace Config', + 'use CodeIgniter\\Config\\BaseConfig;' => 'use CodeIgniter\\Shield\\Config\\AuthToken as ShieldAuthToken;', + 'extends BaseConfig' => 'extends ShieldAuthToken', + ]; + + $this->copyAndReplace($file, $replaces); + } + /** * Write a file, catching any exceptions and showing a * nicely formatted error. diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 43225adf9..9a4281715 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -68,6 +68,10 @@ public function testRun(): void $this->assertStringContainsString('namespace Config;', $auth); $this->assertStringContainsString('use CodeIgniter\Shield\Config\Auth as ShieldAuth;', $auth); + $authToken = file_get_contents($appFolder . 'Config/AuthToken.php'); + $this->assertStringContainsString('namespace Config;', $authToken); + $this->assertStringContainsString('use CodeIgniter\Shield\Config\AuthToken as ShieldAuthToken;', $authToken); + $routes = file_get_contents($appFolder . 'Config/Routes.php'); $this->assertStringContainsString('service(\'auth\')->routes($routes);', $routes); @@ -79,6 +83,7 @@ public function testRun(): void $this->assertStringContainsString( ' Created: vfs://root/Config/Auth.php Created: vfs://root/Config/AuthGroups.php + Created: vfs://root/Config/AuthToken.php Updated: vfs://root/Controllers/BaseController.php Updated: vfs://root/Config/Routes.php Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons.', From 9d223a4b4c39970f29a823ba02e273bbef44927f Mon Sep 17 00:00:00 2001 From: tswagger Date: Mon, 18 Sep 2023 09:11:18 -0500 Subject: [PATCH 237/401] Minor fix in docs Signed-off-by: tswagger --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 98a9505a7..c3452f19c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -219,7 +219,7 @@ public $aliases = [ | session and tokens | The `Session` and `AccessTokens` authenticators, respectively. | | chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | | jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | -| hmac | The `HMAC` authenticator. SEE [HMAC Authentication](./guides/api_hmac_keys.md). | +| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | | auth-rates | Provides a good basis for rate limiting of auth-related routes. | | group | Checks if the user is in one of the groups passed in. | | permission | Checks if the user has the passed permissions. | From ebfa46d280b903a7a97b8649403d5841a2317fa5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 14:31:37 +0900 Subject: [PATCH 238/401] docs: add "What is Shield?" --- docs/index.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/index.md b/docs/index.md index 8d1103e7a..c7b11c8ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,35 @@ # Shield Documentation +## What is Shield? + +Shield is an authentication and authorization framework for CodeIgniter 4. While +it does provide a base set of tools that are commonly used in websites, it is +designed to be flexible and easily customizable. + +### Primary Goals + +The primary goals for Shield are: + +1. It must be very flexible and allow developers to extend/override almost any part of it. +2. It must have security at its core. It is an auth lib after all. +3. To cover many auth needs right out of the box, but be simple to add additional functionality to. + +### Important Features + +* Session-based authentication (traditional email/password with remember me) +* Stateless authentication using Personal Access Tokens +* Optional Email verification on account registration +* Optional Email-based Two-Factor Authentication after login +* Magic Login Links when a user forgets their password +* Flexible groups-based access control (think roles, but more flexible) +* Users can be granted additional permissions + +### License + +Shield is licensed under the MIT License - see the [LICENSE](https://github.com/codeigniter4/shield/blob/develop/LICENSE) file for details. + +## Getting Started + * [Installation Guide](install.md) * [Concepts You Need To Know](concepts.md) * [Quick Start Guide](quickstart.md) From d649989923be9a554337c95e65bebd877ef0be5b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 14:32:23 +0900 Subject: [PATCH 239/401] docs: remove TOC It is redundant because TOC is put in the right column. --- docs/auth_actions.md | 4 ---- docs/authentication.md | 28 ---------------------------- docs/authorization.md | 25 ------------------------- docs/banning_users.md | 4 ---- docs/concepts.md | 7 ------- docs/customization.md | 15 --------------- docs/events.md | 10 ---------- docs/forcing_password_reset.md | 8 -------- docs/install.md | 13 ------------- docs/quickstart.md | 27 --------------------------- 10 files changed, 141 deletions(-) diff --git a/docs/auth_actions.md b/docs/auth_actions.md index 4fb2a5866..e7275b0b2 100644 --- a/docs/auth_actions.md +++ b/docs/auth_actions.md @@ -1,9 +1,5 @@ # Authentication Actions -- [Authentication Actions](#authentication-actions) - - [Configuring Actions](#configuring-actions) - - [Defining New Actions](#defining-new-actions) - Authentication Actions are a way to group actions that can happen after login or registration. Shield ships with two actions you can use, and makes it simple for you to define your own. diff --git a/docs/authentication.md b/docs/authentication.md index 7a5f026e1..e87c5d946 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -1,33 +1,5 @@ # Authentication -- [Authentication](#authentication) - - [Available Authenticators](#available-authenticators) - - [Auth Helper](#auth-helper) - - [Authenticator Responses](#authenticator-responses) - - [isOK()](#isok) - - [reason()](#reason) - - [extraInfo()](#extrainfo) - - [Session Authenticator](#session-authenticator) - - [attempt()](#attempt) - - [check()](#check) - - [loggedIn()](#loggedin) - - [logout()](#logout) - - [forget()](#forget) - - [Access Token Authenticator](#access-token-authenticator) - - [Access Token/API Authentication](#access-tokenapi-authentication) - - [Generating Access Tokens](#generating-access-tokens) - - [Revoking Access Tokens](#revoking-access-tokens) - - [Retrieving Access Tokens](#retrieving-access-tokens) - - [Access Token Lifetime](#access-token-lifetime) - - [Access Token Scopes](#access-token-scopes) - - [HMAC SHA256 Token Authenticator](#hmac-sha256-token-authenticator) - - [HMAC Keys/API Authentication](#hmac-keysapi-authentication) - - [Generating HMAC Access Keys](#generating-hmac-access-keys) - - [Revoking HMAC Keys](#revoking-hmac-keys) - - [Retrieving HMAC Keys](#retrieving-hmac-keys) - - [HMAC Keys Lifetime](#hmac-keys-lifetime) - - [HMAC Keys Scopes](#hmac-keys-scopes) - Authentication is the process of determining that a visitor actually belongs to your website, and identifying them. Shield provides a flexible and secure authentication system for your web apps and APIs. diff --git a/docs/authorization.md b/docs/authorization.md index 7265ce6c2..571af598d 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -1,30 +1,5 @@ # Authorization -- [Authorization](#authorization) - - [Defining Available Groups](#defining-available-groups) - - [Default User Group](#default-user-group) - - [Defining Available Permissions](#defining-available-permissions) - - [Assigning Permissions to Groups](#assigning-permissions-to-groups) - - [Authorizing Users](#authorizing-users) - - [can()](#can) - - [inGroup()](#ingroup) - - [hasPermission()](#haspermission) - - [Authorizing via Routes](#authorizing-via-routes) - - [Managing User Permissions](#managing-user-permissions) - - [addPermission()](#addpermission) - - [removePermission()](#removepermission) - - [syncPermissions()](#syncpermissions) - - [getPermissions()](#getpermissions) - - [Managing User Groups](#managing-user-groups) - - [addGroup()](#addgroup) - - [removeGroup()](#removegroup) - - [syncGroups()](#syncgroups) - - [getGroups()](#getgroups) - - [User Activation](#user-activation) - - [Checking Activation Status](#checking-activation-status) - - [Activating a User](#activating-a-user) - - [Deactivating a User](#deactivating-a-user) - Authorization happens once a user has been identified through authentication. It is the process of determining what actions a user is allowed to do within your site. diff --git a/docs/banning_users.md b/docs/banning_users.md index b0a81c4da..b4fea4c40 100644 --- a/docs/banning_users.md +++ b/docs/banning_users.md @@ -2,10 +2,6 @@ Shield provides a way to ban users from your application. This is useful if you need to prevent a user from logging in, or logging them out in the event that they breach your terms of service. -- [Checking if the User is Banned](#check-if-a-user-is-banned) -- [Banning a User](#banning-a-user) -- [Unbanning a User](#unbanning-a-user) -- [Getting the Reason for Ban ](#getting-the-reason-for-ban) ### Check if a User is Banned diff --git a/docs/concepts.md b/docs/concepts.md index eaf57379a..dc1b17452 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -2,13 +2,6 @@ This document covers some of the base concepts used throughout the library. -- [Shield Concepts](#shield-concepts) - - [Repository State](#repository-state) - - [Settings](#settings) - - [User Providers](#user-providers) - - [User Identities](#user-identities) - - [Password Validators](#password-validators) - ## Repository State Shield is designed so that the initial setup of your application can all happen in code with nothing required to be diff --git a/docs/customization.md b/docs/customization.md index 52f6108a3..66e21312e 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -1,20 +1,5 @@ # Customizing Shield -- [Customizing Shield](#customizing-shield) - - [Custom Table Names](#custom-table-names) - - [Route Configuration](#route-configuration) - - [Custom Redirect URLs](#custom-redirect-urls) - - [Customize Login Redirect](#customize-login-redirect) - - [Customize Register Redirect](#customize-register-redirect) - - [Customize Logout Redirect](#customize-logout-redirect) - - [Extending the Controllers](#extending-the-controllers) - - [Integrating Custom View Libraries](#integrating-custom-view-libraries) - - [Custom Validation Rules](#custom-validation-rules) - - [Registration](#registration) - - [Login](#login) - - [Custom User Provider](#custom-user-provider) - - [Custom Login Identifier](#custom-login-identifier) - ## Custom Table Names If you want to change the default table names, you can change the table names diff --git a/docs/events.md b/docs/events.md index c1609f030..b1360de48 100644 --- a/docs/events.md +++ b/docs/events.md @@ -2,16 +2,6 @@ Shield fires off several events during the lifecycle of the application that your code can tap into. -- [Events](#events) - - [Responding to Events](#responding-to-events) - - [Event List](#event-list) - - [register](#register) - - [login](#login) - - [failedLogin](#failedlogin) - - [logout](#logout) - - [magicLogin](#magiclogin) - - [Event Timing](#event-timing) - ## Responding to Events When you want to respond to an event that Shield publishes, you will need to add it to your **app/Config/Events.php** diff --git a/docs/forcing_password_reset.md b/docs/forcing_password_reset.md index 927b3aec8..241af0988 100644 --- a/docs/forcing_password_reset.md +++ b/docs/forcing_password_reset.md @@ -2,14 +2,6 @@ Depending on the scope of your application, there may be times when you'll decide that it is absolutely necessary to force user(s) to reset their password. This practice is common when you find out that users of your application do not use strong passwords OR there is a reasonable suspicion that their passwords have been compromised. This guide provides you with ways to achieve this. -- [Forcing Password Reset](#forcing-password-reset) - - [Available Methods](#available-methods) - - [Check if a User Requires Password Reset](#check-if-a-user-requires-password-reset) - - [Force Password Reset On a User](#force-password-reset-on-a-user) - - [Remove Force Password Reset Flag On a User](#remove-force-password-reset-flag-on-a-user) - - [Force Password Reset On Multiple Users](#force-password-reset-on-multiple-users) - - [Force Password Reset On All Users](#force-password-reset-on-all-users) - ## Available Methods Shield provides a way to enforce password resets throughout your application. The `Resettable` trait on the `User` entity and the `UserIdentityModel` provides the following methods to do so. diff --git a/docs/install.md b/docs/install.md index c3452f19c..ecf9ab3a3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,18 +1,5 @@ # Installation -- [Installation](#installation) - - [Requirements](#requirements) - - [Composer Installation](#composer-installation) - - [Troubleshooting](#troubleshooting) - - [IMPORTANT: composer error](#important-composer-error) - - [Initial Setup](#initial-setup) - - [Command Setup](#command-setup) - - [Manual Setup](#manual-setup) - - [Controller Filters](#controller-filters) - - [Protect All Pages](#protect-all-pages) - - [Rate Limiting](#rate-limiting) - - [Forcing Password Reset](#forcing-password-reset) - These instructions assume that you have already [installed the CodeIgniter 4 app starter](https://codeigniter.com/user_guide/installation/installing_composer.html) as the basis for your new project, set up your **.env** file, and created a database that you can access via the Spark CLI script. ## Requirements diff --git a/docs/quickstart.md b/docs/quickstart.md index 49e25a38f..3281985e9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -4,33 +4,6 @@ Learning any new authentication system can be difficult, especially as they get > **Note** The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. -- [Quick Start Guide](#quick-start-guide) - - [Authentication Flow](#authentication-flow) - - [Configure Config\\Auth](#configure-configauth) - - [Configure Redirect URLs](#configure-redirect-urls) - - [Configure Remember-me Functionality](#configure-remember-me-functionality) - - [Change Access Token Lifetime](#change-access-token-lifetime) - - [Enable Account Activation via Email](#enable-account-activation-via-email) - - [Enable Two-Factor Authentication](#enable-two-factor-authentication) - - [Responding to Magic Link Logins](#responding-to-magic-link-logins) - - [Session Notification](#session-notification) - - [Event](#event) - - [Authorization Flow](#authorization-flow) - - [Configure Config\\AuthGroups](#configure-configauthgroups) - - [Change Available Groups](#change-available-groups) - - [Set the Default Group](#set-the-default-group) - - [Change Available Permissions](#change-available-permissions) - - [Assign Permissions to a Group](#assign-permissions-to-a-group) - - [Assign Permissions to a User](#assign-permissions-to-a-user) - - [Check If a User Has Permission](#check-if-a-user-has-permission) - - [Adding a Group To a User](#adding-a-group-to-a-user) - - [Removing a Group From a User](#removing-a-group-from-a-user) - - [Checking If User Belongs To a Group](#checking-if-user-belongs-to-a-group) - - [Managing Users](#managing-users) - - [Creating Users](#creating-users) - - [Deleting Users](#deleting-users) - - [Editing a User](#editing-a-user) - ## Authentication Flow ### Configure Config\Auth From eb48201f630a66fcfd9eae386eccba3fd31f4eef Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 14:33:23 +0900 Subject: [PATCH 240/401] docs: remove white speaces --- docs/banning_users.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/banning_users.md b/docs/banning_users.md index b4fea4c40..54e1196fd 100644 --- a/docs/banning_users.md +++ b/docs/banning_users.md @@ -2,16 +2,15 @@ Shield provides a way to ban users from your application. This is useful if you need to prevent a user from logging in, or logging them out in the event that they breach your terms of service. +### Check if a User is Banned -### Check if a User is Banned - -You can check if a user is banned using `isBanned()` method on the `User` entity. The method returns a boolean `true`/`false`. +You can check if a user is banned using `isBanned()` method on the `User` entity. The method returns a boolean `true`/`false`. ```php if ($user->isBanned()) { //... } -``` +``` ### Banning a User @@ -26,7 +25,7 @@ $user->ban('Your reason for banning the user here'); ### Unbanning a User -Unbanning a user can be done using the `unBan()` method on the `User` entity. This method will also reset the `status_message` property. +Unbanning a user can be done using the `unBan()` method on the `User` entity. This method will also reset the `status_message` property. ```php $user->unBan(); @@ -38,4 +37,4 @@ The reason for the ban can be obtained user the `getBanMessage()` method on the ```php $user->getBanMessage(); -``` \ No newline at end of file +``` From 0ca67e33eaf27f3c69dfed5952cb4af5824bfc9b Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 14:33:40 +0900 Subject: [PATCH 241/401] docs: add section title --- docs/testing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 27cd64dbe..30766858a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,6 +1,8 @@ # Testing -When performing [HTTP testing](https://codeigniter.com/user_guide/testing/feature.html) in your applications, you +## HTTP Feature Testing + +When performing [HTTP Feature Testing](https://codeigniter.com/user_guide/testing/feature.html) in your applications, you will often need to ensure you are logged in to check security, or simply to access protected locations. Shield provides the `AuthenticationTesting` trait to help you out. Use it within the test class and then you can use the `actingAs()` method that takes a User instance. This user will be logged in during the test. @@ -24,7 +26,7 @@ class ActionsTest extends TestCase ->withSession([ 'auth_action' => Email2FA::class, ])->get('/auth/a/show'); - + $result->assertStatus(200); // Should auto-populate in the form $result->assertSee($this->user->email); From b6020564b5f6c35084035ed154070482c3db9798 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 14:33:55 +0900 Subject: [PATCH 242/401] docs: reorganize TOC --- mkdocs.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 8e3d3140d..ed9048c0f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,18 +38,21 @@ extra_javascript: nav: - Home: index.md - - Installation: install.md - - Concepts: concepts.md - - Quick Start Guide: quickstart.md - - Authentication: authentication.md - - Authorization: authorization.md - - Auth Actions: auth_actions.md - - Events: events.md - - Testing: testing.md - - Customization: customization.md - - Forcing Password Reset: forcing_password_reset.md - - Banning Users: banning_users.md - - session_auth_event_and_logging.md + - Getting Started: + - Installation: install.md + - Concepts: concepts.md + - Quick Start Guide: quickstart.md + - References: + - Authentication: authentication.md + - Authorization: authorization.md + - Auth Actions: auth_actions.md + - Events: events.md + - Testing: testing.md + - Forcing Password Reset: forcing_password_reset.md + - Banning Users: banning_users.md + - session_auth_event_and_logging.md + - Customization: + - customization.md - Guides: - guides/api_hmac_keys.md - guides/api_tokens.md From f9fbb4a48435fcf7d8c9ffd75f32fa7a03c0d2f2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 16:51:16 +0900 Subject: [PATCH 243/401] docs: split quickstart.md --- docs/authenticaton_flow.md | 108 ++++++++++++++ docs/authorization_flow.md | 127 ++++++++++++++++ docs/index.md | 10 +- docs/managing_users.md | 57 ++++++++ docs/quickstart.md | 292 ------------------------------------- mkdocs.yml | 5 +- 6 files changed, 305 insertions(+), 294 deletions(-) create mode 100644 docs/authenticaton_flow.md create mode 100644 docs/authorization_flow.md create mode 100644 docs/managing_users.md delete mode 100644 docs/quickstart.md diff --git a/docs/authenticaton_flow.md b/docs/authenticaton_flow.md new file mode 100644 index 000000000..7827d0ab9 --- /dev/null +++ b/docs/authenticaton_flow.md @@ -0,0 +1,108 @@ +# Authentication Flow + +Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. + +> **Note** +> The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. + +## Configure Config\Auth + +### Configure Redirect URLs + +If you need everyone to redirect to a single URL after login/logout/register actions, you can modify the `Config\Auth::$redirects` array in **app/Config/Auth.php**`** to specify the url to redirect to. + +By default, a successful login or register attempt will all redirect to `/`, while a logout action +will redirect to a [named route](https://codeigniter.com/user_guide/incoming/routing.html#using-named-routes "See routing docs") `login` or a *URI path* `/login`. You can change the default URLs used within the **`**app/Config/Auth.php** config file: + +```php +public array $redirects = [ + 'register' => '/', + 'login' => '/', + 'logout' => 'login', +]; +``` + +> **Note** +> This redirect happens after the specified action is complete. In the case of register or login, it might not happen immediately. For example, if you have any Auth Actions specified, they will be redirected when those actions are completed successfully. If no Auth Actions are specified, they will be redirected immediately after registration or login. + +### Configure Remember-me Functionality + +Remember-me functionality is enabled by default for the `Session` handler. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. + +```php +public array $sessionConfig = [ + 'field' => 'user', + 'allowRemembering' => true, + 'rememberCookieName' => 'remember', + 'rememberLength' => 30 * DAY, +]; +``` + +### Change Access Token Lifetime + +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the `Auth` config file. + +```php +public int $unusedTokenLifetime = YEAR; +``` + +### Enable Account Activation via Email + +> **Note** +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). + +By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the `Auth` config file. + +```php +public array $actions = [ + 'register' => \CodeIgniter\Shield\Authentication\Actions\EmailActivator::class, + 'login' => null, +]; +``` + +### Enable Two-Factor Authentication + +> **Note** +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). + +Turned off by default, Shield's Email-based 2FA can be enabled by specifying the class to use in the `Auth` config file. + +```php +public array $actions = [ + 'register' => null, + 'login' => \CodeIgniter\Shield\Authentication\Actions\Email2FA::class, +]; +``` + +## Responding to Magic Link Logins + +> **Note** +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). + +Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. + +### Session Notification + +You can detect if a user has finished the magic link login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`. + +```php +if (session('magicLogin')) { + return redirect()->route('set_password'); +} +``` + +This value sticks around in the session for 5 minutes. Once you no longer need to take any actions, you might want to delete the value from the session. + +```php +session()->removeTempdata('magicLogin'); +``` + +### Event + +At the same time the above session variable is set, a `magicLogin` [event](https://codeigniter.com/user_guide/extending/events.html) is fired off that you may subscribe to. Note that no data is passed to the event as you can easily grab the current user from the `user()` helper or the `auth()->user()` method. + +```php +Events::on('magicLogin', static function () { + // ... +}); +``` diff --git a/docs/authorization_flow.md b/docs/authorization_flow.md new file mode 100644 index 000000000..568ca5094 --- /dev/null +++ b/docs/authorization_flow.md @@ -0,0 +1,127 @@ +# Authorization Flow + +## Configure Config\AuthGroups + +### Change Available Groups + +The available groups are defined in the **app/Config/AuthGroups.php** config file, under the `$groups` property. Add new entries to the array, or remove existing ones to make them available throughout your application. + +```php +public array $groups = [ + 'superadmin' => [ + 'title' => 'Super Admin', + 'description' => 'Complete control of the site.', + ], + // +]; +``` + +### Set the Default Group + +When a user registers on your site, they are assigned the group specified at `Config\AuthGroups::$defaultGroup`. Change this to one of the keys in the `$groups` array to update this. + +### Change Available Permissions + +The permissions on the site are stored in the `AuthGroups` config file also. Each one is defined by a string that represents a context and a permission, joined with a decimal point. + +```php +public array $permissions = [ + 'admin.access' => 'Can access the sites admin area', + 'admin.settings' => 'Can access the main site settings', + 'users.manage-admins' => 'Can manage other admins', + 'users.create' => 'Can create new non-admin users', + 'users.edit' => 'Can edit existing non-admin users', + 'users.delete' => 'Can delete existing non-admin users', + 'beta.access' => 'Can access beta-level features', +]; +``` + +### Assign Permissions to a Group + +Each group can have its own specific set of permissions. These are defined in `Config\AuthGroups::$matrix`. You can specify each permission by it's full name, or using the context and an asterisk (*) to specify all permissions within that context. + +```php +public array $matrix = [ + 'superadmin' => [ + 'admin.*', + 'users.*', + 'beta.access', + ], + // +]; +``` + +## Assign Permissions to a User + +Permissions can also be assigned directly to a user, regardless of what groups they belong to. This is done programatically on the `User` Entity. + +```php +$user = auth()->user(); + +$user->addPermission('users.create', 'beta.access'); +``` + +This will add all new permissions. You can also sync permissions so that the user ONLY has the given permissions directly assigned to them. Any not in the provided list are removed from the user. + +```php +$user = auth()->user(); + +$user->syncPermissions('users.create', 'beta.access'); +``` + +## Check If a User Has Permission + +When you need to check if a user has a specific permission use the `can()` method on the `User` entity. This method checks permissions within the groups they belong to and permissions directly assigned to the user. + +```php +if (! auth()->user()->can('users.create')) { + return redirect()->back()->with('error', 'You do not have permissions to access that page.'); +} +``` + +> **Note** The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. + +## Adding a Group To a User + +Groups are assigned to a user via the `addGroup()` method. You can pass multiple groups in and they will all be assigned to the user. + +```php +$user = auth()->user(); +$user->addGroup('admin', 'beta'); +``` + +This will add all new groups. You can also sync groups so that the user ONLY belongs to the groups directly assigned to them. Any not in the provided list are removed from the user. + +```php +$user = auth()->user(); +$user->syncGroups('admin', 'beta'); +``` + +## Removing a Group From a User + +Groups are removed from a user via the `removeGroup()` method. Multiple groups may be removed at once by passing all of their names into the method. + +```php +$user = auth()->user(); +$user->removeGroup('admin', 'beta'); +``` + +## Checking If User Belongs To a Group + +You can check if a user belongs to a group with the `inGroup()` method. + +```php +$user = auth()->user(); +if ($user->inGroup('admin')) { + // do something +} +``` + +You can pass more than one group to the method and it will return `true` if the user belongs to any of the specified groups. + +```php +$user = auth()->user(); +if ($user->inGroup('admin', 'beta')) { + // do something +} +``` diff --git a/docs/index.md b/docs/index.md index c7b11c8ee..994283c94 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,7 +32,15 @@ Shield is licensed under the MIT License - see the [LICENSE](https://github.com/ * [Installation Guide](install.md) * [Concepts You Need To Know](concepts.md) -* [Quick Start Guide](quickstart.md) + +## Quick Start Guide + +* [Authentication Flow](authenticaton_flow.md) +* [Authorization Flow](authorization_flow.md) +* [Managing Users](managing_users.md) + +## References + * [Authentication](authentication.md) * [Authorization](authorization.md) * [Auth Actions](auth_actions.md) diff --git a/docs/managing_users.md b/docs/managing_users.md new file mode 100644 index 000000000..e963a3b34 --- /dev/null +++ b/docs/managing_users.md @@ -0,0 +1,57 @@ +# Managing Users + +Since Shield uses a more complex user setup than many other systems, separating [User Identities](concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. + +## Creating Users + +By default, the only values stored in the users table is the username. The first step is to create the user record with the username. If you don't have a username, be sure to set the value to `null` anyway, so that it passes CodeIgniter's empty data check. + +```php +use CodeIgniter\Shield\Entities\User; + +// Get the User Provider (UserModel by default) +$users = auth()->getProvider(); + +$user = new User([ + 'username' => 'foo-bar', + 'email' => 'foo.bar@example.com', + 'password' => 'secret plain text password', +]); +$users->save($user); + +// To get the complete user object with ID, we need to get from the database +$user = $users->findById($users->getInsertID()); + +// Add to default group +$users->addToDefaultGroup($user); +``` + +## Deleting Users + +A user's data can be spread over a few different tables so you might be concerned about how to delete all of the user's data from the system. This is handled automatically at the database level for all information that Shield knows about, through the `onCascade` settings of the table's foreign keys. You can delete a user like any other entity. + +```php +// Get the User Provider (UserModel by default) +$users = auth()->getProvider(); + +$users->delete($user->id, true); +``` + +> **Note** The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. + +## Editing a User + +The `UserModel::save()`, `update()` and `insert()` methods have been modified to ensure that an email or password previously set on the `User` entity will be automatically updated in the correct `UserIdentity` record. + +```php +// Get the User Provider (UserModel by default) +$users = auth()->getProvider(); + +$user = $users->findById(123); +$user->fill([ + 'username' => 'JoeSmith111', + 'email' => 'joe.smith@example.com', + 'password' => 'secret123' +]); +$users->save($user); +``` diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 3281985e9..000000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,292 +0,0 @@ -# Quick Start Guide - -Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. - -> **Note** The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. - -## Authentication Flow - -### Configure Config\Auth - -#### Configure Redirect URLs - -If you need everyone to redirect to a single URL after login/logout/register actions, you can modify the `Config\Auth::$redirects` array in **app/Config/Auth.php**`** to specify the url to redirect to. - -By default, a successful login or register attempt will all redirect to `/`, while a logout action -will redirect to a [named route](https://codeigniter.com/user_guide/incoming/routing.html#using-named-routes "See routing docs") `login` or a *URI path* `/login`. You can change the default URLs used within the **`**app/Config/Auth.php** config file: - -```php -public array $redirects = [ - 'register' => '/', - 'login' => '/', - 'logout' => 'login', -]; -``` - -> **Note** This redirect happens after the specified action is complete. In the case of register or login, it might not happen immediately. For example, if you have any Auth Actions specified, they will be redirected when those actions are completed successfully. If no Auth Actions are specified, they will be redirected immediately after registration or login. - -#### Configure Remember-me Functionality - -Remember-me functionality is enabled by default for the `Session` handler. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. - -```php -public array $sessionConfig = [ - 'field' => 'user', - 'allowRemembering' => true, - 'rememberCookieName' => 'remember', - 'rememberLength' => 30 * DAY, -]; -``` - -#### Change Access Token Lifetime - -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the `Auth` config file. - -```php -public int $unusedTokenLifetime = YEAR; -``` - -#### Enable Account Activation via Email - -> **Note** You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). - -By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the `Auth` config file. - -```php -public array $actions = [ - 'register' => \CodeIgniter\Shield\Authentication\Actions\EmailActivator::class, - 'login' => null, -]; -``` - -#### Enable Two-Factor Authentication - -> **Note** You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). - -Turned off by default, Shield's Email-based 2FA can be enabled by specifying the class to use in the `Auth` config file. - -```php -public array $actions = [ - 'register' => null, - 'login' => \CodeIgniter\Shield\Authentication\Actions\Email2FA::class, -]; -``` - -### Responding to Magic Link Logins - -> **Note** You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). - -Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. - -#### Session Notification - -You can detect if a user has finished the magic link login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`. - -```php -if (session('magicLogin')) { - return redirect()->route('set_password'); -} -``` - -This value sticks around in the session for 5 minutes. Once you no longer need to take any actions, you might want to delete the value from the session. - -```php -session()->removeTempdata('magicLogin'); -``` - -#### Event - -At the same time the above session variable is set, a `magicLogin` [event](https://codeigniter.com/user_guide/extending/events.html) is fired off that you may subscribe to. Note that no data is passed to the event as you can easily grab the current user from the `user()` helper or the `auth()->user()` method. - -```php -Events::on('magicLogin', static function () { - // ... -}); -``` - - -## Authorization Flow - -### Configure Config\AuthGroups - -#### Change Available Groups - -The available groups are defined in the **app/Config/AuthGroups.php** config file, under the `$groups` property. Add new entries to the array, or remove existing ones to make them available throughout your application. - -```php -public array $groups = [ - 'superadmin' => [ - 'title' => 'Super Admin', - 'description' => 'Complete control of the site.', - ], - // -]; -``` - -#### Set the Default Group - -When a user registers on your site, they are assigned the group specified at `Config\AuthGroups::$defaultGroup`. Change this to one of the keys in the `$groups` array to update this. - -#### Change Available Permissions - -The permissions on the site are stored in the `AuthGroups` config file also. Each one is defined by a string that represents a context and a permission, joined with a decimal point. - -```php -public array $permissions = [ - 'admin.access' => 'Can access the sites admin area', - 'admin.settings' => 'Can access the main site settings', - 'users.manage-admins' => 'Can manage other admins', - 'users.create' => 'Can create new non-admin users', - 'users.edit' => 'Can edit existing non-admin users', - 'users.delete' => 'Can delete existing non-admin users', - 'beta.access' => 'Can access beta-level features', -]; -``` - -#### Assign Permissions to a Group - -Each group can have its own specific set of permissions. These are defined in `Config\AuthGroups::$matrix`. You can specify each permission by it's full name, or using the context and an asterisk (*) to specify all permissions within that context. - -```php -public array $matrix = [ - 'superadmin' => [ - 'admin.*', - 'users.*', - 'beta.access', - ], - // -]; -``` - -#### Assign Permissions to a User - -Permissions can also be assigned directly to a user, regardless of what groups they belong to. This is done programatically on the `User` Entity. - -```php -$user = auth()->user(); - -$user->addPermission('users.create', 'beta.access'); -``` - -This will add all new permissions. You can also sync permissions so that the user ONLY has the given permissions directly assigned to them. Any not in the provided list are removed from the user. - -```php -$user = auth()->user(); - -$user->syncPermissions('users.create', 'beta.access'); -``` - -## Check If a User Has Permission - -When you need to check if a user has a specific permission use the `can()` method on the `User` entity. This method checks permissions within the groups they belong to and permissions directly assigned to the user. - -```php -if (! auth()->user()->can('users.create')) { - return redirect()->back()->with('error', 'You do not have permissions to access that page.'); -} -``` - -> **Note** The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. - -### Adding a Group To a User - -Groups are assigned to a user via the `addGroup()` method. You can pass multiple groups in and they will all be assigned to the user. - -```php -$user = auth()->user(); -$user->addGroup('admin', 'beta'); -``` - -This will add all new groups. You can also sync groups so that the user ONLY belongs to the groups directly assigned to them. Any not in the provided list are removed from the user. - -```php -$user = auth()->user(); -$user->syncGroups('admin', 'beta'); -``` - -### Removing a Group From a User - -Groups are removed from a user via the `removeGroup()` method. Multiple groups may be removed at once by passing all of their names into the method. - -```php -$user = auth()->user(); -$user->removeGroup('admin', 'beta'); -``` - -### Checking If User Belongs To a Group - -You can check if a user belongs to a group with the `inGroup()` method. - -```php -$user = auth()->user(); -if ($user->inGroup('admin')) { - // do something -} -``` - -You can pass more than one group to the method and it will return `true` if the user belongs to any of the specified groups. - -```php -$user = auth()->user(); -if ($user->inGroup('admin', 'beta')) { - // do something -} -``` - -## Managing Users - -Since Shield uses a more complex user setup than many other systems, separating [User Identities](concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. - -### Creating Users - -By default, the only values stored in the users table is the username. The first step is to create the user record with the username. If you don't have a username, be sure to set the value to `null` anyway, so that it passes CodeIgniter's empty data check. - -```php -use CodeIgniter\Shield\Entities\User; - -// Get the User Provider (UserModel by default) -$users = auth()->getProvider(); - -$user = new User([ - 'username' => 'foo-bar', - 'email' => 'foo.bar@example.com', - 'password' => 'secret plain text password', -]); -$users->save($user); - -// To get the complete user object with ID, we need to get from the database -$user = $users->findById($users->getInsertID()); - -// Add to default group -$users->addToDefaultGroup($user); -``` - -### Deleting Users - -A user's data can be spread over a few different tables so you might be concerned about how to delete all of the user's data from the system. This is handled automatically at the database level for all information that Shield knows about, through the `onCascade` settings of the table's foreign keys. You can delete a user like any other entity. - -```php -// Get the User Provider (UserModel by default) -$users = auth()->getProvider(); - -$users->delete($user->id, true); -``` - -> **Note** The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. - -### Editing a User - -The `UserModel::save()`, `update()` and `insert()` methods have been modified to ensure that an email or password previously set on the `User` entity will be automatically updated in the correct `UserIdentity` record. - -```php -// Get the User Provider (UserModel by default) -$users = auth()->getProvider(); - -$user = $users->findById(123); -$user->fill([ - 'username' => 'JoeSmith111', - 'email' => 'joe.smith@example.com', - 'password' => 'secret123' -]); -$users->save($user); -``` diff --git a/mkdocs.yml b/mkdocs.yml index ed9048c0f..08fe66e39 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,7 +41,10 @@ nav: - Getting Started: - Installation: install.md - Concepts: concepts.md - - Quick Start Guide: quickstart.md + - Quick Start Guide: + - authenticaton_flow.md + - authorization_flow.md + - managing_users.md - References: - Authentication: authentication.md - Authorization: authorization.md From 79ff70c848844268c5e4f809ac264481f46f4eb9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 16:57:21 +0900 Subject: [PATCH 244/401] docs: remove redundant TOC --- docs/index.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/docs/index.md b/docs/index.md index 994283c94..3384d3360 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,35 +27,3 @@ The primary goals for Shield are: ### License Shield is licensed under the MIT License - see the [LICENSE](https://github.com/codeigniter4/shield/blob/develop/LICENSE) file for details. - -## Getting Started - -* [Installation Guide](install.md) -* [Concepts You Need To Know](concepts.md) - -## Quick Start Guide - -* [Authentication Flow](authenticaton_flow.md) -* [Authorization Flow](authorization_flow.md) -* [Managing Users](managing_users.md) - -## References - -* [Authentication](authentication.md) -* [Authorization](authorization.md) -* [Auth Actions](auth_actions.md) -* [Events](events.md) -* [Testing](testing.md) -* [Customization](customization.md) -* [Forcing Password Reset](forcing_password_reset.md) -* [Banning Users](banning_users.md) - -## Guides - -* [Protecting an API with Access Tokens](guides/api_tokens.md) -* [Mobile Authentication with Access Tokens](guides/mobile_apps.md) -* [How to Strengthen the Password](guides/strengthen_password.md) - -## Addons - -* [JWT Authentication](addons/jwt.md) From c14253af51da6c5b01c11e92eb5ddcbd418a9bb3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 16:58:29 +0900 Subject: [PATCH 245/401] docs: move two pages to Guides --- mkdocs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 08fe66e39..4bc40484c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,14 +51,14 @@ nav: - Auth Actions: auth_actions.md - Events: events.md - Testing: testing.md - - Forcing Password Reset: forcing_password_reset.md - - Banning Users: banning_users.md - session_auth_event_and_logging.md - Customization: - customization.md - Guides: - - guides/api_hmac_keys.md + - Forcing Password Reset: forcing_password_reset.md + - Banning Users: banning_users.md - guides/api_tokens.md + - guides/api_hmac_keys.md - guides/mobile_apps.md - guides/strengthen_password.md - Addons: From 5db81205a30743f28c6743250a7d2b55f470ea79 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 16:59:22 +0900 Subject: [PATCH 246/401] docs: move Concepts to first --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 4bc40484c..23c179231 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,8 +39,8 @@ extra_javascript: nav: - Home: index.md - Getting Started: - - Installation: install.md - Concepts: concepts.md + - Installation: install.md - Quick Start Guide: - authenticaton_flow.md - authorization_flow.md From e43e689ce8a6c719e5497d5f096225b5575bb3d9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:10:13 +0900 Subject: [PATCH 247/401] docs: move two files to guides/ --- docs/{ => guides}/banning_users.md | 0 docs/{ => guides}/forcing_password_reset.md | 0 mkdocs.yml | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/{ => guides}/banning_users.md (100%) rename docs/{ => guides}/forcing_password_reset.md (100%) diff --git a/docs/banning_users.md b/docs/guides/banning_users.md similarity index 100% rename from docs/banning_users.md rename to docs/guides/banning_users.md diff --git a/docs/forcing_password_reset.md b/docs/guides/forcing_password_reset.md similarity index 100% rename from docs/forcing_password_reset.md rename to docs/guides/forcing_password_reset.md diff --git a/mkdocs.yml b/mkdocs.yml index 23c179231..567f5f45a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,8 +55,8 @@ nav: - Customization: - customization.md - Guides: - - Forcing Password Reset: forcing_password_reset.md - - Banning Users: banning_users.md + - guides/forcing_password_reset.md + - guides/banning_users.md - guides/api_tokens.md - guides/api_hmac_keys.md - guides/mobile_apps.md From 32a68b048ab45e83708a1015875bd511692e827a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:11:56 +0900 Subject: [PATCH 248/401] docs: fix typo in file name --- docs/{authenticaton_flow.md => authentication_flow.md} | 0 mkdocs.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{authenticaton_flow.md => authentication_flow.md} (100%) diff --git a/docs/authenticaton_flow.md b/docs/authentication_flow.md similarity index 100% rename from docs/authenticaton_flow.md rename to docs/authentication_flow.md diff --git a/mkdocs.yml b/mkdocs.yml index 567f5f45a..7b64bf5c3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,7 +42,7 @@ nav: - Concepts: concepts.md - Installation: install.md - Quick Start Guide: - - authenticaton_flow.md + - authentication_flow.md - authorization_flow.md - managing_users.md - References: From ac9aff9da4601bc02c7716d2cbb286cb735b151a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:12:59 +0900 Subject: [PATCH 249/401] docs: move three files to quick_start_guide/ --- docs/{ => quick_start_guide}/authentication_flow.md | 0 docs/{ => quick_start_guide}/authorization_flow.md | 0 docs/{ => quick_start_guide}/managing_users.md | 0 mkdocs.yml | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename docs/{ => quick_start_guide}/authentication_flow.md (100%) rename docs/{ => quick_start_guide}/authorization_flow.md (100%) rename docs/{ => quick_start_guide}/managing_users.md (100%) diff --git a/docs/authentication_flow.md b/docs/quick_start_guide/authentication_flow.md similarity index 100% rename from docs/authentication_flow.md rename to docs/quick_start_guide/authentication_flow.md diff --git a/docs/authorization_flow.md b/docs/quick_start_guide/authorization_flow.md similarity index 100% rename from docs/authorization_flow.md rename to docs/quick_start_guide/authorization_flow.md diff --git a/docs/managing_users.md b/docs/quick_start_guide/managing_users.md similarity index 100% rename from docs/managing_users.md rename to docs/quick_start_guide/managing_users.md diff --git a/mkdocs.yml b/mkdocs.yml index 7b64bf5c3..ad589d868 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,9 +42,9 @@ nav: - Concepts: concepts.md - Installation: install.md - Quick Start Guide: - - authentication_flow.md - - authorization_flow.md - - managing_users.md + - quick_start_guide/authentication_flow.md + - quick_start_guide/authorization_flow.md + - quick_start_guide/managing_users.md - References: - Authentication: authentication.md - Authorization: authorization.md From 0c60b997b4cbc0fb46c8d31dc78187113c01662a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:13:31 +0900 Subject: [PATCH 250/401] docs: fix indentation --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index ad589d868..5c63132ce 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,7 @@ nav: - Testing: testing.md - session_auth_event_and_logging.md - Customization: - - customization.md + - customization.md - Guides: - guides/forcing_password_reset.md - guides/banning_users.md From 4db37306cc36249f4241fee24df0359f9c8d759a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:27:17 +0900 Subject: [PATCH 251/401] docs: split customization.md --- docs/customization.md | 312 ------------------ docs/customization/extending_controllers.md | 31 ++ .../integrating_custom_view_libs.md | 18 + docs/customization/login_identifier.md | 34 ++ docs/customization/redirect_urls.md | 60 ++++ docs/customization/route_config.md | 14 + docs/customization/table_names.md | 20 ++ docs/customization/user_provider.md | 20 ++ docs/customization/validation_rules.md | 106 ++++++ mkdocs.yml | 9 +- 10 files changed, 311 insertions(+), 313 deletions(-) delete mode 100644 docs/customization.md create mode 100644 docs/customization/extending_controllers.md create mode 100644 docs/customization/integrating_custom_view_libs.md create mode 100644 docs/customization/login_identifier.md create mode 100644 docs/customization/redirect_urls.md create mode 100644 docs/customization/route_config.md create mode 100644 docs/customization/table_names.md create mode 100644 docs/customization/user_provider.md create mode 100644 docs/customization/validation_rules.md diff --git a/docs/customization.md b/docs/customization.md deleted file mode 100644 index 66e21312e..000000000 --- a/docs/customization.md +++ /dev/null @@ -1,312 +0,0 @@ -# Customizing Shield - -## Custom Table Names - -If you want to change the default table names, you can change the table names -in **app/Config/Auth.php**. - -```php -public array $tables = [ - 'users' => 'users', - 'identities' => 'auth_identities', - 'logins' => 'auth_logins', - 'token_logins' => 'auth_token_logins', - 'remember_tokens' => 'auth_remember_tokens', - 'groups_users' => 'auth_groups_users', - 'permissions_users' => 'auth_permissions_users', -]; -``` - -Set the table names that you want in the array values. - -> **Note** You must change the table names before running database migrations. - -## Route Configuration - -If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: - -```php -service('auth')->routes($routes, ['except' => ['login', 'register']]); -``` - -Then add the routes to your customized controllers: - -```php -$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); -$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); -``` - -## Custom Redirect URLs - -### Customize Login Redirect - -You can customize where a user is redirected to on login with the `loginRedirect()` method of the **app/Config/Auth.php** config file. This is handy if you want to redirect based on user group or other criteria. - -```php -public function loginRedirect(): string -{ - $url = auth()->user()->inGroup('admin') - ? '/admin' - : setting('Auth.redirects')['login']; - - return $this->getUrl($url); -} -``` - -Oftentimes, you will want to have different redirects for different user groups. A simple example -might be that you want admins redirected to `/admin` while all other groups redirect to `/`. -The **app/Config/Auth.php** config file also includes methods that you can add additional logic to in order to -achieve this: - -```php -public function loginRedirect(): string -{ - if (auth()->user()->can('admin.access')) { - return '/admin'; - } - - $url = setting('Auth.redirects')['login']; - - return $this->getUrl($url); -} -``` - -### Customize Register Redirect - -You can customize where a user is redirected to after registration in the `registerRedirect()` method of the **app/Config/Auth.php** config file. - -```php -public function registerRedirect(): string -{ - $url = setting('Auth.redirects')['register']; - - return $this->getUrl($url); -} -``` - -### Customize Logout Redirect - -The logout redirect can also be overridden by the `logoutRedirect()` method of the **app/Config/Auth.php** config file. This will not be used as often as login and register, but you might find the need. For example, if you programatically logged a user out you might want to take them to a page that specifies why they were logged out. Otherwise, you might take them to the home page or even the login page. - -```php -public function logoutRedirect(): string -{ - $url = setting('Auth.redirects')['logout']; - - return $this->getUrl($url); -} -``` - -## Extending the Controllers - -Shield has the following controllers that can be extended to handle -various parts of the authentication process: - -- **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification. -- **LoginController** handles the login process. -- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules. -- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to - override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used. - -It is not recommended to copy the entire controller into **app/Controllers** and change its namespace. Instead, you should create a new controller that extends -the existing controller and then only override the methods needed. This allows the other methods to stay up to date with any security -updates that might happen in the controllers. - -```php -themedView($view, $data, $options); - } -} -``` - -## Custom Validation Rules - -### Registration - -Shield has the following rules for registration: - -```php -[ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', - 'is_unique[users.username]', - ], - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email', - 'is_unique[auth_identities.secret]', - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|strong_password', - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', - ], -]; -``` - -> **Note** If you customize the table names, the table names -> (`users` and `auth_identities`) in the above rules will be automatically -> changed. The rules are implemented in -> `RegisterController::getValidationRules()`. - -If you need a different set of rules for registration, you can specify them in your `Validation` configuration (**app/Config/Validation.php**) like: - -```php - //-------------------------------------------------------------------- - // Rules For Registration - //-------------------------------------------------------------------- - public $registration = [ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', - 'is_unique[users.username]', - ], - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email', - 'is_unique[auth_identities.secret]', - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|strong_password', - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', - ], - ]; -``` - -> **Note** If you customize the table names, set the correct table names in the -> rules. - -### Login - -Similar to the process for validation rules in the **Registration** section, you can add rules for the login form to **app/Config/Validation.php** and change the rules. - -```php - //-------------------------------------------------------------------- - // Rules For Login - //-------------------------------------------------------------------- - public $login = [ - // 'username' => [ - // 'label' => 'Auth.username', - // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', - // ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => 'required|max_length[254]|valid_email', - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required', - ], - ]; -``` - -## Custom User Provider - -If you want to customize user attributes, you need to create your own -[User Provider](./concepts.md#user-providers) class. -The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`. - -Shield has a CLI command to quickly create a custom `UserModel` class by running the following -command in the terminal: - -```console -php spark shield:model UserModel -``` - -The class name is optional. If none is provided, the generated class name would be `UserModel`. - -After creating the class, set the `$userProvider` property in **app/Config/Auth.php** as follows: - -```php -public string $userProvider = \App\Models\UserModel::class; -``` - -## Custom Login Identifier - -If your application has a need to use something other than `email` or `username`, you may specify any valid column within the `users` table that you may have added. This allows you to easily use phone numbers, employee or school IDs, etc as the user identifier. You must implement the following steps to set this up: - -This only works with the Session authenticator. - -1. Create a [migration](http://codeigniter.com/user_guide/dbmgmt/migration.html) that adds a new column to the `users` table. -2. Edit `app/Config/Auth.php` so that the new column you just created is within the `$validFields` array. - - ```php - public array $validFields = [ - 'employee_id' - ]; - ``` - - If you have multiple login forms on your site that use different credentials, you must have all of the valid identifying fields in the array. - - ```php - public array $validFields = [ - 'email', - 'employee_id' - ]; - ``` - > **Warning** - > It is very important for security that if you add a new column for identifier you must write a new **Validation Rules** and then set it using the [custom-validation-rules](https://github.com/codeigniter4/shield/blob/develop/docs/customization.md#custom-validation-rules) description. - -3. Edit the login form to change the name of the default `email` input to the new field name. - - ```php - -
- -
- ``` diff --git a/docs/customization/extending_controllers.md b/docs/customization/extending_controllers.md new file mode 100644 index 000000000..ea03a54ee --- /dev/null +++ b/docs/customization/extending_controllers.md @@ -0,0 +1,31 @@ +# Extending the Controllers + +Shield has the following controllers that can be extended to handle +various parts of the authentication process: + +- **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification. +- **LoginController** handles the login process. +- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules. +- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to + override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used. + +It is not recommended to copy the entire controller into **app/Controllers** and change its namespace. Instead, you should create a new controller that extends +the existing controller and then only override the methods needed. This allows the other methods to stay up to date with any security +updates that might happen in the controllers. + +```php +themedView($view, $data, $options); + } +} +``` diff --git a/docs/customization/login_identifier.md b/docs/customization/login_identifier.md new file mode 100644 index 000000000..ea96993a4 --- /dev/null +++ b/docs/customization/login_identifier.md @@ -0,0 +1,34 @@ +# Customizing Login Identifier + +If your application has a need to use something other than `email` or `username`, you may specify any valid column within the `users` table that you may have added. This allows you to easily use phone numbers, employee or school IDs, etc as the user identifier. You must implement the following steps to set this up: + +This only works with the Session authenticator. + +1. Create a [migration](http://codeigniter.com/user_guide/dbmgmt/migration.html) that adds a new column to the `users` table. +2. Edit `app/Config/Auth.php` so that the new column you just created is within the `$validFields` array. + + ```php + public array $validFields = [ + 'employee_id' + ]; + ``` + + If you have multiple login forms on your site that use different credentials, you must have all of the valid identifying fields in the array. + + ```php + public array $validFields = [ + 'email', + 'employee_id' + ]; + ``` + > **Warning** + > It is very important for security that if you add a new column for identifier you must write a new **Validation Rules** and then set it using the [custom-validation-rules](https://github.com/codeigniter4/shield/blob/develop/docs/customization.md#custom-validation-rules) description. + +3. Edit the login form to change the name of the default `email` input to the new field name. + + ```php + +
+ +
+ ``` diff --git a/docs/customization/redirect_urls.md b/docs/customization/redirect_urls.md new file mode 100644 index 000000000..5ecf6834b --- /dev/null +++ b/docs/customization/redirect_urls.md @@ -0,0 +1,60 @@ +# Customizing Redirect URLs + +## Customize Login Redirect + +You can customize where a user is redirected to on login with the `loginRedirect()` method of the **app/Config/Auth.php** config file. This is handy if you want to redirect based on user group or other criteria. + +```php +public function loginRedirect(): string +{ + $url = auth()->user()->inGroup('admin') + ? '/admin' + : setting('Auth.redirects')['login']; + + return $this->getUrl($url); +} +``` + +Oftentimes, you will want to have different redirects for different user groups. A simple example +might be that you want admins redirected to `/admin` while all other groups redirect to `/`. +The **app/Config/Auth.php** config file also includes methods that you can add additional logic to in order to +achieve this: + +```php +public function loginRedirect(): string +{ + if (auth()->user()->can('admin.access')) { + return '/admin'; + } + + $url = setting('Auth.redirects')['login']; + + return $this->getUrl($url); +} +``` + +## Customize Register Redirect + +You can customize where a user is redirected to after registration in the `registerRedirect()` method of the **app/Config/Auth.php** config file. + +```php +public function registerRedirect(): string +{ + $url = setting('Auth.redirects')['register']; + + return $this->getUrl($url); +} +``` + +## Customize Logout Redirect + +The logout redirect can also be overridden by the `logoutRedirect()` method of the **app/Config/Auth.php** config file. This will not be used as often as login and register, but you might find the need. For example, if you programatically logged a user out you might want to take them to a page that specifies why they were logged out. Otherwise, you might take them to the home page or even the login page. + +```php +public function logoutRedirect(): string +{ + $url = setting('Auth.redirects')['logout']; + + return $this->getUrl($url); +} +``` diff --git a/docs/customization/route_config.md b/docs/customization/route_config.md new file mode 100644 index 000000000..729383280 --- /dev/null +++ b/docs/customization/route_config.md @@ -0,0 +1,14 @@ +# Customizing Route Configuration + +If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: + +```php +service('auth')->routes($routes, ['except' => ['login', 'register']]); +``` + +Then add the routes to your customized controllers: + +```php +$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); +$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); +``` diff --git a/docs/customization/table_names.md b/docs/customization/table_names.md new file mode 100644 index 000000000..2fcaad8c7 --- /dev/null +++ b/docs/customization/table_names.md @@ -0,0 +1,20 @@ +# Customizing Table Names + +If you want to change the default table names, you can change the table names +in **app/Config/Auth.php**. + +```php +public array $tables = [ + 'users' => 'users', + 'identities' => 'auth_identities', + 'logins' => 'auth_logins', + 'token_logins' => 'auth_token_logins', + 'remember_tokens' => 'auth_remember_tokens', + 'groups_users' => 'auth_groups_users', + 'permissions_users' => 'auth_permissions_users', +]; +``` + +Set the table names that you want in the array values. + +> **Note** You must change the table names before running database migrations. diff --git a/docs/customization/user_provider.md b/docs/customization/user_provider.md new file mode 100644 index 000000000..f55ac2cd4 --- /dev/null +++ b/docs/customization/user_provider.md @@ -0,0 +1,20 @@ +# Customizing User Provider + +If you want to customize user attributes, you need to create your own +[User Provider](./concepts.md#user-providers) class. +The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`. + +Shield has a CLI command to quickly create a custom `UserModel` class by running the following +command in the terminal: + +```console +php spark shield:model UserModel +``` + +The class name is optional. If none is provided, the generated class name would be `UserModel`. + +After creating the class, set the `$userProvider` property in **app/Config/Auth.php** as follows: + +```php +public string $userProvider = \App\Models\UserModel::class; +``` diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md new file mode 100644 index 000000000..f0442ff72 --- /dev/null +++ b/docs/customization/validation_rules.md @@ -0,0 +1,106 @@ +# Customizing Validation Rules + +## Registration + +Shield has the following rules for registration: + +```php +[ + 'username' => [ + 'label' => 'Auth.username', + 'rules' => [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + 'is_unique[users.username]', + ], + ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email', + 'is_unique[auth_identities.secret]', + ], + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required|strong_password', + ], + 'password_confirm' => [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ], +]; +``` + +> **Note** If you customize the table names, the table names +> (`users` and `auth_identities`) in the above rules will be automatically +> changed. The rules are implemented in +> `RegisterController::getValidationRules()`. + +If you need a different set of rules for registration, you can specify them in your `Validation` configuration (**app/Config/Validation.php**) like: + +```php + //-------------------------------------------------------------------- + // Rules For Registration + //-------------------------------------------------------------------- + public $registration = [ + 'username' => [ + 'label' => 'Auth.username', + 'rules' => [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + 'is_unique[users.username]', + ], + ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email', + 'is_unique[auth_identities.secret]', + ], + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required|strong_password', + ], + 'password_confirm' => [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ], + ]; +``` + +> **Note** If you customize the table names, set the correct table names in the +> rules. + +## Login + +Similar to the process for validation rules in the **Registration** section, you can add rules for the login form to **app/Config/Validation.php** and change the rules. + +```php + //-------------------------------------------------------------------- + // Rules For Login + //-------------------------------------------------------------------- + public $login = [ + // 'username' => [ + // 'label' => 'Auth.username', + // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', + // ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => 'required|max_length[254]|valid_email', + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required', + ], + ]; +``` diff --git a/mkdocs.yml b/mkdocs.yml index 5c63132ce..f18ca9d7c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,14 @@ nav: - Testing: testing.md - session_auth_event_and_logging.md - Customization: - - customization.md + - customization/table_names.md + - customization/route_config.md + - customization/redirect_urls.md + - customization/extending_controllers.md + - customization/integrating_custom_view_libs.md + - customization/validation_rules.md + - customization/user_provider.md + - customization/login_identifier.md - Guides: - guides/forcing_password_reset.md - guides/banning_users.md From f5be15954cfd74fd6b4c012d71b78a87a25fa202 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 17:29:13 +0900 Subject: [PATCH 252/401] docs: update links --- docs/concepts.md | 2 +- docs/customization/login_identifier.md | 2 +- docs/customization/user_provider.md | 2 +- docs/guides/strengthen_password.md | 2 +- docs/install.md | 4 ++-- docs/quick_start_guide/authentication_flow.md | 6 +++--- docs/quick_start_guide/managing_users.md | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index dc1b17452..c5f6c3f65 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -21,7 +21,7 @@ on the standard Config class if nothing is found in the database. Shield has a model to handle user persistence. Shield calls this the "User Provider" class. A default model is provided for you by the `CodeIgniter\Shield\Models\UserModel` class. -You can use your own model to customize user attributes. See [Customizing Shield](./customization.md#custom-user-provider) for details. +You can use your own model to customize user attributes. See [Customizing User Provider](./customization/user_provider.md) for details. ## User Identities diff --git a/docs/customization/login_identifier.md b/docs/customization/login_identifier.md index ea96993a4..2239d3ad8 100644 --- a/docs/customization/login_identifier.md +++ b/docs/customization/login_identifier.md @@ -22,7 +22,7 @@ This only works with the Session authenticator. ]; ``` > **Warning** - > It is very important for security that if you add a new column for identifier you must write a new **Validation Rules** and then set it using the [custom-validation-rules](https://github.com/codeigniter4/shield/blob/develop/docs/customization.md#custom-validation-rules) description. + > It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. 3. Edit the login form to change the name of the default `email` input to the new field name. diff --git a/docs/customization/user_provider.md b/docs/customization/user_provider.md index f55ac2cd4..36af8d838 100644 --- a/docs/customization/user_provider.md +++ b/docs/customization/user_provider.md @@ -1,7 +1,7 @@ # Customizing User Provider If you want to customize user attributes, you need to create your own -[User Provider](./concepts.md#user-providers) class. +[User Provider](../concepts.md#user-providers) class. The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`. Shield has a CLI command to quickly create a custom `UserModel` class by running the following diff --git a/docs/guides/strengthen_password.md b/docs/guides/strengthen_password.md index 102dc9b1d..6fad8ea5c 100644 --- a/docs/guides/strengthen_password.md +++ b/docs/guides/strengthen_password.md @@ -107,7 +107,7 @@ By default, Shield has the validation rules for maximum password length. - 72 bytes for PASSWORD_BCRYPT - 255 characters for others -You can customize the validation rule. See [Customizing Shield](../customization.md). +You can customize the validation rule. See [Customizing Validation Rules](../customization/validation_rules.md). ## $supportOldDangerousPassword diff --git a/docs/install.md b/docs/install.md index ecf9ab3a3..47aad61b0 100644 --- a/docs/install.md +++ b/docs/install.md @@ -63,7 +63,7 @@ Require it with an explicit version constraint allowing its desired stability. > **Note** If you want to customize table names, you must change the table names > before running database migrations. - > See [Customizing Shield](./customization.md#custom-table-names). + > See [Customizing Table Names](./customization/table_names.md). 2. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). @@ -140,7 +140,7 @@ your project. > **Note** If you want to customize table names, you must change the table names > before running database migrations. - > See [Customizing Shield](./customization.md#custom-table-names). + > See [Customizing Table Names](./customization/table_names.md). ```console php spark migrate --all diff --git a/docs/quick_start_guide/authentication_flow.md b/docs/quick_start_guide/authentication_flow.md index 7827d0ab9..428e14703 100644 --- a/docs/quick_start_guide/authentication_flow.md +++ b/docs/quick_start_guide/authentication_flow.md @@ -49,7 +49,7 @@ public int $unusedTokenLifetime = YEAR; ### Enable Account Activation via Email > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the `Auth` config file. @@ -63,7 +63,7 @@ public array $actions = [ ### Enable Two-Factor Authentication > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). Turned off by default, Shield's Email-based 2FA can be enabled by specifying the class to use in the `Auth` config file. @@ -77,7 +77,7 @@ public array $actions = [ ## Responding to Magic Link Logins > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. diff --git a/docs/quick_start_guide/managing_users.md b/docs/quick_start_guide/managing_users.md index e963a3b34..84c57815b 100644 --- a/docs/quick_start_guide/managing_users.md +++ b/docs/quick_start_guide/managing_users.md @@ -1,6 +1,6 @@ # Managing Users -Since Shield uses a more complex user setup than many other systems, separating [User Identities](concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. +Since Shield uses a more complex user setup than many other systems, separating [User Identities](../concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. ## Creating Users From d19c6e3a118ab9fbf99da00e05461cec7997c59f Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 21:18:23 +0900 Subject: [PATCH 253/401] docs: fix invalid markdown --- docs/quick_start_guide/authentication_flow.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quick_start_guide/authentication_flow.md b/docs/quick_start_guide/authentication_flow.md index 428e14703..40de6e511 100644 --- a/docs/quick_start_guide/authentication_flow.md +++ b/docs/quick_start_guide/authentication_flow.md @@ -9,10 +9,10 @@ Learning any new authentication system can be difficult, especially as they get ### Configure Redirect URLs -If you need everyone to redirect to a single URL after login/logout/register actions, you can modify the `Config\Auth::$redirects` array in **app/Config/Auth.php**`** to specify the url to redirect to. +If you need everyone to redirect to a single URL after login/logout/register actions, you can modify the `Config\Auth::$redirects` array in **app/Config/Auth.php** to specify the url to redirect to. By default, a successful login or register attempt will all redirect to `/`, while a logout action -will redirect to a [named route](https://codeigniter.com/user_guide/incoming/routing.html#using-named-routes "See routing docs") `login` or a *URI path* `/login`. You can change the default URLs used within the **`**app/Config/Auth.php** config file: +will redirect to a [named route](https://codeigniter.com/user_guide/incoming/routing.html#using-named-routes "See routing docs") `login` or a *URI path* `/login`. You can change the default URLs used within the **app/Config/Auth.php** config file: ```php public array $redirects = [ From 33a4c582f24a367da6d33170884da4c3f0f0e4d6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 21:18:40 +0900 Subject: [PATCH 254/401] docs: replace handler with authenticator --- docs/quick_start_guide/authentication_flow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_start_guide/authentication_flow.md b/docs/quick_start_guide/authentication_flow.md index 40de6e511..d22383f38 100644 --- a/docs/quick_start_guide/authentication_flow.md +++ b/docs/quick_start_guide/authentication_flow.md @@ -27,7 +27,7 @@ public array $redirects = [ ### Configure Remember-me Functionality -Remember-me functionality is enabled by default for the `Session` handler. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. +Remember-me functionality is enabled by default for the `Session` authenticator. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. ```php public array $sessionConfig = [ From 811505450863c1a316711c8d59649ca55ce4af7f Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Sep 2023 21:23:06 +0900 Subject: [PATCH 255/401] docs: add "official" --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 3384d3360..81a746ebc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ ## What is Shield? -Shield is an authentication and authorization framework for CodeIgniter 4. While +Shield is the official authentication and authorization framework for CodeIgniter 4. While it does provide a base set of tools that are commonly used in websites, it is designed to be flexible and easily customizable. From 826ca41fbf88beb25ef233db766c852907317b5f Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 15:00:28 +0900 Subject: [PATCH 256/401] docs: improve comments --- src/Config/AuthToken.php | 4 ++-- src/Filters/TokenAuth.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Config/AuthToken.php b/src/Config/AuthToken.php index 2d8254471..f83c665fa 100644 --- a/src/Config/AuthToken.php +++ b/src/Config/AuthToken.php @@ -7,13 +7,13 @@ use CodeIgniter\Config\BaseConfig; /** - * Authenticator Configuration for Token Auth and HMAC Auth + * Configuration for Token Auth and HMAC Auth */ class AuthToken extends BaseConfig { /** * -------------------------------------------------------------------- - * Record Login Attempts for Token and HMAC Authorization + * Record Login Attempts for Token Auth and HMAC Auth * -------------------------------------------------------------------- * Specify which login attempts are recorded in the database. * diff --git a/src/Filters/TokenAuth.php b/src/Filters/TokenAuth.php index ee504cbd5..4425c14ff 100644 --- a/src/Filters/TokenAuth.php +++ b/src/Filters/TokenAuth.php @@ -21,7 +21,7 @@ class TokenAuth implements FilterInterface { /** * Do whatever processing this filter needs to do. - * By default it should not return anything during + * By default, it should not return anything during * normal execution. However, when an abnormal state * is found, it should return an instance of * CodeIgniter\HTTP\Response. If it does, script From 92ca399ab36e75cd0d2f8b261231e8da6f100ca0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 15:00:51 +0900 Subject: [PATCH 257/401] fix: AccessTokens authenticator records all accesses Now Record only failure failures by default. --- .../Authenticators/AccessTokens.php | 52 +++++++++++++------ .../AccessTokenAuthenticatorTest.php | 6 +-- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index a09000703..bf6a7a5c4 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -8,6 +8,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\AuthenticationException; use CodeIgniter\Shield\Authentication\AuthenticatorInterface; +use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Exceptions\InvalidArgumentException; use CodeIgniter\Shield\Models\TokenLoginModel; @@ -42,6 +43,8 @@ public function __construct(UserModel $provider) */ public function attempt(array $credentials): Result { + $config = config('AuthToken'); + /** @var IncomingRequest $request */ $request = service('request'); @@ -51,14 +54,16 @@ public function attempt(array $credentials): Result $result = $this->check($credentials); if (! $result->isOK()) { - // Always record a login attempt, whether success or not. - $this->loginModel->recordLoginAttempt( - self::ID_TYPE_ACCESS_TOKEN, - $credentials['token'] ?? '', - false, - $ipAddress, - $userAgent - ); + if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) { + // Record all failed login attempts. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_ACCESS_TOKEN, + $credentials['token'] ?? '', + false, + $ipAddress, + $userAgent + ); + } return $result; } @@ -66,6 +71,18 @@ public function attempt(array $credentials): Result $user = $result->extraInfo(); if ($user->isBanned()) { + if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) { + // Record a banned login attempt. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_ACCESS_TOKEN, + $credentials['token'] ?? '', + false, + $ipAddress, + $userAgent, + $user->id + ); + } + $this->user = null; return new Result([ @@ -80,14 +97,17 @@ public function attempt(array $credentials): Result $this->login($user); - $this->loginModel->recordLoginAttempt( - self::ID_TYPE_ACCESS_TOKEN, - $credentials['token'] ?? '', - true, - $ipAddress, - $userAgent, - $this->user->id - ); + if ($config->recordLoginAttempt === Auth::RECORD_LOGIN_ATTEMPT_ALL) { + // Record a successful login attempt. + $this->loginModel->recordLoginAttempt( + self::ID_TYPE_ACCESS_TOKEN, + $credentials['token'] ?? '', + true, + $ipAddress, + $userAgent, + $this->user->id + ); + } return $result; } diff --git a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php index c5042dbb8..b87bf3930 100644 --- a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php @@ -174,7 +174,7 @@ public function testAttemptCannotFindUser(): void $this->assertFalse($result->isOK()); $this->assertSame(lang('Auth.badToken'), $result->reason()); - // A login attempt should have always been recorded + // A failed login attempt should have been recorded by default. $this->seeInDatabase($this->tables['token_logins'], [ 'id_type' => AccessTokens::ID_TYPE_ACCESS_TOKEN, 'identifier' => 'abc123', @@ -202,8 +202,8 @@ public function testAttemptSuccess(): void $this->assertInstanceOf(AccessToken::class, $foundUser->currentAccessToken()); $this->assertSame($token->token, $foundUser->currentAccessToken()->token); - // A login attempt should have been recorded - $this->seeInDatabase($this->tables['token_logins'], [ + // A successful login attempt is not recorded by default. + $this->dontSeeInDatabase($this->tables['token_logins'], [ 'id_type' => AccessTokens::ID_TYPE_ACCESS_TOKEN, 'identifier' => $token->raw_token, 'success' => 1, From 5e759984e29ccc2ea4fce8e77cdbb8b591cdb8e1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 15:18:54 +0900 Subject: [PATCH 258/401] docs: add UPGRADING.md --- UPGRADING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 1ffe19509..e8d30a4ef 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,16 @@ # Upgrade Guide +## Version 1.0.0-beta.6 to 1.0.0-beta.7 + +### Install New Config AuthToken.php + +A new Config file **AuthToken.php** has been introduced. Run `php spark shield:setup` +again to install it into **app/Config/**, or install it manually. + +Then change the default settings as necessary. When using Token authentication, +the default value has been changed from all accesses to be recorded in the +``token_logins`` table to only accesses that fail authentication to be recorded. + ## Version 1.0.0-beta.3 to 1.0.0-beta.4 ### Important Password Changes From 2bb01145629a16bdeca03dda2a23330ab17413bb Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 15:45:59 +0900 Subject: [PATCH 259/401] docs: fix wrong header key --- src/Authentication/Authenticators/HmacSha256.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index 3e6d7a260..ea9932091 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -200,7 +200,7 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']), + 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['hmac']), ])->isOK(); } From e977f0e4567877e6e69326fc48964c9e18ee439a Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 06:42:46 +0900 Subject: [PATCH 260/401] docs: extract "Controller Filters" --- docs/authenticators_and_filters.md | 101 +++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 102 insertions(+) create mode 100644 docs/authenticators_and_filters.md diff --git a/docs/authenticators_and_filters.md b/docs/authenticators_and_filters.md new file mode 100644 index 000000000..8c029f350 --- /dev/null +++ b/docs/authenticators_and_filters.md @@ -0,0 +1,101 @@ +# Authenticators and Controller Filters + +## Authenticators + +## Controller Filters + +The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes the shield provides are: + +```php +public $aliases = [ + // ... + 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, + 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, + 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, + 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, + 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, + 'permission' => \CodeIgniter\Shield\Filters\PermissionFilter::class, + 'force-reset' => \CodeIgniter\Shield\Filters\ForcePasswordResetFilter::class, + 'jwt' => \CodeIgniter\Shield\Filters\JWTAuth::class, +]; +``` + +Filters | Description +--- | --- +session and tokens | The `Session` and `AccessTokens` authenticators, respectively. +chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. +jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). +auth-rates | Provides a good basis for rate limiting of auth-related routes. +group | Checks if the user is in one of the groups passed in. +permission | Checks if the user has the passed permissions. +force-reset | Checks if the user requires a password reset. + +These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). + +> **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. + +### Protect All Pages + +If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['login*', 'register', 'auth/a/*']], + ], + // ... +]; +``` + +### Rate Limiting + +To help protect your authentication forms from being spammed by bots, it is recommended that you use +the `auth-rates` filter on all of your authentication routes. This can be done with the following +filter setup: + +```php +public $filters = [ + 'auth-rates' => [ + 'before' => [ + 'login*', 'register', 'auth/*' + ] + ] +]; +``` + +### Forcing Password Reset + +If your application requires a force password reset functionality, ensure that you exclude the auth pages and the actual password reset page from the `before` global. This will ensure that your users do not run into a *too many redirects* error. See: + +```php +public $globals = [ + 'before' => [ + //... + //... + 'force-reset' => ['except' => ['login*', 'register', 'auth/a/*', 'change-password', 'logout']] + ] +]; +``` +In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. + +> **Note** If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. + +For example, if you configured your routes like so: + +```php +$routes->group('accounts', static function($routes) { + service('auth')->routes($routes); +}); +``` +Then the global `before` filter for `session` should look like so: + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*']] + ] +] +``` +The same should apply for the Rate Limiting and Forcing Password Reset. diff --git a/mkdocs.yml b/mkdocs.yml index f18ca9d7c..2d1bb83e9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,6 +41,7 @@ nav: - Getting Started: - Concepts: concepts.md - Installation: install.md + - Authenticators and Filters: authenticators_and_filters.md - Quick Start Guide: - quick_start_guide/authentication_flow.md - quick_start_guide/authorization_flow.md From 159295b8904195fbdc5cf6d851cedc6462333581 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 06:45:20 +0900 Subject: [PATCH 261/401] docs: remove "the" --- docs/authenticators_and_filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authenticators_and_filters.md b/docs/authenticators_and_filters.md index 8c029f350..53a640643 100644 --- a/docs/authenticators_and_filters.md +++ b/docs/authenticators_and_filters.md @@ -4,7 +4,7 @@ ## Controller Filters -The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes the shield provides are: +The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: ```php public $aliases = [ From b42159ca694a0f8a8ea8caae6d476a0433d1d6b5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 06:51:48 +0900 Subject: [PATCH 262/401] docs: add list of Authenticators --- docs/authenticators_and_filters.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/authenticators_and_filters.md b/docs/authenticators_and_filters.md index 53a640643..d1839bc7b 100644 --- a/docs/authenticators_and_filters.md +++ b/docs/authenticators_and_filters.md @@ -2,6 +2,13 @@ ## Authenticators +Shield provides the following Authenticators: + +- **Session** authenticator provides traditional Email/Password authentication. +- **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. +- **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, + you need additional setup. See [JWT Authentication](./addons/jwt.md). + ## Controller Filters The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: From a937a807833593e65814a95e0d57a896d0a4a7aa Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 06:58:56 +0900 Subject: [PATCH 263/401] docs: add section title --- docs/authenticators_and_filters.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/authenticators_and_filters.md b/docs/authenticators_and_filters.md index d1839bc7b..340b7fcc3 100644 --- a/docs/authenticators_and_filters.md +++ b/docs/authenticators_and_filters.md @@ -41,6 +41,8 @@ These can be used in any of the [normal filter config settings](https://codeigni > **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. +## Configure Controller Filters + ### Protect All Pages If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. From 5c636e58809c45d98bbed6b60d46518ebcb3d505 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 07:05:21 +0900 Subject: [PATCH 264/401] docs: change first letters of keywords to capital letters --- docs/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index 81a746ebc..615a5ed1c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,13 +16,13 @@ The primary goals for Shield are: ### Important Features -* Session-based authentication (traditional email/password with remember me) +* Session-based authentication (traditional Email/Password with Remember me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration * Optional Email-based Two-Factor Authentication after login * Magic Login Links when a user forgets their password -* Flexible groups-based access control (think roles, but more flexible) -* Users can be granted additional permissions +* Flexible Groups-based access control (think Roles, but more flexible) +* Users can be granted additional Permissions ### License From 91e094d12ad6a36300f0419cc402727bd7123a7b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 08:52:56 +0900 Subject: [PATCH 265/401] docs: change page titles and move a section. --- ...orization_flow.md => using_access_tokens_auth.md} | 12 ++++++++++-- ...{authentication_flow.md => using_session_auth.md} | 12 ++---------- mkdocs.yml | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) rename docs/quick_start_guide/{authorization_flow.md => using_access_tokens_auth.md} (93%) rename docs/quick_start_guide/{authentication_flow.md => using_session_auth.md} (94%) diff --git a/docs/quick_start_guide/authorization_flow.md b/docs/quick_start_guide/using_access_tokens_auth.md similarity index 93% rename from docs/quick_start_guide/authorization_flow.md rename to docs/quick_start_guide/using_access_tokens_auth.md index 568ca5094..d493e6df2 100644 --- a/docs/quick_start_guide/authorization_flow.md +++ b/docs/quick_start_guide/using_access_tokens_auth.md @@ -1,6 +1,14 @@ -# Authorization Flow +# Using AccessTokens Authenticator -## Configure Config\AuthGroups +## Configure + +### Change Access Token Lifetime + +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. + +```php +public int $unusedTokenLifetime = YEAR; +``` ### Change Available Groups diff --git a/docs/quick_start_guide/authentication_flow.md b/docs/quick_start_guide/using_session_auth.md similarity index 94% rename from docs/quick_start_guide/authentication_flow.md rename to docs/quick_start_guide/using_session_auth.md index d22383f38..cd42a36f8 100644 --- a/docs/quick_start_guide/authentication_flow.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -1,11 +1,11 @@ -# Authentication Flow +# Using Session Authenticator Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. > **Note** > The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. -## Configure Config\Auth +## Configure ### Configure Redirect URLs @@ -38,14 +38,6 @@ public array $sessionConfig = [ ]; ``` -### Change Access Token Lifetime - -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the `Auth` config file. - -```php -public int $unusedTokenLifetime = YEAR; -``` - ### Enable Account Activation via Email > **Note** diff --git a/mkdocs.yml b/mkdocs.yml index 2d1bb83e9..b4b1fefd9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,8 +43,8 @@ nav: - Installation: install.md - Authenticators and Filters: authenticators_and_filters.md - Quick Start Guide: - - quick_start_guide/authentication_flow.md - - quick_start_guide/authorization_flow.md + - quick_start_guide/using_session_auth.md + - quick_start_guide/using_access_tokens_auth.md - quick_start_guide/managing_users.md - References: - Authentication: authentication.md From a3d84c8fb2fb9d79bdc1954506979bb7ef0ec907 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 09:24:52 +0900 Subject: [PATCH 266/401] docs: add "Using Authorization" and move the existing contents. --- .../using_access_tokens_auth.md | 124 ----------------- docs/quick_start_guide/using_authorization.md | 127 ++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 128 insertions(+), 124 deletions(-) create mode 100644 docs/quick_start_guide/using_authorization.md diff --git a/docs/quick_start_guide/using_access_tokens_auth.md b/docs/quick_start_guide/using_access_tokens_auth.md index d493e6df2..9938680d1 100644 --- a/docs/quick_start_guide/using_access_tokens_auth.md +++ b/docs/quick_start_guide/using_access_tokens_auth.md @@ -9,127 +9,3 @@ By default, Access Tokens can be used for 1 year since the last use. This can be ```php public int $unusedTokenLifetime = YEAR; ``` - -### Change Available Groups - -The available groups are defined in the **app/Config/AuthGroups.php** config file, under the `$groups` property. Add new entries to the array, or remove existing ones to make them available throughout your application. - -```php -public array $groups = [ - 'superadmin' => [ - 'title' => 'Super Admin', - 'description' => 'Complete control of the site.', - ], - // -]; -``` - -### Set the Default Group - -When a user registers on your site, they are assigned the group specified at `Config\AuthGroups::$defaultGroup`. Change this to one of the keys in the `$groups` array to update this. - -### Change Available Permissions - -The permissions on the site are stored in the `AuthGroups` config file also. Each one is defined by a string that represents a context and a permission, joined with a decimal point. - -```php -public array $permissions = [ - 'admin.access' => 'Can access the sites admin area', - 'admin.settings' => 'Can access the main site settings', - 'users.manage-admins' => 'Can manage other admins', - 'users.create' => 'Can create new non-admin users', - 'users.edit' => 'Can edit existing non-admin users', - 'users.delete' => 'Can delete existing non-admin users', - 'beta.access' => 'Can access beta-level features', -]; -``` - -### Assign Permissions to a Group - -Each group can have its own specific set of permissions. These are defined in `Config\AuthGroups::$matrix`. You can specify each permission by it's full name, or using the context and an asterisk (*) to specify all permissions within that context. - -```php -public array $matrix = [ - 'superadmin' => [ - 'admin.*', - 'users.*', - 'beta.access', - ], - // -]; -``` - -## Assign Permissions to a User - -Permissions can also be assigned directly to a user, regardless of what groups they belong to. This is done programatically on the `User` Entity. - -```php -$user = auth()->user(); - -$user->addPermission('users.create', 'beta.access'); -``` - -This will add all new permissions. You can also sync permissions so that the user ONLY has the given permissions directly assigned to them. Any not in the provided list are removed from the user. - -```php -$user = auth()->user(); - -$user->syncPermissions('users.create', 'beta.access'); -``` - -## Check If a User Has Permission - -When you need to check if a user has a specific permission use the `can()` method on the `User` entity. This method checks permissions within the groups they belong to and permissions directly assigned to the user. - -```php -if (! auth()->user()->can('users.create')) { - return redirect()->back()->with('error', 'You do not have permissions to access that page.'); -} -``` - -> **Note** The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. - -## Adding a Group To a User - -Groups are assigned to a user via the `addGroup()` method. You can pass multiple groups in and they will all be assigned to the user. - -```php -$user = auth()->user(); -$user->addGroup('admin', 'beta'); -``` - -This will add all new groups. You can also sync groups so that the user ONLY belongs to the groups directly assigned to them. Any not in the provided list are removed from the user. - -```php -$user = auth()->user(); -$user->syncGroups('admin', 'beta'); -``` - -## Removing a Group From a User - -Groups are removed from a user via the `removeGroup()` method. Multiple groups may be removed at once by passing all of their names into the method. - -```php -$user = auth()->user(); -$user->removeGroup('admin', 'beta'); -``` - -## Checking If User Belongs To a Group - -You can check if a user belongs to a group with the `inGroup()` method. - -```php -$user = auth()->user(); -if ($user->inGroup('admin')) { - // do something -} -``` - -You can pass more than one group to the method and it will return `true` if the user belongs to any of the specified groups. - -```php -$user = auth()->user(); -if ($user->inGroup('admin', 'beta')) { - // do something -} -``` diff --git a/docs/quick_start_guide/using_authorization.md b/docs/quick_start_guide/using_authorization.md new file mode 100644 index 000000000..ba2a99d3b --- /dev/null +++ b/docs/quick_start_guide/using_authorization.md @@ -0,0 +1,127 @@ +# Using Authorization + +## Configure + +### Change Available Groups + +The available groups are defined in the **app/Config/AuthGroups.php** config file, under the `$groups` property. Add new entries to the array, or remove existing ones to make them available throughout your application. + +```php +public array $groups = [ + 'superadmin' => [ + 'title' => 'Super Admin', + 'description' => 'Complete control of the site.', + ], + // +]; +``` + +### Set the Default Group + +When a user registers on your site, they are assigned the group specified at `Config\AuthGroups::$defaultGroup`. Change this to one of the keys in the `$groups` array to update this. + +### Change Available Permissions + +The permissions on the site are stored in the `AuthGroups` config file also. Each one is defined by a string that represents a context and a permission, joined with a decimal point. + +```php +public array $permissions = [ + 'admin.access' => 'Can access the sites admin area', + 'admin.settings' => 'Can access the main site settings', + 'users.manage-admins' => 'Can manage other admins', + 'users.create' => 'Can create new non-admin users', + 'users.edit' => 'Can edit existing non-admin users', + 'users.delete' => 'Can delete existing non-admin users', + 'beta.access' => 'Can access beta-level features', +]; +``` + +### Assign Permissions to a Group + +Each group can have its own specific set of permissions. These are defined in `Config\AuthGroups::$matrix`. You can specify each permission by it's full name, or using the context and an asterisk (*) to specify all permissions within that context. + +```php +public array $matrix = [ + 'superadmin' => [ + 'admin.*', + 'users.*', + 'beta.access', + ], + // +]; +``` + +## Assign Permissions to a User + +Permissions can also be assigned directly to a user, regardless of what groups they belong to. This is done programatically on the `User` Entity. + +```php +$user = auth()->user(); + +$user->addPermission('users.create', 'beta.access'); +``` + +This will add all new permissions. You can also sync permissions so that the user ONLY has the given permissions directly assigned to them. Any not in the provided list are removed from the user. + +```php +$user = auth()->user(); + +$user->syncPermissions('users.create', 'beta.access'); +``` + +## Check If a User Has Permission + +When you need to check if a user has a specific permission use the `can()` method on the `User` entity. This method checks permissions within the groups they belong to and permissions directly assigned to the user. + +```php +if (! auth()->user()->can('users.create')) { + return redirect()->back()->with('error', 'You do not have permissions to access that page.'); +} +``` + +> **Note** The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. + +## Adding a Group To a User + +Groups are assigned to a user via the `addGroup()` method. You can pass multiple groups in and they will all be assigned to the user. + +```php +$user = auth()->user(); +$user->addGroup('admin', 'beta'); +``` + +This will add all new groups. You can also sync groups so that the user ONLY belongs to the groups directly assigned to them. Any not in the provided list are removed from the user. + +```php +$user = auth()->user(); +$user->syncGroups('admin', 'beta'); +``` + +## Removing a Group From a User + +Groups are removed from a user via the `removeGroup()` method. Multiple groups may be removed at once by passing all of their names into the method. + +```php +$user = auth()->user(); +$user->removeGroup('admin', 'beta'); +``` + +## Checking If User Belongs To a Group + +You can check if a user belongs to a group with the `inGroup()` method. + +```php +$user = auth()->user(); +if ($user->inGroup('admin')) { + // do something +} +``` + +You can pass more than one group to the method and it will return `true` if the user belongs to any of the specified groups. + +```php +$user = auth()->user(); +if ($user->inGroup('admin', 'beta')) { + // do something +} +``` diff --git a/mkdocs.yml b/mkdocs.yml index b4b1fefd9..bafd6e637 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - Authenticators and Filters: authenticators_and_filters.md - Quick Start Guide: - quick_start_guide/using_session_auth.md + - quick_start_guide/using_authorization.md - quick_start_guide/using_access_tokens_auth.md - quick_start_guide/managing_users.md - References: From 2952e1fcd80f92d384412b66f5c61e9d48fba42b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 09:09:45 +0900 Subject: [PATCH 267/401] docs: replace "Magic Login Links" with "Magic Link Login" For consistency. --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 615a5ed1c..0fcb10842 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ The primary goals for Shield are: * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration * Optional Email-based Two-Factor Authentication after login -* Magic Login Links when a user forgets their password +* Magic Link Login when a user forgets their password * Flexible Groups-based access control (think Roles, but more flexible) * Users can be granted additional Permissions From 0d3c652fd9f598ef0b3bff00da9f3736536163cf Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 09:14:09 +0900 Subject: [PATCH 268/401] docs: add page for Magic Link Login and move the existing content. --- docs/magic_link_login.md | 34 ++++++++++++++++++++ docs/quick_start_guide/using_session_auth.md | 33 ------------------- mkdocs.yml | 3 +- 3 files changed, 36 insertions(+), 34 deletions(-) create mode 100644 docs/magic_link_login.md diff --git a/docs/magic_link_login.md b/docs/magic_link_login.md new file mode 100644 index 000000000..700654cfd --- /dev/null +++ b/docs/magic_link_login.md @@ -0,0 +1,34 @@ +# Magic Link Login + +## Responding to Magic Link Logins + +> **Note** +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). + +Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. + +### Session Notification + +You can detect if a user has finished the magic link login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`. + +```php +if (session('magicLogin')) { + return redirect()->route('set_password'); +} +``` + +This value sticks around in the session for 5 minutes. Once you no longer need to take any actions, you might want to delete the value from the session. + +```php +session()->removeTempdata('magicLogin'); +``` + +### Event + +At the same time the above session variable is set, a `magicLogin` [event](https://codeigniter.com/user_guide/extending/events.html) is fired off that you may subscribe to. Note that no data is passed to the event as you can easily grab the current user from the `user()` helper or the `auth()->user()` method. + +```php +Events::on('magicLogin', static function () { + // ... +}); +``` diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index cd42a36f8..134f7640b 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -65,36 +65,3 @@ public array $actions = [ 'login' => \CodeIgniter\Shield\Authentication\Actions\Email2FA::class, ]; ``` - -## Responding to Magic Link Logins - -> **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). - -Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. - -### Session Notification - -You can detect if a user has finished the magic link login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`. - -```php -if (session('magicLogin')) { - return redirect()->route('set_password'); -} -``` - -This value sticks around in the session for 5 minutes. Once you no longer need to take any actions, you might want to delete the value from the session. - -```php -session()->removeTempdata('magicLogin'); -``` - -### Event - -At the same time the above session variable is set, a `magicLogin` [event](https://codeigniter.com/user_guide/extending/events.html) is fired off that you may subscribe to. Note that no data is passed to the event as you can easily grab the current user from the `user()` helper or the `auth()->user()` method. - -```php -Events::on('magicLogin', static function () { - // ... -}); -``` diff --git a/mkdocs.yml b/mkdocs.yml index bafd6e637..ac2d2dfcd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,8 +49,9 @@ nav: - quick_start_guide/managing_users.md - References: - Authentication: authentication.md + - auth_actions.md + - magic_link_login.md - Authorization: authorization.md - - Auth Actions: auth_actions.md - Events: events.md - Testing: testing.md - session_auth_event_and_logging.md From 0ffd7c6595f6871d0895c084511da6941b617ea1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 09:32:48 +0900 Subject: [PATCH 269/401] docs: add "Controller Filters" and move the existing contents. --- docs/authenticators_and_filters.md | 100 ----------------------------- docs/controller_filters.md | 99 ++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 100 insertions(+), 100 deletions(-) create mode 100644 docs/controller_filters.md diff --git a/docs/authenticators_and_filters.md b/docs/authenticators_and_filters.md index 340b7fcc3..e628302b8 100644 --- a/docs/authenticators_and_filters.md +++ b/docs/authenticators_and_filters.md @@ -8,103 +8,3 @@ Shield provides the following Authenticators: - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](./addons/jwt.md). - -## Controller Filters - -The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: - -```php -public $aliases = [ - // ... - 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, - 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, - 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, - 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, - 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, - 'permission' => \CodeIgniter\Shield\Filters\PermissionFilter::class, - 'force-reset' => \CodeIgniter\Shield\Filters\ForcePasswordResetFilter::class, - 'jwt' => \CodeIgniter\Shield\Filters\JWTAuth::class, -]; -``` - -Filters | Description ---- | --- -session and tokens | The `Session` and `AccessTokens` authenticators, respectively. -chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. -jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). -auth-rates | Provides a good basis for rate limiting of auth-related routes. -group | Checks if the user is in one of the groups passed in. -permission | Checks if the user has the passed permissions. -force-reset | Checks if the user requires a password reset. - -These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). - -> **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. - -## Configure Controller Filters - -### Protect All Pages - -If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. - -```php -public $globals = [ - 'before' => [ - // ... - 'session' => ['except' => ['login*', 'register', 'auth/a/*']], - ], - // ... -]; -``` - -### Rate Limiting - -To help protect your authentication forms from being spammed by bots, it is recommended that you use -the `auth-rates` filter on all of your authentication routes. This can be done with the following -filter setup: - -```php -public $filters = [ - 'auth-rates' => [ - 'before' => [ - 'login*', 'register', 'auth/*' - ] - ] -]; -``` - -### Forcing Password Reset - -If your application requires a force password reset functionality, ensure that you exclude the auth pages and the actual password reset page from the `before` global. This will ensure that your users do not run into a *too many redirects* error. See: - -```php -public $globals = [ - 'before' => [ - //... - //... - 'force-reset' => ['except' => ['login*', 'register', 'auth/a/*', 'change-password', 'logout']] - ] -]; -``` -In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. - -> **Note** If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. - -For example, if you configured your routes like so: - -```php -$routes->group('accounts', static function($routes) { - service('auth')->routes($routes); -}); -``` -Then the global `before` filter for `session` should look like so: - -```php -public $globals = [ - 'before' => [ - // ... - 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*']] - ] -] -``` -The same should apply for the Rate Limiting and Forcing Password Reset. diff --git a/docs/controller_filters.md b/docs/controller_filters.md new file mode 100644 index 000000000..210d9239c --- /dev/null +++ b/docs/controller_filters.md @@ -0,0 +1,99 @@ +# Controller Filters + +The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: + +```php +public $aliases = [ + // ... + 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, + 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, + 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, + 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, + 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, + 'permission' => \CodeIgniter\Shield\Filters\PermissionFilter::class, + 'force-reset' => \CodeIgniter\Shield\Filters\ForcePasswordResetFilter::class, + 'jwt' => \CodeIgniter\Shield\Filters\JWTAuth::class, +]; +``` + +Filters | Description +--- | --- +session and tokens | The `Session` and `AccessTokens` authenticators, respectively. +chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. +jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). +auth-rates | Provides a good basis for rate limiting of auth-related routes. +group | Checks if the user is in one of the groups passed in. +permission | Checks if the user has the passed permissions. +force-reset | Checks if the user requires a password reset. + +These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). + +> **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. + +## Configure Controller Filters + +### Protect All Pages + +If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['login*', 'register', 'auth/a/*']], + ], + // ... +]; +``` + +### Rate Limiting + +To help protect your authentication forms from being spammed by bots, it is recommended that you use +the `auth-rates` filter on all of your authentication routes. This can be done with the following +filter setup: + +```php +public $filters = [ + 'auth-rates' => [ + 'before' => [ + 'login*', 'register', 'auth/*' + ] + ] +]; +``` + +### Forcing Password Reset + +If your application requires a force password reset functionality, ensure that you exclude the auth pages and the actual password reset page from the `before` global. This will ensure that your users do not run into a *too many redirects* error. See: + +```php +public $globals = [ + 'before' => [ + //... + //... + 'force-reset' => ['except' => ['login*', 'register', 'auth/a/*', 'change-password', 'logout']] + ] +]; +``` +In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. + +> **Note** If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. + +For example, if you configured your routes like so: + +```php +$routes->group('accounts', static function($routes) { + service('auth')->routes($routes); +}); +``` +Then the global `before` filter for `session` should look like so: + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*']] + ] +] +``` +The same should apply for the Rate Limiting and Forcing Password Reset. diff --git a/mkdocs.yml b/mkdocs.yml index ac2d2dfcd..b2762899a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - quick_start_guide/using_access_tokens_auth.md - quick_start_guide/managing_users.md - References: + - controller_filters.md - Authentication: authentication.md - auth_actions.md - magic_link_login.md From 228ad4874ce7bdc0a16c5b15fa927c6b97883245 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 09:35:02 +0900 Subject: [PATCH 270/401] docs: change filename --- docs/{authenticators_and_filters.md => authenticators.md} | 4 +--- mkdocs.yml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) rename docs/{authenticators_and_filters.md => authenticators.md} (86%) diff --git a/docs/authenticators_and_filters.md b/docs/authenticators.md similarity index 86% rename from docs/authenticators_and_filters.md rename to docs/authenticators.md index e628302b8..bf2e5e508 100644 --- a/docs/authenticators_and_filters.md +++ b/docs/authenticators.md @@ -1,6 +1,4 @@ -# Authenticators and Controller Filters - -## Authenticators +# Authenticators Shield provides the following Authenticators: diff --git a/mkdocs.yml b/mkdocs.yml index b2762899a..904b99ff1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,7 +41,7 @@ nav: - Getting Started: - Concepts: concepts.md - Installation: install.md - - Authenticators and Filters: authenticators_and_filters.md + - authenticators.md - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md From 6f260fe298215a845a7998177a733d41e58b8e38 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:06:44 +0900 Subject: [PATCH 271/401] docs: add descriptions --- docs/quick_start_guide/using_session_auth.md | 43 +++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index 134f7640b..6c7869b62 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -1,11 +1,13 @@ # Using Session Authenticator +**Session** authenticator provides traditional Email/Password authentication. + Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. > **Note** > The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. -## Configure +## Configuration ### Configure Redirect URLs @@ -27,7 +29,7 @@ public array $redirects = [ ### Configure Remember-me Functionality -Remember-me functionality is enabled by default for the `Session` authenticator. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. +Remember-me functionality is enabled by default. While this is handled in a secure manner, some sites may want it disabled. You might also want to change how long it remembers a user and doesn't require additional login. ```php public array $sessionConfig = [ @@ -65,3 +67,40 @@ public array $actions = [ 'login' => \CodeIgniter\Shield\Authentication\Actions\Email2FA::class, ]; ``` + +## Customizing Routes + +If you need to customize how any of the auth features are handled, you can still +use the `service('auth')->routes()` helper, but you will need to pass the `except` +option with a list of routes to customize: + +```php +service('auth')->routes($routes, ['except' => ['login', 'register']]); +``` + +Then add the routes to your customized controllers: + +```php +$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); +$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); +``` + +Check your routes with the [spark routes](https://codeigniter.com/user_guide/incoming/routing.html#spark-routes) +command. + +## Protecting Pages + +If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['login*', 'register', 'auth/a/*']], + ], + // ... +]; +``` + +Check your filters with the [spark routes](https://codeigniter.com/user_guide/incoming/routing.html#spark-routes) +command. From 7008664cb89bf4f29cc85c98b48816f74f8bfbbe Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:23:26 +0900 Subject: [PATCH 272/401] docs: remove using_access_tokens_auth.md --- docs/authenticators.md | 14 ++++++++++++++ docs/quick_start_guide/using_access_tokens_auth.md | 11 ----------- mkdocs.yml | 1 - 3 files changed, 14 insertions(+), 12 deletions(-) delete mode 100644 docs/quick_start_guide/using_access_tokens_auth.md diff --git a/docs/authenticators.md b/docs/authenticators.md index bf2e5e508..c73f0ffae 100644 --- a/docs/authenticators.md +++ b/docs/authenticators.md @@ -1,8 +1,22 @@ # Authenticators +## Authenticator List + Shield provides the following Authenticators: - **Session** authenticator provides traditional Email/Password authentication. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](./addons/jwt.md). + +## Configuration + +### AccessTokens Authenticator + +#### Change Access Token Lifetime + +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. + +```php +public int $unusedTokenLifetime = YEAR; +``` diff --git a/docs/quick_start_guide/using_access_tokens_auth.md b/docs/quick_start_guide/using_access_tokens_auth.md deleted file mode 100644 index 9938680d1..000000000 --- a/docs/quick_start_guide/using_access_tokens_auth.md +++ /dev/null @@ -1,11 +0,0 @@ -# Using AccessTokens Authenticator - -## Configure - -### Change Access Token Lifetime - -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. - -```php -public int $unusedTokenLifetime = YEAR; -``` diff --git a/mkdocs.yml b/mkdocs.yml index 904b99ff1..e27e0c0c0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,7 +45,6 @@ nav: - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md - - quick_start_guide/using_access_tokens_auth.md - quick_start_guide/managing_users.md - References: - controller_filters.md From 85fd665cb161903a9cc7447578cfee9b02367f6b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:24:28 +0900 Subject: [PATCH 273/401] docs: change section title --- docs/quick_start_guide/using_authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_start_guide/using_authorization.md b/docs/quick_start_guide/using_authorization.md index ba2a99d3b..8d75c9952 100644 --- a/docs/quick_start_guide/using_authorization.md +++ b/docs/quick_start_guide/using_authorization.md @@ -1,6 +1,6 @@ # Using Authorization -## Configure +## Configuration ### Change Available Groups From adf6024b3920ebe2309200e0a19056a95572996c Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:27:47 +0900 Subject: [PATCH 274/401] docs: add links --- docs/authenticators.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/authenticators.md b/docs/authenticators.md index c73f0ffae..f6821148a 100644 --- a/docs/authenticators.md +++ b/docs/authenticators.md @@ -5,7 +5,10 @@ Shield provides the following Authenticators: - **Session** authenticator provides traditional Email/Password authentication. + See [Using Session Authenticator](./quick_start_guide/using_session_auth.md) + for usage. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. + See [Protecting an API with Access Tokens](./guides/api_tokens.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](./addons/jwt.md). From cf1e5f1508c978f668599f07e7554c7d93aa12c5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:36:14 +0900 Subject: [PATCH 275/401] docs: add "Getting Started > Configuration" --- docs/authenticators.md | 12 ------------ docs/configuration.md | 11 +++++++++++ mkdocs.yml | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 docs/configuration.md diff --git a/docs/authenticators.md b/docs/authenticators.md index f6821148a..351eace03 100644 --- a/docs/authenticators.md +++ b/docs/authenticators.md @@ -11,15 +11,3 @@ Shield provides the following Authenticators: See [Protecting an API with Access Tokens](./guides/api_tokens.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](./addons/jwt.md). - -## Configuration - -### AccessTokens Authenticator - -#### Change Access Token Lifetime - -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. - -```php -public int $unusedTokenLifetime = YEAR; -``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..65580f668 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,11 @@ +# Configuration + +## AccessTokens Authenticator + +### Change Access Token Lifetime + +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. + +```php +public int $unusedTokenLifetime = YEAR; +``` diff --git a/mkdocs.yml b/mkdocs.yml index e27e0c0c0..3cd974a47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ nav: - Concepts: concepts.md - Installation: install.md - authenticators.md + - configuration.md - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md From 80e46ebe4bf6f9b4e1180d48da5c92915e97d6d4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:37:28 +0900 Subject: [PATCH 276/401] docs: update link --- docs/magic_link_login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/magic_link_login.md b/docs/magic_link_login.md index 700654cfd..26687f975 100644 --- a/docs/magic_link_login.md +++ b/docs/magic_link_login.md @@ -3,7 +3,7 @@ ## Responding to Magic Link Logins > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](./install.md#initial-setup). Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. From 604b3903d14dfedcb3ed5d343a81d8358ed4087f Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:43:58 +0900 Subject: [PATCH 277/401] docs: fix section title --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 65580f668..cd05668c4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,7 +2,7 @@ ## AccessTokens Authenticator -### Change Access Token Lifetime +### Access Token Lifetime By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. From 32ab5ca711f086a562ce029e1cc006188967c67f Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:44:14 +0900 Subject: [PATCH 278/401] docs: remove duplicated "the" --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index cd05668c4..f31ac38c2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,7 +4,7 @@ ### Access Token Lifetime -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the the **app/Config/Auth.php** config file. +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the **app/Config/Auth.php** config file. ```php public int $unusedTokenLifetime = YEAR; From 38fb0daff21adb389f88429cd5a484dc04697643 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:47:22 +0900 Subject: [PATCH 279/401] docs: move Configuration to References --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 3cd974a47..129f7a6f6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,12 +42,12 @@ nav: - Concepts: concepts.md - Installation: install.md - authenticators.md - - configuration.md - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md - quick_start_guide/managing_users.md - References: + - configuration.md - controller_filters.md - Authentication: authentication.md - auth_actions.md From ae56615408985ff8372ba91c798830196b1ee9be Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 11:53:04 +0900 Subject: [PATCH 280/401] docs: add explanation to configuration.md --- docs/configuration.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index f31ac38c2..df421818d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,5 +1,20 @@ # Configuration +## Config files + +Shield has a lot of Config items. Change the default values as needed. + +If you have completed the setup according to this documentation, you will have +the following configuration files: + +- **app/Config/Auth.php** +- **app/Config/AuthGroups.php** - For Authorization +- **app/Config/AuthJWT.php** - For JWT Authentication + +Note that you do not need to have configuration files for features you do not use. + +This section describes the major Config items that are not described elsewhere. + ## AccessTokens Authenticator ### Access Token Lifetime From 969e47c88c18023cd4e415ce5113d8fcb2e5c97b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 15:51:47 +0900 Subject: [PATCH 281/401] docs: add empty line --- docs/customization/integrating_custom_view_libs.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/customization/integrating_custom_view_libs.md b/docs/customization/integrating_custom_view_libs.md index fc97891bb..4025de85c 100644 --- a/docs/customization/integrating_custom_view_libs.md +++ b/docs/customization/integrating_custom_view_libs.md @@ -1,6 +1,8 @@ # Integrating Custom View Libraries -If your application uses a different method to convert view files to HTML than CodeIgniter's built-in `view()` helper you can easily integrate your system anywhere that a view is rendered within Shield. All controllers and actions use the `CodeIgniter\Shield\Traits\Viewable` trait which provides a simple `view()` method that takes the same arguments as the `view()` helper. This allows you to extend the Action or Controller and only change the single method of rendering the view, leaving all of the logic untouched so your app will not need to maintain Shield logic when it doesn't need to change it. +If your application uses a different method to convert view files to HTML than CodeIgniter's built-in `view()` helper, you can easily integrate your system anywhere that a view is rendered within Shield. + +All controllers and actions use the `CodeIgniter\Shield\Traits\Viewable` trait which provides a simple `view()` method that takes the same arguments as the `view()` helper. This allows you to extend the Action or Controller and only change the single method of rendering the view, leaving all of the logic untouched so your app will not need to maintain Shield logic when it doesn't need to change it. ```php use Acme\Themes\Traits\Themeable; From d7008e8594ea9da2866c28adaa394f55e12606d2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 16:13:46 +0900 Subject: [PATCH 282/401] docs: move configuration.md to "Getting Started" --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 129f7a6f6..3cd974a47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,12 +42,12 @@ nav: - Concepts: concepts.md - Installation: install.md - authenticators.md + - configuration.md - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md - quick_start_guide/managing_users.md - References: - - configuration.md - controller_filters.md - Authentication: authentication.md - auth_actions.md From a6a6d12d3918c7d83fa7a9d29062080f00f61836 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Sep 2023 16:25:09 +0900 Subject: [PATCH 283/401] docs: add Configuration in magic_link_login.md --- docs/magic_link_login.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/magic_link_login.md b/docs/magic_link_login.md index 26687f975..ef4b2f15a 100644 --- a/docs/magic_link_login.md +++ b/docs/magic_link_login.md @@ -1,5 +1,28 @@ # Magic Link Login +Magic Link Login is a feature that allows users to log in if they forget their +password. + +## Configuration + +### Configure Magic Link Login Functionality + +Magic Link Login functionality is enabled by default. +You can change it within the **app/Config/Auth.php** file. + +```php +public bool $allowMagicLinkLogins = true; +``` + +### Magic Link Lifetime + +By default, Magic Link can be used for 1 hour. This can be easily modified +in the **app/Config/Auth.php** file. + +```php +public int $magicLinkLifetime = HOUR; +``` + ## Responding to Magic Link Logins > **Note** From 14a607c1cc1ea62deaf2cd2c6011dacf3fa87f3f Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:39:01 +0900 Subject: [PATCH 284/401] docs: fix rebase mistake --- docs/controller_filters.md | 20 ++++---- docs/install.md | 100 ------------------------------------- 2 files changed, 11 insertions(+), 109 deletions(-) diff --git a/docs/controller_filters.md b/docs/controller_filters.md index 210d9239c..b59a0042a 100644 --- a/docs/controller_filters.md +++ b/docs/controller_filters.md @@ -7,6 +7,7 @@ public $aliases = [ // ... 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, + 'hmac' => \CodeIgniter\Shield\Filters\HmacAuth::class, 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, @@ -16,15 +17,16 @@ public $aliases = [ ]; ``` -Filters | Description ---- | --- -session and tokens | The `Session` and `AccessTokens` authenticators, respectively. -chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. -jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). -auth-rates | Provides a good basis for rate limiting of auth-related routes. -group | Checks if the user is in one of the groups passed in. -permission | Checks if the user has the passed permissions. -force-reset | Checks if the user requires a password reset. +| Filters | Description | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| session and tokens | The `Session` and `AccessTokens` authenticators, respectively. | +| chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | +| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | +| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | +| auth-rates | Provides a good basis for rate limiting of auth-related routes. | +| group | Checks if the user is in one of the groups passed in. | +| permission | Checks if the user has the passed permissions. | +| force-reset | Checks if the user requires a password reset. | These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). diff --git a/docs/install.md b/docs/install.md index 47aad61b0..59f76fd56 100644 --- a/docs/install.md +++ b/docs/install.md @@ -181,103 +181,3 @@ your project. // ... } ``` - -## Controller Filters - -The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes the shield provides are: - -```php -public $aliases = [ - // ... - 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, - 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, - 'hmac' => \CodeIgniter\Shield\Filters\HmacAuth::class, - 'chain' => \CodeIgniter\Shield\Filters\ChainAuth::class, - 'auth-rates' => \CodeIgniter\Shield\Filters\AuthRates::class, - 'group' => \CodeIgniter\Shield\Filters\GroupFilter::class, - 'permission' => \CodeIgniter\Shield\Filters\PermissionFilter::class, - 'force-reset' => \CodeIgniter\Shield\Filters\ForcePasswordResetFilter::class, - 'jwt' => \CodeIgniter\Shield\Filters\JWTAuth::class, -]; -``` - -| Filters | Description | -|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| session and tokens | The `Session` and `AccessTokens` authenticators, respectively. | -| chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | -| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | -| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | -| auth-rates | Provides a good basis for rate limiting of auth-related routes. | -| group | Checks if the user is in one of the groups passed in. | -| permission | Checks if the user has the passed permissions. | -| force-reset | Checks if the user requires a password reset. | - -These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). - -> **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. - -### Protect All Pages - -If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/panel` and ...), you need to add the following code in the **app/Config/Filters.php** file. - -```php -public $globals = [ - 'before' => [ - // ... - 'session' => ['except' => ['login*', 'register', 'auth/a/*']], - ], - // ... -]; -``` - -### Rate Limiting - -To help protect your authentication forms from being spammed by bots, it is recommended that you use -the `auth-rates` filter on all of your authentication routes. This can be done with the following -filter setup: - -```php -public $filters = [ - 'auth-rates' => [ - 'before' => [ - 'login*', 'register', 'auth/*' - ] - ] -]; -``` - -### Forcing Password Reset - -If your application requires a force password reset functionality, ensure that you exclude the auth pages and the actual password reset page from the `before` global. This will ensure that your users do not run into a *too many redirects* error. See: - -```php -public $globals = [ - 'before' => [ - //... - //... - 'force-reset' => ['except' => ['login*', 'register', 'auth/a/*', 'change-password', 'logout']] - ] -]; -``` -In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. - -> **Note** If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. - -For example, if you configured your routes like so: - -```php -$routes->group('accounts', static function($routes) { - service('auth')->routes($routes); -}); -``` -Then the global `before` filter for `session` should look like so: - -```php -public $globals = [ - 'before' => [ - // ... - 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*']] - ] -] -``` -The same should apply for the Rate Limiting and Forcing Password Reset. From dfe0448cd99758140e0933af2cd271642c689094 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:40:45 +0900 Subject: [PATCH 285/401] docs: add empty lines and line breaks --- docs/controller_filters.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/controller_filters.md b/docs/controller_filters.md index b59a0042a..9a8eaf9be 100644 --- a/docs/controller_filters.md +++ b/docs/controller_filters.md @@ -30,7 +30,8 @@ public $aliases = [ These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). -> **Note** These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. +> **Note** +> These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. ## Configure Controller Filters @@ -79,7 +80,8 @@ public $globals = [ ``` In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. -> **Note** If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. +> **Note** +> If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. For example, if you configured your routes like so: @@ -88,6 +90,7 @@ $routes->group('accounts', static function($routes) { service('auth')->routes($routes); }); ``` + Then the global `before` filter for `session` should look like so: ```php @@ -98,4 +101,5 @@ public $globals = [ ] ] ``` + The same should apply for the Rate Limiting and Forcing Password Reset. From 331a81e618d25329a98f6a6768cd011a85f33e18 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:46:12 +0900 Subject: [PATCH 286/401] docs: improve descriptions for Filters --- docs/controller_filters.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/controller_filters.md b/docs/controller_filters.md index 9a8eaf9be..f219200c1 100644 --- a/docs/controller_filters.md +++ b/docs/controller_filters.md @@ -17,16 +17,17 @@ public $aliases = [ ]; ``` -| Filters | Description | -|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| session and tokens | The `Session` and `AccessTokens` authenticators, respectively. | -| chained | The filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | -| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | -| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | -| auth-rates | Provides a good basis for rate limiting of auth-related routes. | -| group | Checks if the user is in one of the groups passed in. | -| permission | Checks if the user has the passed permissions. | -| force-reset | Checks if the user requires a password reset. | +| Filters | Description | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| session | The `Session` authenticator. | +| tokens | The `AccessTokens` authenticator. | +| chained | The filter will check authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | +| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | +| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | +| auth-rates | Provides a good basis for rate limiting of auth-related routes. | +| group | Checks if the user is in one of the groups passed in. | +| permission | Checks if the user has the passed permissions. | +| force-reset | Checks if the user requires a password reset. | These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). From ba72bddd4f539e391efe65dd22dd708871087477 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:48:20 +0900 Subject: [PATCH 287/401] docs: add line break after **Note** --- docs/authorization.md | 15 ++++++++++----- docs/customization/table_names.md | 3 ++- docs/customization/validation_rules.md | 6 ++++-- docs/guides/api_tokens.md | 3 ++- docs/install.md | 6 ++++-- docs/quick_start_guide/managing_users.md | 3 ++- docs/quick_start_guide/using_authorization.md | 3 ++- 7 files changed, 26 insertions(+), 13 deletions(-) diff --git a/docs/authorization.md b/docs/authorization.md index 571af598d..67d5af0df 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -57,7 +57,8 @@ public array $permissions = [ In order to grant any permissions to a group, they must have the permission assigned to the group, within the `AuthGroups` config file, under the `$matrix` property. -> **Note** This defines **group-level permissons**. +> **Note** +> This defines **group-level permissons**. The matrix is an associative array with the group name as the key, and an array of permissions that should be applied to that group. @@ -121,7 +122,8 @@ if (! $user->hasPermission('users.create')) { } ``` -> **Note** This method checks only **user-level permissions**, and does not check +> **Note** +> This method checks only **user-level permissions**, and does not check > group-level permissions. If you want to check if the user can do something, > use the `$user->can()` method instead. @@ -150,7 +152,8 @@ $routes->group('admin', ['filter' => 'group:admin,superadmin'], static function Note that the options (`filter`) passed to the outer `group()` are not merged with the inner `group()` options. -> **Note** If you set more than one filter to a route, you need to enable +> **Note** +> If you set more than one filter to a route, you need to enable > [Multiple Filters](https://codeigniter.com/user_guide/incoming/routing.html#multiple-filters). ## Managing User Permissions @@ -193,7 +196,8 @@ Returns all **user-level** permissions this user has assigned directly to them. $user->getPermissions(); ``` -> **Note** This method does not return **group-level permissions**. +> **Note** +> This method does not return **group-level permissions**. ## Managing User Groups @@ -246,7 +250,8 @@ if ($user->isActivated()) { } ``` -> **Note** If no activator is specified in the `Auth` config file, `actions['register']` property, then this will always return `true`. +> **Note** +> If no activator is specified in the `Auth` config file, `actions['register']` property, then this will always return `true`. You can check if a user has not been activated yet via the `isNotActivated()` method. diff --git a/docs/customization/table_names.md b/docs/customization/table_names.md index 2fcaad8c7..03eca1c98 100644 --- a/docs/customization/table_names.md +++ b/docs/customization/table_names.md @@ -17,4 +17,5 @@ public array $tables = [ Set the table names that you want in the array values. -> **Note** You must change the table names before running database migrations. +> **Note** +> You must change the table names before running database migrations. diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md index f0442ff72..f637d4301 100644 --- a/docs/customization/validation_rules.md +++ b/docs/customization/validation_rules.md @@ -36,7 +36,8 @@ Shield has the following rules for registration: ]; ``` -> **Note** If you customize the table names, the table names +> **Note** +> If you customize the table names, the table names > (`users` and `auth_identities`) in the above rules will be automatically > changed. The rules are implemented in > `RegisterController::getValidationRules()`. @@ -78,7 +79,8 @@ If you need a different set of rules for registration, you can specify them in y ]; ``` -> **Note** If you customize the table names, set the correct table names in the +> **Note** +> If you customize the table names, set the correct table names in the > rules. ## Login diff --git a/docs/guides/api_tokens.md b/docs/guides/api_tokens.md index ed9dee901..50824d17a 100644 --- a/docs/guides/api_tokens.md +++ b/docs/guides/api_tokens.md @@ -2,7 +2,8 @@ Access Tokens can be used to authenticate users for your own site, or when allowing third-party developers to access your API. When making requests using access tokens, the token should be included in the `Authorization` header as a `Bearer` token. -> **Note** By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/Auth.php** config file. +> **Note** +> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/Auth.php** config file. Tokens are issued with the `generateAccessToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. Tokens are hashed using a SHA-256 algorithm before being saved to the database. The access token returned when you generate it will include a `raw_token` field that contains the plain-text, un-hashed, token. You should display this to your user at once so they have a chance to copy it somewhere safe, as this is the only time this will be available. After this request, there is no way to get the raw token. diff --git a/docs/install.md b/docs/install.md index 59f76fd56..26c17c1a1 100644 --- a/docs/install.md +++ b/docs/install.md @@ -61,7 +61,8 @@ Require it with an explicit version constraint allowing its desired stability. php spark shield:setup ``` - > **Note** If you want to customize table names, you must change the table names + > **Note** + > If you want to customize table names, you must change the table names > before running database migrations. > See [Customizing Table Names](./customization/table_names.md). @@ -138,7 +139,8 @@ your project. 5. **Migration** Run the migrations. - > **Note** If you want to customize table names, you must change the table names + > **Note** + > If you want to customize table names, you must change the table names > before running database migrations. > See [Customizing Table Names](./customization/table_names.md). diff --git a/docs/quick_start_guide/managing_users.md b/docs/quick_start_guide/managing_users.md index 84c57815b..3f4a2b3c0 100644 --- a/docs/quick_start_guide/managing_users.md +++ b/docs/quick_start_guide/managing_users.md @@ -37,7 +37,8 @@ $users = auth()->getProvider(); $users->delete($user->id, true); ``` -> **Note** The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. +> **Note** +> The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. ## Editing a User diff --git a/docs/quick_start_guide/using_authorization.md b/docs/quick_start_guide/using_authorization.md index 8d75c9952..c4d6d9f3a 100644 --- a/docs/quick_start_guide/using_authorization.md +++ b/docs/quick_start_guide/using_authorization.md @@ -79,7 +79,8 @@ if (! auth()->user()->can('users.create')) { } ``` -> **Note** The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. +> **Note** +> The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. ## Adding a Group To a User From a33cf5a2febbd16aa4e1443e9d66c7f6b0ba1475 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:51:19 +0900 Subject: [PATCH 288/401] docs: add HmacSha256 authenticator --- docs/authenticators.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/authenticators.md b/docs/authenticators.md index 351eace03..c00a4b6ec 100644 --- a/docs/authenticators.md +++ b/docs/authenticators.md @@ -9,5 +9,7 @@ Shield provides the following Authenticators: for usage. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. See [Protecting an API with Access Tokens](./guides/api_tokens.md) for usage. +- **HmacSha256** authenticator provides stateless authentication using HMAC Keys. + See [guides/api_hmac_keys](./guides/api_hmac_keys.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](./addons/jwt.md). From 6584605b4eba02c57c73036f3f8548763c3f5436 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 09:55:33 +0900 Subject: [PATCH 289/401] docs: move "Managing Users" to "Guides" --- docs/{quick_start_guide => guides}/managing_users.md | 0 mkdocs.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{quick_start_guide => guides}/managing_users.md (100%) diff --git a/docs/quick_start_guide/managing_users.md b/docs/guides/managing_users.md similarity index 100% rename from docs/quick_start_guide/managing_users.md rename to docs/guides/managing_users.md diff --git a/mkdocs.yml b/mkdocs.yml index 3cd974a47..95020c784 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,7 +46,6 @@ nav: - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md - - quick_start_guide/managing_users.md - References: - controller_filters.md - Authentication: authentication.md @@ -66,6 +65,7 @@ nav: - customization/user_provider.md - customization/login_identifier.md - Guides: + - guides/managing_users.md - guides/forcing_password_reset.md - guides/banning_users.md - guides/api_tokens.md From ae30aac04a71e835343fad829b1dc9c2761a6081 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:03:58 +0900 Subject: [PATCH 290/401] docs: add section "User Management" and change section order --- .../banning_users.md | 0 .../forcing_password_reset.md | 0 .../managing_users.md | 0 mkdocs.yml | 25 ++++++++++--------- 4 files changed, 13 insertions(+), 12 deletions(-) rename docs/{guides => user_management}/banning_users.md (100%) rename docs/{guides => user_management}/forcing_password_reset.md (100%) rename docs/{guides => user_management}/managing_users.md (100%) diff --git a/docs/guides/banning_users.md b/docs/user_management/banning_users.md similarity index 100% rename from docs/guides/banning_users.md rename to docs/user_management/banning_users.md diff --git a/docs/guides/forcing_password_reset.md b/docs/user_management/forcing_password_reset.md similarity index 100% rename from docs/guides/forcing_password_reset.md rename to docs/user_management/forcing_password_reset.md diff --git a/docs/guides/managing_users.md b/docs/user_management/managing_users.md similarity index 100% rename from docs/guides/managing_users.md rename to docs/user_management/managing_users.md diff --git a/mkdocs.yml b/mkdocs.yml index 95020c784..61622b499 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,15 +46,6 @@ nav: - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md - - References: - - controller_filters.md - - Authentication: authentication.md - - auth_actions.md - - magic_link_login.md - - Authorization: authorization.md - - Events: events.md - - Testing: testing.md - - session_auth_event_and_logging.md - Customization: - customization/table_names.md - customization/route_config.md @@ -64,13 +55,23 @@ nav: - customization/validation_rules.md - customization/user_provider.md - customization/login_identifier.md + - User Management: + - user_management/managing_users.md + - user_management/forcing_password_reset.md + - user_management/banning_users.md - Guides: - - guides/managing_users.md - - guides/forcing_password_reset.md - - guides/banning_users.md - guides/api_tokens.md - guides/api_hmac_keys.md - guides/mobile_apps.md - guides/strengthen_password.md + - References: + - controller_filters.md + - Authentication: authentication.md + - auth_actions.md + - magic_link_login.md + - Authorization: authorization.md + - Events: events.md + - Testing: testing.md + - session_auth_event_and_logging.md - Addons: - JWT Authentication: addons/jwt.md From 62685e84013c4ebc88ed5859025549c883e67a70 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:08:05 +0900 Subject: [PATCH 291/401] docs: change sub section order --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 61622b499..f568a7753 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,10 +50,10 @@ nav: - customization/table_names.md - customization/route_config.md - customization/redirect_urls.md - - customization/extending_controllers.md - - customization/integrating_custom_view_libs.md - customization/validation_rules.md - customization/user_provider.md + - customization/extending_controllers.md + - customization/integrating_custom_view_libs.md - customization/login_identifier.md - User Management: - user_management/managing_users.md From e52341550b9d9b87503738f9b96e54484c8a253d Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:15:16 +0900 Subject: [PATCH 292/401] docs: move files to references/ --- docs/{ => references}/auth_actions.md | 0 docs/{ => references}/authentication.md | 0 docs/{ => references}/authorization.md | 0 docs/{ => references}/controller_filters.md | 22 +++++++++---------- docs/{ => references}/events.md | 0 docs/{ => references}/magic_link_login.md | 2 +- .../session_auth_event_and_logging.md | 0 docs/{ => references}/testing.md | 0 mkdocs.yml | 16 +++++++------- 9 files changed, 20 insertions(+), 20 deletions(-) rename docs/{ => references}/auth_actions.md (100%) rename docs/{ => references}/authentication.md (100%) rename docs/{ => references}/authorization.md (100%) rename docs/{ => references}/controller_filters.md (87%) rename docs/{ => references}/events.md (100%) rename docs/{ => references}/magic_link_login.md (96%) rename docs/{ => references}/session_auth_event_and_logging.md (100%) rename docs/{ => references}/testing.md (100%) diff --git a/docs/auth_actions.md b/docs/references/auth_actions.md similarity index 100% rename from docs/auth_actions.md rename to docs/references/auth_actions.md diff --git a/docs/authentication.md b/docs/references/authentication.md similarity index 100% rename from docs/authentication.md rename to docs/references/authentication.md diff --git a/docs/authorization.md b/docs/references/authorization.md similarity index 100% rename from docs/authorization.md rename to docs/references/authorization.md diff --git a/docs/controller_filters.md b/docs/references/controller_filters.md similarity index 87% rename from docs/controller_filters.md rename to docs/references/controller_filters.md index f219200c1..7db3be157 100644 --- a/docs/controller_filters.md +++ b/docs/references/controller_filters.md @@ -17,17 +17,17 @@ public $aliases = [ ]; ``` -| Filters | Description | -|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| session | The `Session` authenticator. | -| tokens | The `AccessTokens` authenticator. | -| chained | The filter will check authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | -| jwt | The `JWT` authenticator. See [JWT Authentication](./addons/jwt.md). | -| hmac | The `HMAC` authenticator. See [HMAC Authentication](./guides/api_hmac_keys.md). | -| auth-rates | Provides a good basis for rate limiting of auth-related routes. | -| group | Checks if the user is in one of the groups passed in. | -| permission | Checks if the user has the passed permissions. | -| force-reset | Checks if the user requires a password reset. | +| Filters | Description | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| session | The `Session` authenticator. | +| tokens | The `AccessTokens` authenticator. | +| chained | The filter will check authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to work for both an SPA using session auth, and a mobile app using access tokens. | +| jwt | The `JWT` authenticator. See [JWT Authentication](../addons/jwt.md). | +| hmac | The `HMAC` authenticator. See [HMAC Authentication](../guides/api_hmac_keys.md). | +| auth-rates | Provides a good basis for rate limiting of auth-related routes. | +| group | Checks if the user is in one of the groups passed in. | +| permission | Checks if the user has the passed permissions. | +| force-reset | Checks if the user requires a password reset. | These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). diff --git a/docs/events.md b/docs/references/events.md similarity index 100% rename from docs/events.md rename to docs/references/events.md diff --git a/docs/magic_link_login.md b/docs/references/magic_link_login.md similarity index 96% rename from docs/magic_link_login.md rename to docs/references/magic_link_login.md index ef4b2f15a..220afffbb 100644 --- a/docs/magic_link_login.md +++ b/docs/references/magic_link_login.md @@ -26,7 +26,7 @@ public int $magicLinkLifetime = HOUR; ## Responding to Magic Link Logins > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](./install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. diff --git a/docs/session_auth_event_and_logging.md b/docs/references/session_auth_event_and_logging.md similarity index 100% rename from docs/session_auth_event_and_logging.md rename to docs/references/session_auth_event_and_logging.md diff --git a/docs/testing.md b/docs/references/testing.md similarity index 100% rename from docs/testing.md rename to docs/references/testing.md diff --git a/mkdocs.yml b/mkdocs.yml index f568a7753..a830ccac7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,13 +65,13 @@ nav: - guides/mobile_apps.md - guides/strengthen_password.md - References: - - controller_filters.md - - Authentication: authentication.md - - auth_actions.md - - magic_link_login.md - - Authorization: authorization.md - - Events: events.md - - Testing: testing.md - - session_auth_event_and_logging.md + - references/controller_filters.md + - references/authentication.md + - references/auth_actions.md + - references/magic_link_login.md + - references/authorization.md + - references/events.md + - references/testing.md + - references/session_auth_event_and_logging.md - Addons: - JWT Authentication: addons/jwt.md From 780c57b5a92799088aff2ee399569b52847ec1cc Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:22:31 +0900 Subject: [PATCH 293/401] docs: update links --- docs/customization/user_provider.md | 2 +- docs/{ => getting_started}/authenticators.md | 8 ++++---- docs/{ => getting_started}/concepts.md | 2 +- docs/{ => getting_started}/configuration.md | 0 docs/{ => getting_started}/install.md | 4 ++-- docs/quick_start_guide/using_session_auth.md | 4 ++-- docs/references/magic_link_login.md | 2 +- docs/user_management/managing_users.md | 2 +- mkdocs.yml | 8 ++++---- 9 files changed, 16 insertions(+), 16 deletions(-) rename docs/{ => getting_started}/authenticators.md (60%) rename docs/{ => getting_started}/concepts.md (98%) rename docs/{ => getting_started}/configuration.md (100%) rename docs/{ => getting_started}/install.md (97%) diff --git a/docs/customization/user_provider.md b/docs/customization/user_provider.md index 36af8d838..12246043c 100644 --- a/docs/customization/user_provider.md +++ b/docs/customization/user_provider.md @@ -1,7 +1,7 @@ # Customizing User Provider If you want to customize user attributes, you need to create your own -[User Provider](../concepts.md#user-providers) class. +[User Provider](../getting_started/concepts.md#user-providers) class. The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`. Shield has a CLI command to quickly create a custom `UserModel` class by running the following diff --git a/docs/authenticators.md b/docs/getting_started/authenticators.md similarity index 60% rename from docs/authenticators.md rename to docs/getting_started/authenticators.md index c00a4b6ec..1ff3c9d89 100644 --- a/docs/authenticators.md +++ b/docs/getting_started/authenticators.md @@ -5,11 +5,11 @@ Shield provides the following Authenticators: - **Session** authenticator provides traditional Email/Password authentication. - See [Using Session Authenticator](./quick_start_guide/using_session_auth.md) + See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) for usage. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. - See [Protecting an API with Access Tokens](./guides/api_tokens.md) for usage. + See [Protecting an API with Access Tokens](../guides/api_tokens.md) for usage. - **HmacSha256** authenticator provides stateless authentication using HMAC Keys. - See [guides/api_hmac_keys](./guides/api_hmac_keys.md) for usage. + See [guides/api_hmac_keys](../guides/api_hmac_keys.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, - you need additional setup. See [JWT Authentication](./addons/jwt.md). + you need additional setup. See [JWT Authentication](../addons/jwt.md). diff --git a/docs/concepts.md b/docs/getting_started/concepts.md similarity index 98% rename from docs/concepts.md rename to docs/getting_started/concepts.md index c5f6c3f65..58d4641c9 100644 --- a/docs/concepts.md +++ b/docs/getting_started/concepts.md @@ -21,7 +21,7 @@ on the standard Config class if nothing is found in the database. Shield has a model to handle user persistence. Shield calls this the "User Provider" class. A default model is provided for you by the `CodeIgniter\Shield\Models\UserModel` class. -You can use your own model to customize user attributes. See [Customizing User Provider](./customization/user_provider.md) for details. +You can use your own model to customize user attributes. See [Customizing User Provider](../customization/user_provider.md) for details. ## User Identities diff --git a/docs/configuration.md b/docs/getting_started/configuration.md similarity index 100% rename from docs/configuration.md rename to docs/getting_started/configuration.md diff --git a/docs/install.md b/docs/getting_started/install.md similarity index 97% rename from docs/install.md rename to docs/getting_started/install.md index 26c17c1a1..8363ca460 100644 --- a/docs/install.md +++ b/docs/getting_started/install.md @@ -64,7 +64,7 @@ Require it with an explicit version constraint allowing its desired stability. > **Note** > If you want to customize table names, you must change the table names > before running database migrations. - > See [Customizing Table Names](./customization/table_names.md). + > See [Customizing Table Names](../customization/table_names.md). 2. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). @@ -142,7 +142,7 @@ your project. > **Note** > If you want to customize table names, you must change the table names > before running database migrations. - > See [Customizing Table Names](./customization/table_names.md). + > See [Customizing Table Names](../customization/table_names.md). ```console php spark migrate --all diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index 6c7869b62..3dfdf39ef 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -43,7 +43,7 @@ public array $sessionConfig = [ ### Enable Account Activation via Email > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the `Auth` config file. @@ -57,7 +57,7 @@ public array $actions = [ ### Enable Two-Factor Authentication > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). Turned off by default, Shield's Email-based 2FA can be enabled by specifying the class to use in the `Auth` config file. diff --git a/docs/references/magic_link_login.md b/docs/references/magic_link_login.md index 220afffbb..361c7aa38 100644 --- a/docs/references/magic_link_login.md +++ b/docs/references/magic_link_login.md @@ -26,7 +26,7 @@ public int $magicLinkLifetime = HOUR; ## Responding to Magic Link Logins > **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../install.md#initial-setup). +> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. diff --git a/docs/user_management/managing_users.md b/docs/user_management/managing_users.md index 3f4a2b3c0..9d73385d3 100644 --- a/docs/user_management/managing_users.md +++ b/docs/user_management/managing_users.md @@ -1,6 +1,6 @@ # Managing Users -Since Shield uses a more complex user setup than many other systems, separating [User Identities](../concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. +Since Shield uses a more complex user setup than many other systems, separating [User Identities](../getting_started/concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. ## Creating Users diff --git a/mkdocs.yml b/mkdocs.yml index a830ccac7..468d7e210 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,10 +39,10 @@ extra_javascript: nav: - Home: index.md - Getting Started: - - Concepts: concepts.md - - Installation: install.md - - authenticators.md - - configuration.md + - Concepts: getting_started/concepts.md + - getting_started/install.md + - getting_started/authenticators.md + - getting_started/configuration.md - Quick Start Guide: - quick_start_guide/using_session_auth.md - quick_start_guide/using_authorization.md From 564352a693f6fa24d333ed35234307fc563f21e4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:23:45 +0900 Subject: [PATCH 294/401] docs: fix link text --- docs/getting_started/authenticators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting_started/authenticators.md b/docs/getting_started/authenticators.md index 1ff3c9d89..87c2b3319 100644 --- a/docs/getting_started/authenticators.md +++ b/docs/getting_started/authenticators.md @@ -10,6 +10,6 @@ Shield provides the following Authenticators: - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. See [Protecting an API with Access Tokens](../guides/api_tokens.md) for usage. - **HmacSha256** authenticator provides stateless authentication using HMAC Keys. - See [guides/api_hmac_keys](../guides/api_hmac_keys.md) for usage. + See [Protecting an API with HMAC Keys](../guides/api_hmac_keys.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](../addons/jwt.md). From 588d17d7c0a0d0e611323405de0deb8445d45b09 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 10:28:42 +0900 Subject: [PATCH 295/401] docs: add about app/Config/AuthToken.php --- docs/getting_started/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/getting_started/configuration.md b/docs/getting_started/configuration.md index df421818d..44694bb97 100644 --- a/docs/getting_started/configuration.md +++ b/docs/getting_started/configuration.md @@ -9,6 +9,7 @@ the following configuration files: - **app/Config/Auth.php** - **app/Config/AuthGroups.php** - For Authorization +- **app/Config/AuthToken.php** - For AccessTokens and HmacSha256 Authentication - **app/Config/AuthJWT.php** - For JWT Authentication Note that you do not need to have configuration files for features you do not use. From 2e48eca57634c5ce10627205651f049f6d2f5123 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:18:42 +0900 Subject: [PATCH 296/401] docs: remove out-of-dated error description --- docs/getting_started/install.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index 8363ca460..8a23656b0 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -7,6 +7,7 @@ These instructions assume that you have already [installed the CodeIgniter 4 app - [Composer](https://getcomposer.org) - Codeigniter **v4.2.7** or later - A created database that you can access via the Spark CLI script + - InnoDB (not MyISAM) is required if MySQL is used. ## Composer Installation @@ -155,10 +156,6 @@ your project. 1. Remove sample migration files in **tests/_support/Database/Migrations/** 2. Or install `sqlite3` php extension - If you get `Specified key was too long` error: - - 1. Use InnoDB, not MyISAM. - 6. Configure **app/Config/Email.php** to allow Shield to send emails. ```php From 2316f763d53a73949b5dde2858e0cd7144bce6ba Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:21:20 +0900 Subject: [PATCH 297/401] docs: replace "remember me" with remember-me For consistency. --- docs/index.md | 2 +- docs/references/authentication.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0fcb10842..8c9b4f1c5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,7 +16,7 @@ The primary goals for Shield are: ### Important Features -* Session-based authentication (traditional Email/Password with Remember me) +* Session-based authentication (traditional Email/Password with Remember-me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration * Optional Email-based Two-Factor Authentication after login diff --git a/docs/references/authentication.md b/docs/references/authentication.md index e87c5d946..b827363eb 100644 --- a/docs/references/authentication.md +++ b/docs/references/authentication.md @@ -73,7 +73,7 @@ Can return a custom bit of information. These will be detailed in the method des The Session authenticator stores the user's authentication within the user's session, and on a secure cookie on their device. This is the standard password-based login used in most web sites. It supports a -secure remember me feature, and more. This can also be used to handle authentication for +secure remember-me feature, and more. This can also be used to handle authentication for single page applications (SPAs). ### attempt() From ab65804b2b61ec1cb075defe00ebe27d16d38978 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:24:32 +0900 Subject: [PATCH 298/401] docs: replace Email/Password with ID/Password --- docs/getting_started/authenticators.md | 2 +- docs/index.md | 2 +- docs/quick_start_guide/using_session_auth.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting_started/authenticators.md b/docs/getting_started/authenticators.md index 87c2b3319..bed96abba 100644 --- a/docs/getting_started/authenticators.md +++ b/docs/getting_started/authenticators.md @@ -4,7 +4,7 @@ Shield provides the following Authenticators: -- **Session** authenticator provides traditional Email/Password authentication. +- **Session** authenticator provides traditional ID/Password authentication. See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) for usage. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. diff --git a/docs/index.md b/docs/index.md index 8c9b4f1c5..d8faf8fde 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,7 +16,7 @@ The primary goals for Shield are: ### Important Features -* Session-based authentication (traditional Email/Password with Remember-me) +* Session-based authentication (traditional ID/Password with Remember-me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration * Optional Email-based Two-Factor Authentication after login diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index 3dfdf39ef..76086a855 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -1,6 +1,6 @@ # Using Session Authenticator -**Session** authenticator provides traditional Email/Password authentication. +**Session** authenticator provides traditional ID/Password authentication. Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. From 4f7d5927cb7088dc2663f2035c2b2a0b5036aadf Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:27:36 +0900 Subject: [PATCH 299/401] docs: add section title --- docs/references/controller_filters.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/references/controller_filters.md b/docs/references/controller_filters.md index 7db3be157..51a761f0d 100644 --- a/docs/references/controller_filters.md +++ b/docs/references/controller_filters.md @@ -1,5 +1,7 @@ # Controller Filters +## Provided Filters + The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: ```php From cd757a292972dd90f848ebaecb7bd317a409d25a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:27:50 +0900 Subject: [PATCH 300/401] docs: move note up To stand it out. --- docs/references/controller_filters.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/references/controller_filters.md b/docs/references/controller_filters.md index 51a761f0d..8c6f0372d 100644 --- a/docs/references/controller_filters.md +++ b/docs/references/controller_filters.md @@ -2,6 +2,9 @@ ## Provided Filters +> **Note** +> These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. + The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: ```php @@ -33,9 +36,6 @@ public $aliases = [ These can be used in any of the [normal filter config settings](https://codeigniter.com/user_guide/incoming/filters.html#globals), or [within the routes file](https://codeigniter.com/user_guide/incoming/routing.html#applying-filters). -> **Note** -> These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. - ## Configure Controller Filters ### Protect All Pages From 10d5f4d0189d20bb084cc0936eedbd658726249c Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:29:49 +0900 Subject: [PATCH 301/401] docs: add link to CI4 user guide --- docs/references/controller_filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references/controller_filters.md b/docs/references/controller_filters.md index 8c6f0372d..e10adc888 100644 --- a/docs/references/controller_filters.md +++ b/docs/references/controller_filters.md @@ -3,7 +3,7 @@ ## Provided Filters > **Note** -> These filters are already loaded for you by the registrar class located at **src/Config/Registrar.php**. +> These filters are already loaded for you by the [registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) class located at **src/Config/Registrar.php**. The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: From f40a5eee0003e7f367a05e86a568aecbfb49f5a4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:37:09 +0900 Subject: [PATCH 302/401] docs: make page title shorter --- docs/customization/route_config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customization/route_config.md b/docs/customization/route_config.md index 729383280..01f59c412 100644 --- a/docs/customization/route_config.md +++ b/docs/customization/route_config.md @@ -1,4 +1,4 @@ -# Customizing Route Configuration +# Customizing Routes If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: From 52d24fdd09412559466ac0c56d694f11ac546940 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:40:25 +0900 Subject: [PATCH 303/401] docs: add Acknowledgements --- docs/index.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/index.md b/docs/index.md index d8faf8fde..ee28fc5a8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,3 +27,22 @@ The primary goals for Shield are: ### License Shield is licensed under the MIT License - see the [LICENSE](https://github.com/codeigniter4/shield/blob/develop/LICENSE) file for details. + +### Acknowledgements + +Every open-source project depends on it's contributors to be a success. The following users have +contributed in one manner or another in making Shield: + + + Contributors + + +Made with [contrib.rocks](https://contrib.rocks). + +The following articles/sites have been fundamental in shaping the security and best practices used +within this library, in no particular order: + +- [Google Cloud: 13 best practices for user account, authentication, and password management, 2021 edition](https://cloud.google.com/blog/products/identity-security/account-authentication-and-password-management-best-practices) +- [NIST Digital Identity Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html) +- [Implementing Secure User Authentication in PHP Applications with Long-Term Persistence (Login with "Remember Me" Cookies) ](https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence) +- [Password Storage - OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) From d8c89bc03d1ff43eb9bf81ddfe857bf57ae300f8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 05:52:19 +0900 Subject: [PATCH 304/401] docs: update and move description for Authenticators --- docs/getting_started/authenticators.md | 7 ++++--- docs/references/authentication.md | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/getting_started/authenticators.md b/docs/getting_started/authenticators.md index bed96abba..0714ea226 100644 --- a/docs/getting_started/authenticators.md +++ b/docs/getting_started/authenticators.md @@ -5,9 +5,10 @@ Shield provides the following Authenticators: - **Session** authenticator provides traditional ID/Password authentication. - See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) - for usage. -- **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens. + It uses username/email/password to authenticate against and stores the user + information in the session. See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) for usage. +- **AccessTokens** authenticator provides stateless authentication using Personal + Access Tokens passed in the HTTP headers. See [Protecting an API with Access Tokens](../guides/api_tokens.md) for usage. - **HmacSha256** authenticator provides stateless authentication using HMAC Keys. See [Protecting an API with HMAC Keys](../guides/api_hmac_keys.md) for usage. diff --git a/docs/references/authentication.md b/docs/references/authentication.md index b827363eb..c1286ad4b 100644 --- a/docs/references/authentication.md +++ b/docs/references/authentication.md @@ -6,9 +6,8 @@ web apps and APIs. ## Available Authenticators -Shield ships with 2 authenticators that will serve several typical situations within web app development: the -Session authenticator, which uses username/email/password to authenticate against and stores it in the session, -and the Access Tokens authenticator which uses private access tokens passed in the headers. +Shield ships with 4 authenticators that will serve several typical situations within web app development. +You can see the [Authenticator List](../getting_started/authenticators.md). The available authenticators are defined in `Config\Auth`: @@ -18,6 +17,7 @@ public $authenticators = [ 'session' => Session::class, 'tokens' => AccessTokens::class, 'hmac' => HmacSha256::class, + // 'jwt' => JWT::class, ]; ``` From d7449c43d4ee4ab519b073a1f7915335f7b0455d Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 7 Sep 2023 09:53:31 +0900 Subject: [PATCH 305/401] chore: use codeigniter4/.github/.github/workflows/phpcpd.yml --- .github/workflows/phpcpd.yml | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/.github/workflows/phpcpd.yml b/.github/workflows/phpcpd.yml index 2ffa76d70..2704b80fc 100644 --- a/.github/workflows/phpcpd.yml +++ b/.github/workflows/phpcpd.yml @@ -15,22 +15,8 @@ on: - '.github/workflows/phpcpd.yml' jobs: - build: - name: Code Copy-Paste Detection - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phpcpd - extensions: dom, mbstring - coverage: none - - - name: Detect duplicate code - run: phpcpd src/ tests/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php --exclude tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php + phpcpd: + uses: codeigniter4/.github/.github/workflows/phpcpd.yml@main + with: + dirs: "src/ tests/" + options: "--exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php --exclude tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php" From f55f63c1266bf9b5c22dad270a5842d7146df785 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 20 Sep 2023 14:31:16 +0800 Subject: [PATCH 306/401] chore: enable phpstan's bleedingEdge.neon --- phpstan.neon.dist | 1 + tests/Authentication/HasHmacTokensTest.php | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f415fc148..eabfd9204 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,6 @@ includes: - phpstan-baseline.php + - phar://vendor/phpstan/phpstan/phpstan.phar/conf/bleedingEdge.neon parameters: tmpDir: build/phpstan diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 35f0b7153..12ebcd284 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -45,19 +45,16 @@ public function testHmacTokens(): void $this->assertSame([], $this->user->accessTokens()); // Give the user a couple of access tokens - /** @var AccessToken $token1 */ $token1 = fake( UserIdentityModel::class, ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key1', 'secret2' => 'secretKey1'] ); - /** @var AccessToken $token2 */ $token2 = fake( UserIdentityModel::class, ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key2', 'secret2' => 'secretKey2'] ); - /** @var AccessToken[] $tokens */ $tokens = $this->user->hmacTokens(); $this->assertCount(2, $tokens); From c6112fab7754576d1bf97bd733727d417921b5b4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 06:51:58 +0900 Subject: [PATCH 307/401] docs: add references/authentication/ and move files --- docs/references/{ => authentication}/auth_actions.md | 0 docs/references/{ => authentication}/authentication.md | 2 +- docs/references/authorization.md | 2 +- mkdocs.yml | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename docs/references/{ => authentication}/auth_actions.md (100%) rename docs/references/{ => authentication}/authentication.md (99%) diff --git a/docs/references/auth_actions.md b/docs/references/authentication/auth_actions.md similarity index 100% rename from docs/references/auth_actions.md rename to docs/references/authentication/auth_actions.md diff --git a/docs/references/authentication.md b/docs/references/authentication/authentication.md similarity index 99% rename from docs/references/authentication.md rename to docs/references/authentication/authentication.md index c1286ad4b..062f99e52 100644 --- a/docs/references/authentication.md +++ b/docs/references/authentication/authentication.md @@ -7,7 +7,7 @@ web apps and APIs. ## Available Authenticators Shield ships with 4 authenticators that will serve several typical situations within web app development. -You can see the [Authenticator List](../getting_started/authenticators.md). +You can see the [Authenticator List](../../getting_started/authenticators.md). The available authenticators are defined in `Config\Auth`: diff --git a/docs/references/authorization.md b/docs/references/authorization.md index 67d5af0df..74b56ba12 100644 --- a/docs/references/authorization.md +++ b/docs/references/authorization.md @@ -238,7 +238,7 @@ $user->getGroups(); ## User Activation -All users have an `active` flag. This is only used when the [`EmailActivation` action](./auth_actions.md), or a custom action used to activate a user, is enabled. +All users have an `active` flag. This is only used when the [`EmailActivation` action](./authentication/auth_actions.md), or a custom action used to activate a user, is enabled. ### Checking Activation Status diff --git a/mkdocs.yml b/mkdocs.yml index 468d7e210..545980b5f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,8 +66,8 @@ nav: - guides/strengthen_password.md - References: - references/controller_filters.md - - references/authentication.md - - references/auth_actions.md + - references/authentication/authentication.md + - references/authentication/auth_actions.md - references/magic_link_login.md - references/authorization.md - references/events.md From 49145387ba5cb9b20e1d754e285f10bd8c0952b5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 07:04:20 +0900 Subject: [PATCH 308/401] docs: split authentication.md --- .../authentication/authentication.md | 374 ------------------ docs/references/authentication/hmac.md | 157 ++++++++ docs/references/authentication/session.md | 90 +++++ docs/references/authentication/tokens.md | 121 ++++++ mkdocs.yml | 8 +- 5 files changed, 374 insertions(+), 376 deletions(-) create mode 100644 docs/references/authentication/hmac.md create mode 100644 docs/references/authentication/session.md create mode 100644 docs/references/authentication/tokens.md diff --git a/docs/references/authentication/authentication.md b/docs/references/authentication/authentication.md index 062f99e52..69174d75c 100644 --- a/docs/references/authentication/authentication.md +++ b/docs/references/authentication/authentication.md @@ -67,377 +67,3 @@ Returns a message that can be displayed to the user when the check fails. ### extraInfo() Can return a custom bit of information. These will be detailed in the method descriptions below. - - -## Session Authenticator - -The Session authenticator stores the user's authentication within the user's session, and on a secure cookie -on their device. This is the standard password-based login used in most web sites. It supports a -secure remember-me feature, and more. This can also be used to handle authentication for -single page applications (SPAs). - -### attempt() - -When a user attempts to login with their email and password, you would call the `attempt()` method -on the auth class, passing in their credentials. - -```php -$credentials = [ - 'email' => $this->request->getPost('email'), - 'password' => $this->request->getPost('password') -]; - -$loginAttempt = auth()->attempt($credentials); - -if (! $loginAttempt->isOK()) { - return redirect()->back()->with('error', $loginAttempt->reason()); -} -``` - -Upon a successful `attempt()`, the user is logged in. The Response object returned will provide -the user that was logged in as `extraInfo()`. - -```php -$result = auth()->attempt($credentials); - -if ($result->isOK()) { - $user = $result->extraInfo(); -} -``` - -If the attempt fails a `failedLogin` event is triggered with the credentials array as -the only parameter. Whether or not they pass, a login attempt is recorded in the `auth_logins` table. - -If `allowRemembering` is `true` in the `Auth` config file, you can tell the Session authenticator -to set a secure remember-me cookie. - -```php -$loginAttempt = auth()->remember()->attempt($credentials); -``` - -### check() - -If you would like to check a user's credentials without logging them in, you can use the `check()` -method. - -```php -$credentials = [ - 'email' => $this->request->getPost('email'), - 'password' => $this->request->getPost('password') -]; - -$validCreds = auth()->check($credentials); - -if (! $validCreds->isOK()) { - return redirect()->back()->with('error', $validCreds->reason()); -} -``` - -The Result instance returned contains the valid user as `extraInfo()`. - -### loggedIn() - -You can determine if a user is currently logged in with the aptly titled method, `loggedIn()`. - -```php -if (auth()->loggedIn()) { - // Do something. -} -``` - -### logout() - -You can call the `logout()` method to log the user out of the current session. This will destroy and -regenerate the current session, purge any remember-me tokens current for this user, and trigger a -`logout` event. - -```php -auth()->logout(); -``` - -### forget() - -The `forget` method will purge all remember-me tokens for the current user, making it so they -will not be remembered on the next visit to the site. - - - -## Access Token Authenticator - -The Access Token authenticator supports the use of revoke-able API tokens without using OAuth. These are commonly -used to provide third-party developers access to your API. These tokens typically have a very long -expiration time, often years. - -These are also suitable for use with mobile applications. In this case, the user would register/sign-in -with their email/password. The application would create a new access token for them, with a recognizable -name, like John's iPhone 12, and return it to the mobile application, where it is stored and used -in all future requests. - -### Access Token/API Authentication - -Using access tokens requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or -use the `CodeIgniter\Shield\Authentication\Traits\HasAccessTokens` on your own user model. This trait -provides all of the custom methods needed to implement access tokens in your application. The necessary -database table, `auth_identities`, is created in Shield's only migration class, which must be run -before first using any of the features of Shield. - -### Generating Access Tokens - -Access tokens are created through the `generateAccessToken()` method on the user. This takes a name to -give to the token as the first argument. The name is used to display it to the user so they can -differentiate between multiple tokens. - -```php -$token = $user->generateAccessToken('Work Laptop'); -``` - -This creates the token using a cryptographically secure random string. The token -is hashed (sha256) before saving it to the database. The method returns an instance of -`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The only time a plain text -version of the token is available is in the `AccessToken` returned immediately after creation. - -**The plain text version should be displayed to the user immediately so they can copy it for -their use.** If a user loses it, they cannot see the raw version anymore, but they can generate -a new token to use. - -```php -$token = $user->generateAccessToken('Work Laptop'); - -// Only available immediately after creation. -echo $token->raw_token; -``` - -### Revoking Access Tokens - -Access tokens can be revoked through the `revokeAccessToken()` method. This takes the plain-text -access token as the only argument. Revoking simply deletes the record from the database. - -```php -$user->revokeAccessToken($token); -``` - -Typically, the plain text token is retrieved from the request's headers as part of the authentication -process. If you need to revoke the token for another user as an admin, and don't have access to the -token, you would need to get the user's access tokens and delete them manually. - -You can revoke all access tokens with the `revokeAllAccessTokens()` method. - -```php -$user->revokeAllAccessTokens(); -``` - -### Retrieving Access Tokens - -The following methods are available to help you retrieve a user's access tokens: - -```php -// Retrieve a single token by plain text token -$token = $user->getAccessToken($rawToken); - -// Retrieve a single token by it's database ID -$token = $user->getAccessTokenById($id); - -// Retrieve all access tokens as an array of AccessToken instances. -$tokens = $user->accessTokens(); -``` - -### Access Token Lifetime - -Tokens will expire after a specified amount of time has passed since they have been used. -By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the `Auth` config file. This is in seconds so that you can use the -[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -that CodeIgniter provides. - -```php -public $unusedTokenLifetime = YEAR; -``` - -### Access Token Scopes - -Each token can be given one or more scopes they can be used within. These can be thought of as -permissions the token grants to the user. Scopes are provided when the token is generated and -cannot be modified afterword. - -```php -$token = $user->gererateAccessToken('Work Laptop', ['posts.manage', 'forums.manage']); -``` - -By default a user is granted a wildcard scope which provides access to all scopes. This is the -same as: - -```php -$token = $user->gererateAccessToken('Work Laptop', ['*']); -``` - -During authentication, the token the user used is stored on the user. Once authenticated, you -can use the `tokenCan()` and `tokenCant()` methods on the user to determine if they have access -to the specified scope. - -```php -if ($user->tokenCan('posts.manage')) { - // do something.... -} - -if ($user->tokenCant('forums.manage')) { - // do something.... -} -``` - -## HMAC SHA256 Token Authenticator - -The HMAC-SHA256 authenticator supports the use of revocable API keys without using OAuth. This provides -an alternative to a token that is passed in every request and instead uses a shared secret that is used to sign -the request in a secure manner. Like authorization tokens, these are commonly used to provide third-party developers -access to your API. These keys typically have a very long expiration time, often years. - -These are also suitable for use with mobile applications. In this case, the user would register/sign-in -with their email/password. The application would create a new access token for them, with a recognizable -name, like John's iPhone 12, and return it to the mobile application, where it is stored and used -in all future requests. - -> **Note** -> For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, -> the term "Token" will be used to represent a set of API Keys (key and secretKey). - -### Usage - -In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: - -``` -Authorization: HMAC-SHA256 : -``` - -The code to do this will look something like this: - -```php -header("Authorization: HMAC-SHA256 {$key}:" . hash_hmac('sha256', $requestBody, $secretKey)); -``` - -Using the CodeIgniter CURLRequest class: - -```php -setHeader('Authorization', "HMAC-SHA256 {$key}:{$hashValue}") - ->setBody($requestBody) - ->request('POST', 'https://example.com/api'); -``` - -### HMAC Keys/API Authentication - -Using HMAC keys requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or -use the `CodeIgniter\Shield\Authentication\Traits\HasHmacTokens` on your own user model. This trait -provides all the custom methods needed to implement HMAC keys in your application. The necessary -database table, `auth_identities`, is created in Shield's only migration class, which must be run -before first using any of the features of Shield. - -### Generating HMAC Access Keys - -Access keys/tokens are created through the `generateHmacToken()` method on the user. This takes a name to -give to the token as the first argument. The name is used to display it to the user, so they can -differentiate between multiple tokens. - -```php -$token = $user->generateHmacToken('Work Laptop'); -``` - -This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys. -This means they are stored as-is in the database. The method returns an instance of -`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is -the shared 'secretKey'. Both are required to when using this authentication method. - -**The plain text version of these keys should be displayed to the user immediately, so they can copy it for -their use.** It is recommended that after that only the 'key' field is displayed to a user. If a user loses the -'secretKey', they should be required to generate a new set of keys to use. - -```php -$token = $user->generateHmacToken('Work Laptop'); - -echo 'Key: ' . $token->secret; -echo 'SecretKey: ' . $token->secret2; -``` - -### Revoking HMAC Keys - -HMAC keys can be revoked through the `revokeHmacToken()` method. This takes the key as the only -argument. Revoking simply deletes the record from the database. - -```php -$user->revokeHmacToken($key); -``` - -You can revoke all HMAC Keys with the `revokeAllHmacTokens()` method. - -```php -$user->revokeAllHmacTokens(); -``` - -### Retrieving HMAC Keys - -The following methods are available to help you retrieve a user's HMAC keys: - -```php -// Retrieve a set of HMAC Token/Keys by key -$token = $user->getHmacToken($key); - -// Retrieve an HMAC token/keys by its database ID -$token = $user->getHmacTokenById($id); - -// Retrieve all HMAC tokens as an array of AccessToken instances. -$tokens = $user->hmacTokens(); -``` - -### HMAC Keys Lifetime - -HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. -This uses the same configuration value as AccessTokens. - -By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the `Auth` config file. This is in seconds so that you can use the -[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -that CodeIgniter provides. - -```php -public $unusedTokenLifetime = YEAR; -``` - -### HMAC Keys Scopes - -Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as -permissions the token grants to the user. Scopes are provided when the token is generated and -cannot be modified afterword. - -```php -$token = $user->gererateHmacToken('Work Laptop', ['posts.manage', 'forums.manage']); -``` - -By default, a user is granted a wildcard scope which provides access to all scopes. This is the -same as: - -```php -$token = $user->gererateHmacToken('Work Laptop', ['*']); -``` - -During authentication, the HMAC Keys the user used is stored on the user. Once authenticated, you -can use the `hmacTokenCan()` and `hmacTokenCant()` methods on the user to determine if they have access -to the specified scope. - -```php -if ($user->hmacTokenCan('posts.manage')) { - // do something.... -} - -if ($user->hmacTokenCant('forums.manage')) { - // do something.... -} -``` diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md new file mode 100644 index 000000000..f5d799ac0 --- /dev/null +++ b/docs/references/authentication/hmac.md @@ -0,0 +1,157 @@ +# HMAC SHA256 Token Authenticator + +The HMAC-SHA256 authenticator supports the use of revocable API keys without using OAuth. This provides +an alternative to a token that is passed in every request and instead uses a shared secret that is used to sign +the request in a secure manner. Like authorization tokens, these are commonly used to provide third-party developers +access to your API. These keys typically have a very long expiration time, often years. + +These are also suitable for use with mobile applications. In this case, the user would register/sign-in +with their email/password. The application would create a new access token for them, with a recognizable +name, like John's iPhone 12, and return it to the mobile application, where it is stored and used +in all future requests. + +> **Note** +> For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, +> the term "Token" will be used to represent a set of API Keys (key and secretKey). + +## Usage + +In order to use HMAC Keys/Token the `Authorization` header will be set to the following in the request: + +``` +Authorization: HMAC-SHA256 : +``` + +The code to do this will look something like this: + +```php +header("Authorization: HMAC-SHA256 {$key}:" . hash_hmac('sha256', $requestBody, $secretKey)); +``` + +Using the CodeIgniter CURLRequest class: + +```php +setHeader('Authorization', "HMAC-SHA256 {$key}:{$hashValue}") + ->setBody($requestBody) + ->request('POST', 'https://example.com/api'); +``` + +## HMAC Keys/API Authentication + +Using HMAC keys requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or +use the `CodeIgniter\Shield\Authentication\Traits\HasHmacTokens` on your own user model. This trait +provides all the custom methods needed to implement HMAC keys in your application. The necessary +database table, `auth_identities`, is created in Shield's only migration class, which must be run +before first using any of the features of Shield. + +## Generating HMAC Access Keys + +Access keys/tokens are created through the `generateHmacToken()` method on the user. This takes a name to +give to the token as the first argument. The name is used to display it to the user, so they can +differentiate between multiple tokens. + +```php +$token = $user->generateHmacToken('Work Laptop'); +``` + +This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys. +This means they are stored as-is in the database. The method returns an instance of +`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is +the shared 'secretKey'. Both are required to when using this authentication method. + +**The plain text version of these keys should be displayed to the user immediately, so they can copy it for +their use.** It is recommended that after that only the 'key' field is displayed to a user. If a user loses the +'secretKey', they should be required to generate a new set of keys to use. + +```php +$token = $user->generateHmacToken('Work Laptop'); + +echo 'Key: ' . $token->secret; +echo 'SecretKey: ' . $token->secret2; +``` + +## Revoking HMAC Keys + +HMAC keys can be revoked through the `revokeHmacToken()` method. This takes the key as the only +argument. Revoking simply deletes the record from the database. + +```php +$user->revokeHmacToken($key); +``` + +You can revoke all HMAC Keys with the `revokeAllHmacTokens()` method. + +```php +$user->revokeAllHmacTokens(); +``` + +## Retrieving HMAC Keys + +The following methods are available to help you retrieve a user's HMAC keys: + +```php +// Retrieve a set of HMAC Token/Keys by key +$token = $user->getHmacToken($key); + +// Retrieve an HMAC token/keys by its database ID +$token = $user->getHmacTokenById($id); + +// Retrieve all HMAC tokens as an array of AccessToken instances. +$tokens = $user->hmacTokens(); +``` + +## HMAC Keys Lifetime + +HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. +This uses the same configuration value as AccessTokens. + +By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` +value in the `Auth` config file. This is in seconds so that you can use the +[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) +that CodeIgniter provides. + +```php +public $unusedTokenLifetime = YEAR; +``` + +## HMAC Keys Scopes + +Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as +permissions the token grants to the user. Scopes are provided when the token is generated and +cannot be modified afterword. + +```php +$token = $user->gererateHmacToken('Work Laptop', ['posts.manage', 'forums.manage']); +``` + +By default, a user is granted a wildcard scope which provides access to all scopes. This is the +same as: + +```php +$token = $user->gererateHmacToken('Work Laptop', ['*']); +``` + +During authentication, the HMAC Keys the user used is stored on the user. Once authenticated, you +can use the `hmacTokenCan()` and `hmacTokenCant()` methods on the user to determine if they have access +to the specified scope. + +```php +if ($user->hmacTokenCan('posts.manage')) { + // do something.... +} + +if ($user->hmacTokenCant('forums.manage')) { + // do something.... +} +``` diff --git a/docs/references/authentication/session.md b/docs/references/authentication/session.md new file mode 100644 index 000000000..015636967 --- /dev/null +++ b/docs/references/authentication/session.md @@ -0,0 +1,90 @@ +# Session Authenticator + +The Session authenticator stores the user's authentication within the user's session, and on a secure cookie +on their device. This is the standard password-based login used in most web sites. It supports a +secure remember-me feature, and more. This can also be used to handle authentication for +single page applications (SPAs). + +## attempt() + +When a user attempts to login with their email and password, you would call the `attempt()` method +on the auth class, passing in their credentials. + +```php +$credentials = [ + 'email' => $this->request->getPost('email'), + 'password' => $this->request->getPost('password') +]; + +$loginAttempt = auth()->attempt($credentials); + +if (! $loginAttempt->isOK()) { + return redirect()->back()->with('error', $loginAttempt->reason()); +} +``` + +Upon a successful `attempt()`, the user is logged in. The Response object returned will provide +the user that was logged in as `extraInfo()`. + +```php +$result = auth()->attempt($credentials); + +if ($result->isOK()) { + $user = $result->extraInfo(); +} +``` + +If the attempt fails a `failedLogin` event is triggered with the credentials array as +the only parameter. Whether or not they pass, a login attempt is recorded in the `auth_logins` table. + +If `allowRemembering` is `true` in the `Auth` config file, you can tell the Session authenticator +to set a secure remember-me cookie. + +```php +$loginAttempt = auth()->remember()->attempt($credentials); +``` + +## check() + +If you would like to check a user's credentials without logging them in, you can use the `check()` +method. + +```php +$credentials = [ + 'email' => $this->request->getPost('email'), + 'password' => $this->request->getPost('password') +]; + +$validCreds = auth()->check($credentials); + +if (! $validCreds->isOK()) { + return redirect()->back()->with('error', $validCreds->reason()); +} +``` + +The Result instance returned contains the valid user as `extraInfo()`. + +## loggedIn() + +You can determine if a user is currently logged in with the aptly titled method, `loggedIn()`. + +```php +if (auth()->loggedIn()) { + // Do something. +} +``` + +## logout() + +You can call the `logout()` method to log the user out of the current session. This will destroy and +regenerate the current session, purge any remember-me tokens current for this user, and trigger a +`logout` event. + +```php +auth()->logout(); +``` + +## forget() + +The `forget` method will purge all remember-me tokens for the current user, making it so they +will not be remembered on the next visit to the site. diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md new file mode 100644 index 000000000..fe2c2569a --- /dev/null +++ b/docs/references/authentication/tokens.md @@ -0,0 +1,121 @@ +# Access Token Authenticator + +The Access Token authenticator supports the use of revoke-able API tokens without using OAuth. These are commonly +used to provide third-party developers access to your API. These tokens typically have a very long +expiration time, often years. + +These are also suitable for use with mobile applications. In this case, the user would register/sign-in +with their email/password. The application would create a new access token for them, with a recognizable +name, like John's iPhone 12, and return it to the mobile application, where it is stored and used +in all future requests. + +## Access Token/API Authentication + +Using access tokens requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or +use the `CodeIgniter\Shield\Authentication\Traits\HasAccessTokens` on your own user model. This trait +provides all of the custom methods needed to implement access tokens in your application. The necessary +database table, `auth_identities`, is created in Shield's only migration class, which must be run +before first using any of the features of Shield. + +## Generating Access Tokens + +Access tokens are created through the `generateAccessToken()` method on the user. This takes a name to +give to the token as the first argument. The name is used to display it to the user so they can +differentiate between multiple tokens. + +```php +$token = $user->generateAccessToken('Work Laptop'); +``` + +This creates the token using a cryptographically secure random string. The token +is hashed (sha256) before saving it to the database. The method returns an instance of +`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The only time a plain text +version of the token is available is in the `AccessToken` returned immediately after creation. + +**The plain text version should be displayed to the user immediately so they can copy it for +their use.** If a user loses it, they cannot see the raw version anymore, but they can generate +a new token to use. + +```php +$token = $user->generateAccessToken('Work Laptop'); + +// Only available immediately after creation. +echo $token->raw_token; +``` + +## Revoking Access Tokens + +Access tokens can be revoked through the `revokeAccessToken()` method. This takes the plain-text +access token as the only argument. Revoking simply deletes the record from the database. + +```php +$user->revokeAccessToken($token); +``` + +Typically, the plain text token is retrieved from the request's headers as part of the authentication +process. If you need to revoke the token for another user as an admin, and don't have access to the +token, you would need to get the user's access tokens and delete them manually. + +You can revoke all access tokens with the `revokeAllAccessTokens()` method. + +```php +$user->revokeAllAccessTokens(); +``` + +## Retrieving Access Tokens + +The following methods are available to help you retrieve a user's access tokens: + +```php +// Retrieve a single token by plain text token +$token = $user->getAccessToken($rawToken); + +// Retrieve a single token by it's database ID +$token = $user->getAccessTokenById($id); + +// Retrieve all access tokens as an array of AccessToken instances. +$tokens = $user->accessTokens(); +``` + +## Access Token Lifetime + +Tokens will expire after a specified amount of time has passed since they have been used. +By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` +value in the `Auth` config file. This is in seconds so that you can use the +[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) +that CodeIgniter provides. + +```php +public $unusedTokenLifetime = YEAR; +``` + +## Access Token Scopes + +Each token can be given one or more scopes they can be used within. These can be thought of as +permissions the token grants to the user. Scopes are provided when the token is generated and +cannot be modified afterword. + +```php +$token = $user->gererateAccessToken('Work Laptop', ['posts.manage', 'forums.manage']); +``` + +By default a user is granted a wildcard scope which provides access to all scopes. This is the +same as: + +```php +$token = $user->gererateAccessToken('Work Laptop', ['*']); +``` + +During authentication, the token the user used is stored on the user. Once authenticated, you +can use the `tokenCan()` and `tokenCant()` methods on the user to determine if they have access +to the specified scope. + +```php +if ($user->tokenCan('posts.manage')) { + // do something.... +} + +if ($user->tokenCant('forums.manage')) { + // do something.... +} +``` diff --git a/mkdocs.yml b/mkdocs.yml index 545980b5f..3d37bf5f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,8 +66,12 @@ nav: - guides/strengthen_password.md - References: - references/controller_filters.md - - references/authentication/authentication.md - - references/authentication/auth_actions.md + - Authentication: + - references/authentication/authentication.md + - references/authentication/session.md + - Token Authenticator: references/authentication/tokens.md + - HMAC Authenticator: references/authentication/hmac.md + - Auth Actions: references/authentication/auth_actions.md - references/magic_link_login.md - references/authorization.md - references/events.md From 5567353caf37bf63c43c8c1141f93af23324c3e5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Sep 2023 07:08:10 +0900 Subject: [PATCH 309/401] docs: move session_auth_event_and_logging.md contents to authentication/session.md --- docs/references/authentication/session.md | 55 +++++++++++++++++-- .../session_auth_event_and_logging.md | 42 -------------- 2 files changed, 50 insertions(+), 47 deletions(-) delete mode 100644 docs/references/session_auth_event_and_logging.md diff --git a/docs/references/authentication/session.md b/docs/references/authentication/session.md index 015636967..9245ebf7e 100644 --- a/docs/references/authentication/session.md +++ b/docs/references/authentication/session.md @@ -5,7 +5,9 @@ on their device. This is the standard password-based login used in most web site secure remember-me feature, and more. This can also be used to handle authentication for single page applications (SPAs). -## attempt() +## Method References + +### attempt() When a user attempts to login with their email and password, you would call the `attempt()` method on the auth class, passing in their credentials. @@ -44,7 +46,7 @@ to set a secure remember-me cookie. $loginAttempt = auth()->remember()->attempt($credentials); ``` -## check() +### check() If you would like to check a user's credentials without logging them in, you can use the `check()` method. @@ -64,7 +66,7 @@ if (! $validCreds->isOK()) { The Result instance returned contains the valid user as `extraInfo()`. -## loggedIn() +### loggedIn() You can determine if a user is currently logged in with the aptly titled method, `loggedIn()`. @@ -74,7 +76,7 @@ if (auth()->loggedIn()) { } ``` -## logout() +### logout() You can call the `logout()` method to log the user out of the current session. This will destroy and regenerate the current session, purge any remember-me tokens current for this user, and trigger a @@ -84,7 +86,50 @@ regenerate the current session, purge any remember-me tokens current for this us auth()->logout(); ``` -## forget() +### forget() The `forget` method will purge all remember-me tokens for the current user, making it so they will not be remembered on the next visit to the site. + +## Events and Logging + +The following is a list of Events and Logging for Session Authenticator. + +### Register + +- Default Register + - Post email/username/password + - OK → event `register` and `login` + - NG → no event +- Register with Email Activation + 1. Post email/username/password + - OK → event `register` + - NG → no event + 2. Post token + - OK → event `login` + - NG → no event + +### Login + +- Default Login + - Post email/password + - OK → event `login` / table `auth_logins` + - NG → event `failedLogin` / table `auth_logins` +- Email2FA Login + 1. Post email/password + - OK → no event / table `auth_logins` + - NG → event `failedLogin` / table `auth_logins` + 2. Post token + - OK → event `login` + - NG → no event +- Remember-me + - Send remember-me cookie w/o session cookie + - OK → no event + - NG → no event +- Magic-link + 1. Post email + - OK → no event + - NG → no event + 2. Send request with token + - OK → event `login` and `magicLogin` / table `auth_logins` + - NG → event `failedLogin` / table `auth_logins` diff --git a/docs/references/session_auth_event_and_logging.md b/docs/references/session_auth_event_and_logging.md deleted file mode 100644 index a1232ccfa..000000000 --- a/docs/references/session_auth_event_and_logging.md +++ /dev/null @@ -1,42 +0,0 @@ -# Session Authenticator Event and Logging - -The following is a list of Events and Logging for Session Authenticator. - -## Register - -- Default Register - - Post email/username/password - - OK → event `register` and `login` - - NG → no event -- Register with Email Activation - 1. Post email/username/password - - OK → event `register` - - NG → no event - 2. Post token - - OK → event `login` - - NG → no event - -## Login - -- Default Login - - Post email/password - - OK → event `login` / table `auth_logins` - - NG → event `failedLogin` / table `auth_logins` -- Email2FA Login - 1. Post email/password - - OK → no event / table `auth_logins` - - NG → event `failedLogin` / table `auth_logins` - 2. Post token - - OK → event `login` - - NG → no event -- Remember-me - - Send remember-me cookie w/o session cookie - - OK → no event - - NG → no event -- Magic-link - 1. Post email - - OK → no event - - NG → no event - 2. Send request with token - - OK → event `login` and `magicLogin` / table `auth_logins` - - NG → event `failedLogin` / table `auth_logins` From 9fe3ee8f461c81b954d9749d1612803552ca81dc Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Sep 2023 17:52:38 +0900 Subject: [PATCH 310/401] docs: update link --- docs/references/events.md | 2 +- mkdocs.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/references/events.md b/docs/references/events.md index b1360de48..20344fd96 100644 --- a/docs/references/events.md +++ b/docs/references/events.md @@ -74,4 +74,4 @@ Events::on('magicLogin', function() { To learn more about Event timing, please see the list below. -- [Session Authenticator Event and Logging](./session_auth_event_and_logging.md). +- [Session Authenticator](./authentication/session.md#events-and-logging). diff --git a/mkdocs.yml b/mkdocs.yml index 3d37bf5f2..2e4fc746f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,6 +76,5 @@ nav: - references/authorization.md - references/events.md - references/testing.md - - references/session_auth_event_and_logging.md - Addons: - JWT Authentication: addons/jwt.md From 3d1b055ca32c80117b587b155ce6928167685bcf Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 07:47:52 +0900 Subject: [PATCH 311/401] chore: enable content.code.copy feature --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 2e4fc746f..ac3f91a65 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ theme: primary: deep orange features: - navigation.sections + - content.code.copy extra: homepage: https://codeigniter.com From a2dc641e6c1cdd9869b10482bff103b5b7119b40 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 07:55:22 +0900 Subject: [PATCH 312/401] docs: add links to references --- docs/getting_started/authenticators.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/getting_started/authenticators.md b/docs/getting_started/authenticators.md index 0714ea226..1e5d31987 100644 --- a/docs/getting_started/authenticators.md +++ b/docs/getting_started/authenticators.md @@ -6,11 +6,14 @@ Shield provides the following Authenticators: - **Session** authenticator provides traditional ID/Password authentication. It uses username/email/password to authenticate against and stores the user - information in the session. See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) for usage. + information in the session. See [Using Session Authenticator](../quick_start_guide/using_session_auth.md) + and [Session Authenticator](../references/authentication/session.md) for usage. - **AccessTokens** authenticator provides stateless authentication using Personal Access Tokens passed in the HTTP headers. - See [Protecting an API with Access Tokens](../guides/api_tokens.md) for usage. + See [Protecting an API with Access Tokens](../guides/api_tokens.md) and + [Access Token Authenticator](../references/authentication/tokens.md) for usage. - **HmacSha256** authenticator provides stateless authentication using HMAC Keys. - See [Protecting an API with HMAC Keys](../guides/api_hmac_keys.md) for usage. + See [Protecting an API with HMAC Keys](../guides/api_hmac_keys.md) and + [HMAC SHA256 Token Authenticator](../references/authentication/hmac.md) for usage. - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this, you need additional setup. See [JWT Authentication](../addons/jwt.md). From 77e2564dc6e20d3540326f30125f2e75a757a2fc Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 08:36:35 +0900 Subject: [PATCH 313/401] docs: add about shield:user command --- docs/user_management/managing_users.md | 31 +++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/user_management/managing_users.md b/docs/user_management/managing_users.md index 9d73385d3..57f67c32d 100644 --- a/docs/user_management/managing_users.md +++ b/docs/user_management/managing_users.md @@ -2,7 +2,9 @@ Since Shield uses a more complex user setup than many other systems, separating [User Identities](../getting_started/concepts.md#user-identities) from the user accounts themselves. This quick overview should help you feel more confident when working with users on a day-to-day basis. -## Creating Users +## Managing Users by Code + +### Creating Users By default, the only values stored in the users table is the username. The first step is to create the user record with the username. If you don't have a username, be sure to set the value to `null` anyway, so that it passes CodeIgniter's empty data check. @@ -26,7 +28,7 @@ $user = $users->findById($users->getInsertID()); $users->addToDefaultGroup($user); ``` -## Deleting Users +### Deleting Users A user's data can be spread over a few different tables so you might be concerned about how to delete all of the user's data from the system. This is handled automatically at the database level for all information that Shield knows about, through the `onCascade` settings of the table's foreign keys. You can delete a user like any other entity. @@ -40,7 +42,7 @@ $users->delete($user->id, true); > **Note** > The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. -## Editing a User +### Editing a User The `UserModel::save()`, `update()` and `insert()` methods have been modified to ensure that an email or password previously set on the `User` entity will be automatically updated in the correct `UserIdentity` record. @@ -56,3 +58,26 @@ $user->fill([ ]); $users->save($user); ``` + +## Managing Users via CLI + +Shield has a CLI command to manage users. You can do the following actions: + +```text +create: Create a new user +activate: Activate a user +deactivate: Deactivate a user +changename: Change user name +changeemail: Change user email +delete: Delete a user +password: Change a user password +list: List users +addgroup: Add a user to a group +removegroup: Remove a user from a group +``` + +You can get help on how to use it by running the following command in a terminal: + +```console +php spark shield:user --help +``` From 36a0621a2e69c51aa15cf123e65b24a6c724e21e Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 10:45:06 +0900 Subject: [PATCH 314/401] docs: update README.md --- README.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 676c7bb2c..bf090af67 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ [![Architecture](https://github.com/codeigniter4/shield/workflows/Deptrac/badge.svg)](https://github.com/codeigniter4/shield/actions/workflows/deptrac.yml) [![Coverage Status](https://coveralls.io/repos/github/codeigniter4/shield/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/shield?branch=develop) -Shield is an authentication and authorization framework for CodeIgniter 4. While it does provide a base set of tools +Shield is the official authentication and authorization framework for CodeIgniter 4. +While it does provide a base set of tools that are commonly used in websites, it is designed to be flexible and easily customizable. The primary goals for Shield are: @@ -18,43 +19,44 @@ The primary goals for Shield are: ## Authentication Methods -Shield provides two primary methods **Session-based** and **Personal Access Codes** -of authentication out of the box. +Shield provides two primary methods **Session-based** and **Access Token** +authentication out of the box. -It also provides **JSON Web Tokens** authentication. +It also provides **HMAC SHA256 Token** and **JSON Web Token** authentication. ### Session-based -This is your typical email/username/password system you see everywhere. It includes a secure "remember me" functionality. +This is your typical email/username/password system you see everywhere. It includes a secure "remember-me" functionality. This can be used for standard web applications, as well as for single page applications. Includes full controllers and basic views for all standard functionality, like registration, login, forgot password, etc. -### Personal Access Codes +### Access Token -These are much like the access codes that GitHub uses, where they are unique to a single user, and a single user +These are much like the access tokens that GitHub uses, where they are unique to a single user, and a single user can have more than one. This can be used for API authentication of third-party users, and even for allowing access for a mobile application that you build. -### HMAC - SHA256 +### HMAC SHA256 Token -This is a slightly more complicated improvement on Access Codes/Tokens. The main advantage with HMAC is the shared Secret Key +This is a slightly more complicated improvement on Access Token authentication. +The main advantage with HMAC is the shared Secret Key is not passed in the request, but is instead used to create a hash signature of the request body. -### JSON Web Tokens +### JSON Web Token JWT or JSON Web Token is a compact and self-contained way of securely transmitting information between parties as a JSON object. It is commonly used for authentication and authorization purposes in web applications. -## Some Important Features +## Important Features -* Session-based authentication (traditional email/password with remember me) +* Session-based authentication (traditional ID/Password with Remember-me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration * Optional Email-based Two-Factor Authentication after login -* Magic Login Links when a user forgets their password -* Flexible groups-based access control (think roles, but more flexible) -* Users can be granted additional permissions +* Magic Link Login when a user forgets their password +* Flexible Groups-based access control (think Roles, but more flexible) +* Users can be granted additional Permissions See the [An Official Auth Library](https://codeigniter.com/news/shield) for more Info. @@ -71,6 +73,7 @@ Usage of Shield requires the following: ### Installation Installation is done through Composer. + ```console composer require codeigniter4/shield ``` @@ -81,6 +84,7 @@ See the docs From c39b057966e791938513cdd9c7b7e9d140be98bd Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 10:54:07 +0900 Subject: [PATCH 315/401] docs: update LICENSE --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8a8446d16..422da62e7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2021 Lonnie Ezell +Copyright (c) 2020-2022 Lonnie Ezell +Copyright (c) 2022-2023 CodeIgniter Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From ba1a6c446617606042458cd0fea29cce64c1aabd Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 05:22:31 +0900 Subject: [PATCH 316/401] docs: fix typo Co-authored-by: Pooya Parsa --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf090af67..2aeab4954 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ See the docs Date: Tue, 19 Sep 2023 15:37:19 +0900 Subject: [PATCH 317/401] refactor: move $authenticatorHeader and $unusedTokenLifetime to Config\AuthToken --- .../Authenticators/AccessTokens.php | 10 +++++---- .../Authenticators/HmacSha256.php | 10 +++++---- src/Config/Auth.php | 4 ++++ src/Config/AuthToken.php | 22 +++++++++++++++++++ .../AccessTokenAuthenticatorTest.php | 2 +- .../Authenticators/HmacAuthenticatorTest.php | 2 +- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index bf6a7a5c4..93d81182c 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -124,7 +124,7 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), + 'reason' => lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['tokens']]), ]); } @@ -149,7 +149,9 @@ public function check(array $credentials): Result // Hasn't been used in a long time if ( $token->last_used_at - && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) + && $token->last_used_at->isBefore( + Time::now()->subSeconds(config('AuthToken')->unusedTokenLifetime) + ) ) { return new Result([ 'success' => false, @@ -188,7 +190,7 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']), + 'token' => $request->getHeaderLine(config('AuthToken')->authenticatorHeader['tokens']), ])->isOK(); } @@ -246,7 +248,7 @@ public function getBearerToken(): ?string /** @var IncomingRequest $request */ $request = service('request'); - $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['tokens']); + $header = $request->getHeaderLine(config('AuthToken')->authenticatorHeader['tokens']); if (empty($header)) { return null; diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index ea9932091..eb92dd8f5 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -124,7 +124,7 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || $credentials['token'] === '') { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('Auth')->authenticatorHeader['hmac']]), + 'reason' => lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['hmac']]), ]); } @@ -161,7 +161,9 @@ public function check(array $credentials): Result // Hasn't been used in a long time if ( isset($token->last_used_at) - && $token->last_used_at->isBefore(Time::now()->subSeconds(config('Auth')->unusedTokenLifetime)) + && $token->last_used_at->isBefore( + Time::now()->subSeconds(config('AuthToken')->unusedTokenLifetime) + ) ) { return new Result([ 'success' => false, @@ -200,7 +202,7 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('Auth')->authenticatorHeader['hmac']), + 'token' => $request->getHeaderLine(config('AuthToken')->authenticatorHeader['hmac']), ])->isOK(); } @@ -260,7 +262,7 @@ public function getFullHmacToken(): ?string /** @var IncomingRequest $request */ $request = service('request'); - $header = $request->getHeaderLine(config('Auth')->authenticatorHeader['hmac']); + $header = $request->getHeaderLine(config('AuthToken')->authenticatorHeader['hmac']); if ($header === '') { return null; diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 0a3bfa374..1bd038f41 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -146,6 +146,8 @@ class Auth extends BaseConfig * The name of Header that the Authorization token should be found. * According to the specs, this should be `Authorization`, but rare * circumstances might need a different header. + * + * @deprecated Moved to AuthToken. No longer used. */ public array $authenticatorHeader = [ 'tokens' => 'Authorization', @@ -158,6 +160,8 @@ class Auth extends BaseConfig * -------------------------------------------------------------------- * Determines the amount of time, in seconds, that an unused * access token can be used. + * + * @deprecated Moved to AuthToken. No longer used. */ public int $unusedTokenLifetime = YEAR; diff --git a/src/Config/AuthToken.php b/src/Config/AuthToken.php index f83c665fa..d945bc454 100644 --- a/src/Config/AuthToken.php +++ b/src/Config/AuthToken.php @@ -24,6 +24,28 @@ class AuthToken extends BaseConfig */ public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE; + /** + * -------------------------------------------------------------------- + * Name of Authenticator Header + * -------------------------------------------------------------------- + * The name of Header that the Authorization token should be found. + * According to the specs, this should be `Authorization`, but rare + * circumstances might need a different header. + */ + public array $authenticatorHeader = [ + 'tokens' => 'Authorization', + 'hmac' => 'Authorization', + ]; + + /** + * -------------------------------------------------------------------- + * Unused Token Lifetime + * -------------------------------------------------------------------- + * Determines the amount of time, in seconds, that an unused token can + * be used. + */ + public int $unusedTokenLifetime = YEAR; + /** * -------------------------------------------------------------------- * HMAC secret key byte size diff --git a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php index b87bf3930..8734e4ffa 100644 --- a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php @@ -110,7 +110,7 @@ public function testCheckNoToken(): void $result = $this->auth->check([]); $this->assertFalse($result->isOK()); - $this->assertSame(lang('Auth.noToken', [config('Auth')->authenticatorHeader['tokens']]), $result->reason()); + $this->assertSame(lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['tokens']]), $result->reason()); } public function testCheckBadToken(): void diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index 9ea22d19b..faf21f769 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -131,7 +131,7 @@ public function testCheckNoToken(): void $result = $this->auth->check([]); $this->assertFalse($result->isOK()); - $this->assertSame(lang('Auth.noToken', [config('Auth')->authenticatorHeader['hmac']]), $result->reason()); + $this->assertSame(lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['hmac']]), $result->reason()); } public function testCheckBadSignature(): void From faf90f6fac88c4b9c5311bbe2808be68e99cdf2b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 15:42:45 +0900 Subject: [PATCH 318/401] style: break long lines --- src/Authentication/Authenticators/AccessTokens.php | 9 +++++++-- src/Authentication/Authenticators/HmacSha256.php | 9 +++++++-- .../Authenticators/AccessTokenAuthenticatorTest.php | 5 ++++- .../Authenticators/HmacAuthenticatorTest.php | 5 ++++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index 93d81182c..f8e2e9254 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -124,7 +124,10 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || empty($credentials['token'])) { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['tokens']]), + 'reason' => lang( + 'Auth.noToken', + [config('AuthToken')->authenticatorHeader['tokens']] + ), ]); } @@ -190,7 +193,9 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('AuthToken')->authenticatorHeader['tokens']), + 'token' => $request->getHeaderLine( + config('AuthToken')->authenticatorHeader['tokens'] + ), ])->isOK(); } diff --git a/src/Authentication/Authenticators/HmacSha256.php b/src/Authentication/Authenticators/HmacSha256.php index eb92dd8f5..697c7a447 100644 --- a/src/Authentication/Authenticators/HmacSha256.php +++ b/src/Authentication/Authenticators/HmacSha256.php @@ -124,7 +124,10 @@ public function check(array $credentials): Result if (! array_key_exists('token', $credentials) || $credentials['token'] === '') { return new Result([ 'success' => false, - 'reason' => lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['hmac']]), + 'reason' => lang( + 'Auth.noToken', + [config('AuthToken')->authenticatorHeader['hmac']] + ), ]); } @@ -202,7 +205,9 @@ public function loggedIn(): bool $request = service('request'); return $this->attempt([ - 'token' => $request->getHeaderLine(config('AuthToken')->authenticatorHeader['hmac']), + 'token' => $request->getHeaderLine( + config('AuthToken')->authenticatorHeader['hmac'] + ), ])->isOK(); } diff --git a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php index 8734e4ffa..12ffadd54 100644 --- a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php @@ -110,7 +110,10 @@ public function testCheckNoToken(): void $result = $this->auth->check([]); $this->assertFalse($result->isOK()); - $this->assertSame(lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['tokens']]), $result->reason()); + $this->assertSame( + lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['tokens']]), + $result->reason() + ); } public function testCheckBadToken(): void diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index faf21f769..96db32cd0 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -131,7 +131,10 @@ public function testCheckNoToken(): void $result = $this->auth->check([]); $this->assertFalse($result->isOK()); - $this->assertSame(lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['hmac']]), $result->reason()); + $this->assertSame( + lang('Auth.noToken', [config('AuthToken')->authenticatorHeader['hmac']]), + $result->reason() + ); } public function testCheckBadSignature(): void From 4664eb7b1ee33e9118e51ee2b878ee711261afee Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 16:08:35 +0900 Subject: [PATCH 319/401] config: add comments --- src/Config/Auth.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 1bd038f41..edaf3c329 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -20,14 +20,21 @@ class Auth extends BaseConfig { + /** + * //////////////////////////////////////////////////////////////////// + * AUTHENTICATION + * //////////////////////////////////////////////////////////////////// + */ + + // Constants for Record Login Attempts. Do not change. public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts /** - * //////////////////////////////////////////////////////////////////// - * AUTHENTICATION - * //////////////////////////////////////////////////////////////////// + * -------------------------------------------------------------------- + * View files + * -------------------------------------------------------------------- */ public array $views = [ 'login' => '\CodeIgniter\Shield\Views\login', From 0f859144d82aade9037c6f40a921f9a9adde5379 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 16:08:58 +0900 Subject: [PATCH 320/401] config: move items to right place --- src/Config/Auth.php | 75 +++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index edaf3c329..d0a10c076 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -50,43 +50,6 @@ class Auth extends BaseConfig 'magic-link-email' => '\CodeIgniter\Shield\Views\Email\magic_link_email', ]; - /** - * -------------------------------------------------------------------- - * Customize the DB group used for each model - * -------------------------------------------------------------------- - */ - public ?string $DBGroup = null; - - /** - * -------------------------------------------------------------------- - * Customize Name of Shield Tables - * -------------------------------------------------------------------- - * Only change if you want to rename the default Shield table names - * - * It may be necessary to change the names of the tables for - * security reasons, to prevent the conflict of table names, - * the internal policy of the companies or any other reason. - * - * - users Auth Users Table, the users info is stored. - * - auth_identities Auth Identities Table, Used for storage of passwords, access tokens, social login identities, etc. - * - auth_logins Auth Login Attempts, Table records login attempts. - * - auth_token_logins Auth Token Login Attempts Table, Records Bearer Token type login attempts. - * - auth_remember_tokens Auth Remember Tokens (remember-me) Table. - * - auth_groups_users Groups Users Table. - * - auth_permissions_users Users Permissions Table. - * - * @var array - */ - public array $tables = [ - 'users' => 'users', - 'identities' => 'auth_identities', - 'logins' => 'auth_logins', - 'token_logins' => 'auth_token_logins', - 'remember_tokens' => 'auth_remember_tokens', - 'groups_users' => 'auth_groups_users', - 'permissions_users' => 'auth_permissions_users', - ]; - /** * -------------------------------------------------------------------- * Redirect URLs @@ -404,6 +367,44 @@ class Auth extends BaseConfig * OTHER SETTINGS * //////////////////////////////////////////////////////////////////// */ + + /** + * -------------------------------------------------------------------- + * Customize the DB group used for each model + * -------------------------------------------------------------------- + */ + public ?string $DBGroup = null; + + /** + * -------------------------------------------------------------------- + * Customize Name of Shield Tables + * -------------------------------------------------------------------- + * Only change if you want to rename the default Shield table names + * + * It may be necessary to change the names of the tables for + * security reasons, to prevent the conflict of table names, + * the internal policy of the companies or any other reason. + * + * - users Auth Users Table, the users info is stored. + * - auth_identities Auth Identities Table, Used for storage of passwords, access tokens, social login identities, etc. + * - auth_logins Auth Login Attempts, Table records login attempts. + * - auth_token_logins Auth Token Login Attempts Table, Records Bearer Token type login attempts. + * - auth_remember_tokens Auth Remember Tokens (remember-me) Table. + * - auth_groups_users Groups Users Table. + * - auth_permissions_users Users Permissions Table. + * + * @var array + */ + public array $tables = [ + 'users' => 'users', + 'identities' => 'auth_identities', + 'logins' => 'auth_logins', + 'token_logins' => 'auth_token_logins', + 'remember_tokens' => 'auth_remember_tokens', + 'groups_users' => 'auth_groups_users', + 'permissions_users' => 'auth_permissions_users', + ]; + /** * -------------------------------------------------------------------- * User Provider From 68ce900fdfc657fb275bebc6489ab33956c54119 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 16:16:01 +0900 Subject: [PATCH 321/401] refactor: move $usernameValidationRules and $emailValidationRules to Config\Auth --- docs/addons/jwt.md | 2 +- docs/guides/mobile_apps.md | 2 +- src/Config/Auth.php | 27 +++++++++++++++++++ src/Config/AuthSession.php | 4 +++ src/Controllers/LoginController.php | 4 +-- src/Controllers/MagicLinkController.php | 2 +- .../RegistrationValidationRules.php | 4 +-- tests/Controllers/LoginTest.php | 2 +- 8 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index f312e5919..ae3d0d7e7 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -215,7 +215,7 @@ class LoginController extends BaseController return setting('Validation.login') ?? [ 'email' => [ 'label' => 'Auth.email', - 'rules' => config(AuthSession::class)->emailValidationRules, + 'rules' => config('Auth')->emailValidationRules, ], 'password' => [ 'label' => 'Auth.password', diff --git a/docs/guides/mobile_apps.md b/docs/guides/mobile_apps.md index b6da507b4..b06a61f26 100644 --- a/docs/guides/mobile_apps.md +++ b/docs/guides/mobile_apps.md @@ -26,7 +26,7 @@ class LoginController extends BaseController $rules = setting('Validation.login') ?? [ 'email' => [ 'label' => 'Auth.email', - 'rules' => config('AuthSession')->emailValidationRules, + 'rules' => config('Auth')->emailValidationRules, ], 'password' => [ 'label' => 'Auth.password', diff --git a/src/Config/Auth.php b/src/Config/Auth.php index d0a10c076..47a229d1d 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -224,6 +224,33 @@ class Auth extends BaseConfig 'rememberLength' => 30 * DAY, ]; + /** + * -------------------------------------------------------------------- + * The validation rules for username + * -------------------------------------------------------------------- + * + * @var string[] + */ + public array $usernameValidationRules = [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + ]; + + /** + * -------------------------------------------------------------------- + * The validation rules for email + * -------------------------------------------------------------------- + * + * @var string[] + */ + public array $emailValidationRules = [ + 'required', + 'max_length[254]', + 'valid_email', + ]; + /** * -------------------------------------------------------------------- * Minimum Password Length diff --git a/src/Config/AuthSession.php b/src/Config/AuthSession.php index f9c4c28b3..02b5aa345 100644 --- a/src/Config/AuthSession.php +++ b/src/Config/AuthSession.php @@ -15,6 +15,8 @@ class AuthSession extends BaseConfig * The validation rules for username * * @var string[] + * + * @deprecated Moved to Auth. No longer used. */ public array $usernameValidationRules = [ 'required', @@ -27,6 +29,8 @@ class AuthSession extends BaseConfig * The validation rules for email * * @var string[] + * + * @deprecated Moved to Auth. No longer used. */ public array $emailValidationRules = [ 'required', diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index eab0aa316..ba7efe56f 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -85,11 +85,11 @@ protected function getValidationRules(): array return setting('Validation.login') ?? [ // 'username' => [ // 'label' => 'Auth.username', - // 'rules' => config('AuthSession')->usernameValidationRules, + // 'rules' => config('Auth')->usernameValidationRules, // ], 'email' => [ 'label' => 'Auth.email', - 'rules' => config('AuthSession')->emailValidationRules, + 'rules' => config('Auth')->emailValidationRules, ], 'password' => [ 'label' => 'Auth.password', diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index fb036e640..45ac55a43 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -236,7 +236,7 @@ protected function getValidationRules(): array return [ 'email' => [ 'label' => 'Auth.email', - 'rules' => config('AuthSession')->emailValidationRules, + 'rules' => config('Auth')->emailValidationRules, ], ]; } diff --git a/src/Validation/RegistrationValidationRules.php b/src/Validation/RegistrationValidationRules.php index d7755d439..ca2c5a44f 100644 --- a/src/Validation/RegistrationValidationRules.php +++ b/src/Validation/RegistrationValidationRules.php @@ -24,11 +24,11 @@ public function __construct() public function get(): array { $registrationUsernameRules = array_merge( - config('AuthSession')->usernameValidationRules, + config('Auth')->usernameValidationRules, [sprintf('is_unique[%s.username]', $this->tables['users'])] ); $registrationEmailRules = array_merge( - config('AuthSession')->emailValidationRules, + config('Auth')->emailValidationRules, [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); diff --git a/tests/Controllers/LoginTest.php b/tests/Controllers/LoginTest.php index b18ca3f3d..f813568c4 100644 --- a/tests/Controllers/LoginTest.php +++ b/tests/Controllers/LoginTest.php @@ -183,7 +183,7 @@ public function testLoginActionUsernameSuccess(): void 'password' => 'required', ]; }; - $config->login['username'] = config('AuthSession')->usernameValidationRules; + $config->login['username'] = config('Auth')->usernameValidationRules; Factories::injectMock('config', 'Validation', $config); $this->user->createEmailIdentity([ From e09b0bf485fc6ce3ce954a941fe4c73819bcbbf9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 16:27:01 +0900 Subject: [PATCH 322/401] docs: add UPGRADING.md --- UPGRADING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index e8d30a4ef..4b6b32480 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,7 +2,9 @@ ## Version 1.0.0-beta.6 to 1.0.0-beta.7 -### Install New Config AuthToken.php +### Mandatory Config Changes + +#### New Config\AuthToken A new Config file **AuthToken.php** has been introduced. Run `php spark shield:setup` again to install it into **app/Config/**, or install it manually. @@ -11,6 +13,16 @@ Then change the default settings as necessary. When using Token authentication, the default value has been changed from all accesses to be recorded in the ``token_logins`` table to only accesses that fail authentication to be recorded. +#### Config\Auth + +The following items have been moved. They are no longer used and should be removed. + +- `$authenticatorHeader` and `$unusedTokenLifetime` are moved to `Config\AuthToken`. + +The following items have been added. Copy the properties in **src/Config/Auth.php**. + +- `$usernameValidationRules` and `$emailValidationRules` are added. + ## Version 1.0.0-beta.3 to 1.0.0-beta.4 ### Important Password Changes From 1ce4093641a75d1c589d0b02a333f0bf61371f13 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Sep 2023 16:29:53 +0900 Subject: [PATCH 323/401] config: remove unused items --- src/Config/Auth.php | 26 ------------------------- src/Config/AuthSession.php | 40 -------------------------------------- 2 files changed, 66 deletions(-) delete mode 100644 src/Config/AuthSession.php diff --git a/src/Config/Auth.php b/src/Config/Auth.php index 47a229d1d..fa680b40f 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -109,32 +109,6 @@ class Auth extends BaseConfig // 'jwt' => JWT::class, ]; - /** - * -------------------------------------------------------------------- - * Name of Authenticator Header - * -------------------------------------------------------------------- - * The name of Header that the Authorization token should be found. - * According to the specs, this should be `Authorization`, but rare - * circumstances might need a different header. - * - * @deprecated Moved to AuthToken. No longer used. - */ - public array $authenticatorHeader = [ - 'tokens' => 'Authorization', - 'hmac' => 'Authorization', - ]; - - /** - * -------------------------------------------------------------------- - * Unused Token Lifetime - * -------------------------------------------------------------------- - * Determines the amount of time, in seconds, that an unused - * access token can be used. - * - * @deprecated Moved to AuthToken. No longer used. - */ - public int $unusedTokenLifetime = YEAR; - /** * -------------------------------------------------------------------- * Default Authenticator diff --git a/src/Config/AuthSession.php b/src/Config/AuthSession.php deleted file mode 100644 index 02b5aa345..000000000 --- a/src/Config/AuthSession.php +++ /dev/null @@ -1,40 +0,0 @@ - Date: Tue, 19 Sep 2023 16:30:10 +0900 Subject: [PATCH 324/401] docs: fix typos --- src/Config/Auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index fa680b40f..c5e15b035 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -55,7 +55,7 @@ class Auth extends BaseConfig * Redirect URLs * -------------------------------------------------------------------- * The default URL that a user will be redirected to after various auth - * auth actions. This can be either of the following: + * actions. This can be either of the following: * * 1. An absolute URL. E.g. http://example.com OR https://example.com * 2. A named route that can be accessed using `route_to()` or `url_to()` @@ -149,7 +149,7 @@ class Auth extends BaseConfig * Record Last Active Date * -------------------------------------------------------------------- * If true, will always update the `last_active` datetime for the - * logged in user on every page request. + * logged-in user on every page request. * This feature only works when session/tokens filter is active. * * @see https://codeigniter4.github.io/shield/install/#protect-all-pages for set filters. From cc5d01faf3a882a3532ac1e97c491b403c788056 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 23 Sep 2023 10:19:55 +0900 Subject: [PATCH 325/401] docs: update config file path --- docs/getting_started/configuration.md | 2 +- docs/guides/api_hmac_keys.md | 2 +- docs/guides/api_tokens.md | 2 +- docs/guides/mobile_apps.md | 2 +- docs/references/authentication/hmac.md | 2 +- docs/references/authentication/tokens.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/getting_started/configuration.md b/docs/getting_started/configuration.md index 44694bb97..7d621fcb0 100644 --- a/docs/getting_started/configuration.md +++ b/docs/getting_started/configuration.md @@ -20,7 +20,7 @@ This section describes the major Config items that are not described elsewhere. ### Access Token Lifetime -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the **app/Config/Auth.php** config file. +By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the **app/Config/AuthToken.php** config file. ```php public int $unusedTokenLifetime = YEAR; diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index 3f9732814..d324378e7 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -10,7 +10,7 @@ API. When making requests using HMAC keys, the token should be included in the ` > **Note** > By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by -> setting the `$authenticatorHeader['hmac']` value in the **app/Config/Auth.php** config file. +> setting the `$authenticatorHeader['hmac']` value in the **app/Config/AuthToken.php** config file. Tokens are issued with the `generateHmacToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. These shared keys are saved to the database in plain text. The diff --git a/docs/guides/api_tokens.md b/docs/guides/api_tokens.md index 50824d17a..c3d6cc0f8 100644 --- a/docs/guides/api_tokens.md +++ b/docs/guides/api_tokens.md @@ -3,7 +3,7 @@ Access Tokens can be used to authenticate users for your own site, or when allowing third-party developers to access your API. When making requests using access tokens, the token should be included in the `Authorization` header as a `Bearer` token. > **Note** -> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/Auth.php** config file. +> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. Tokens are issued with the `generateAccessToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. Tokens are hashed using a SHA-256 algorithm before being saved to the database. The access token returned when you generate it will include a `raw_token` field that contains the plain-text, un-hashed, token. You should display this to your user at once so they have a chance to copy it somewhere safe, as this is the only time this will be available. After this request, there is no way to get the raw token. diff --git a/docs/guides/mobile_apps.md b/docs/guides/mobile_apps.md index b06a61f26..6698527f5 100644 --- a/docs/guides/mobile_apps.md +++ b/docs/guides/mobile_apps.md @@ -70,6 +70,6 @@ When making all future requests to the API, the mobile client should return the > **Note** > -> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change the header name by setting the `$authenticatorHeader['tokens']` value in the **app/Config/Auth.php** config file. +> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change the header name by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. > > e.g. if `$authenticatorHeader['tokens']` is set to `PersonalAccessCodes` then the mobile client should return the raw token in the `PersonalAccessCodes` header as a `Bearer` token. diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index f5d799ac0..b581e0a1c 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -117,7 +117,7 @@ HMAC Keys/Tokens will expire after a specified amount of time has passed since t This uses the same configuration value as AccessTokens. By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the `Auth` config file. This is in seconds so that you can use the +value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the [time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) that CodeIgniter provides. diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index fe2c2569a..9e50c9320 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -81,7 +81,7 @@ $tokens = $user->accessTokens(); Tokens will expire after a specified amount of time has passed since they have been used. By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the `Auth` config file. This is in seconds so that you can use the +value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the [time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) that CodeIgniter provides. From 9dd0068d04e9a766fb2df2d45a3640a73488d1a3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 16:21:50 +0900 Subject: [PATCH 326/401] docs: update validation rules --- docs/customization/validation_rules.md | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md index f637d4301..1eec89fe7 100644 --- a/docs/customization/validation_rules.md +++ b/docs/customization/validation_rules.md @@ -2,7 +2,7 @@ ## Registration -Shield has the following rules for registration: +Shield has the following rules for registration by default: ```php [ @@ -27,7 +27,10 @@ Shield has the following rules for registration: ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|strong_password', + 'rules' => 'required|max_byte[72]|strong_password[]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes' + ] ], 'password_confirm' => [ 'label' => 'Auth.passwordConfirm', @@ -70,7 +73,10 @@ If you need a different set of rules for registration, you can specify them in y ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|strong_password', + 'rules' => 'required|max_byte[72]|strong_password[]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes' + ] ], 'password_confirm' => [ 'label' => 'Auth.passwordConfirm', @@ -97,12 +103,19 @@ Similar to the process for validation rules in the **Registration** section, you // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', // ], 'email' => [ - 'label' => 'Auth.email', - 'rules' => 'required|max_length[254]|valid_email', + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email' + ], ], 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required', + 'label' => 'Auth.password', + 'rules' => 'required|max_byte[72]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes', + ] ], ]; ``` From ac302bea04dec52e934e08f9de8f8c158f7fe8d5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 24 Sep 2023 16:29:29 +0900 Subject: [PATCH 327/401] fix: remove `strong_password` In previous versions, we used `strong_password` not `strong_password[]`. --- src/Commands/User.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Commands/User.php b/src/Commands/User.php index c78b86e01..9a2b39c1f 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -232,6 +232,9 @@ private function setValidationRules(): void if (($key = array_search('strong_password[]', $passwordRules, true)) !== false) { unset($passwordRules[$key]); } + if (($key = array_search('strong_password', $passwordRules, true)) !== false) { + unset($passwordRules[$key]); + } /** @var Auth $config */ $config = config('Auth'); From 9554986d45d66ae4598ac58769fc2ac8cca7a5fb Mon Sep 17 00:00:00 2001 From: Mohammed AlShannaq Date: Sun, 24 Sep 2023 08:49:39 +0000 Subject: [PATCH 328/401] lang: [ar] fix issue 851 --- src/Language/ar/Auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Language/ar/Auth.php b/src/Language/ar/Auth.php index 3b08f8f8f..3f92deec0 100644 --- a/src/Language/ar/Auth.php +++ b/src/Language/ar/Auth.php @@ -57,7 +57,7 @@ 'backToLogin' => 'العودة إلى نموذج تسجيل الدخول', // Passwords - 'errorPasswordLength' => 'يجب أن تتكون كلمات المرور من {0 ، number} من الأحرف على الأقل.', + 'errorPasswordLength' => 'يجب أن تتكون كلمات المرور من {0, number} من الأحرف على الأقل.', 'suggestPasswordLength' => 'عبارات المرور - التي يصل طولها إلى 255 حرفًا - تجعل كلمات المرور أكثر أمانًا ويسهل تذكرها.', 'errorPasswordCommon' => 'يجب ألا تكون كلمة المرور كلمة مرور شائعة.', 'suggestPasswordCommon' => 'تم فحص كلمة المرور مقابل أكثر من 65 ألف كلمة مرور أو كلمات مرور شائعة الاستخدام تم تسريبها من خلال الاختراقات.', @@ -65,7 +65,7 @@ 'suggestPasswordPersonal' => 'لا يجب اجزاء من عنوان بريدك الإلكتروني أو اسم المستخدم ككلمات مرور.', 'errorPasswordTooSimilar' => 'كلمة المرور مشابهة جدًا لاسم المستخدم.', 'suggestPasswordTooSimilar' => 'لا تستخدم أجزاء من اسم المستخدم الخاص بك في كلمة المرور الخاصة بك.', - 'errorPasswordPwned' => 'تم الكشف عن كلمة المرور {0} بسبب اختراق البيانات وشوهدت {1 ، عدد} مرة في {2} في كلمات المرور المخترقة.', + 'errorPasswordPwned' => 'تم الكشف عن كلمة المرور {0} بسبب اختراق البيانات وشوهدت {1, number} مرة في {2} في كلمات المرور المخترقة.', 'suggestPasswordPwned' => 'يجب عدم استخدام {0} أبدًا ككلمة مرور. إذا كنت تستخدمها في أي مكان ، فقم بتغييرها على الفور.', 'errorPasswordEmpty' => 'كلمة مرور مطلوبة', 'errorPasswordTooLongBytes' => 'لا يمكن أن يتجاوز طول كلمة المرور {param} بايت.', From 6963bb026114e8edadb4996766434ea196912f54 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:09:50 +0330 Subject: [PATCH 329/401] chore: add navigation footer --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index ac3f91a65..27bccb08d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ theme: features: - navigation.sections - content.code.copy + - navigation.footer extra: homepage: https://codeigniter.com From 4d3c185b3505d10802db94829f6faae28f184079 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:12:08 +0330 Subject: [PATCH 330/401] add send PR from site --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 27bccb08d..cb13e1a39 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,6 +15,7 @@ theme: - navigation.sections - content.code.copy - navigation.footer + - content.action.edit extra: homepage: https://codeigniter.com @@ -25,6 +26,7 @@ extra: name: GitHub repo_url: https://github.com/codeigniter4/shield +edit_uri: edit/develop/docs/ markdown_extensions: - pymdownx.superfences From 0d2b74372f4a7fcc03a8abd70668331d305140f1 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:21:09 +0330 Subject: [PATCH 331/401] add accent with colore orange --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index cb13e1a39..070976e87 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,7 @@ theme: text: Raleway palette: primary: deep orange + accent: orange features: - navigation.sections - content.code.copy From 3aee075d95fb01f9f95ff25e7067ae6a0644e232 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:29:53 +0330 Subject: [PATCH 332/401] add back to top --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 070976e87..6d6dc4712 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,7 @@ theme: - content.code.copy - navigation.footer - content.action.edit + - navigation.top extra: homepage: https://codeigniter.com From 23dfa283e7d5ef2ee6240b35ad650eedb69d8087 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:36:36 +0330 Subject: [PATCH 333/401] add copyright for codeigniter --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 6d6dc4712..74e4ed10f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ theme: extra: homepage: https://codeigniter.com + generator: false social: - icon: fontawesome/brands/github @@ -29,6 +30,7 @@ extra: repo_url: https://github.com/codeigniter4/shield edit_uri: edit/develop/docs/ +copyright: Copyright © 2023 CodeIgniter Foundation. markdown_extensions: - pymdownx.superfences From 697643fa637af3df35ebc780313a332b9d78608d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:42:55 +0330 Subject: [PATCH 334/401] improve search --- mkdocs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 74e4ed10f..ebb7fc9a6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,6 +18,9 @@ theme: - navigation.footer - content.action.edit - navigation.top + - search.suggest + - search.highlight + - search.share extra: homepage: https://codeigniter.com From 49a3509f96b19abaff6be0969b749a4c1fc0311a Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 11:56:35 +0330 Subject: [PATCH 335/401] add dark mode --- mkdocs.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index ebb7fc9a6..afd6d28ba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,8 +10,22 @@ theme: font: text: Raleway palette: - primary: deep orange - accent: orange + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep orange + accent: orange + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep orange + accent: orange + toggle: + icon: material/brightness-4 + name: Switch to light mode features: - navigation.sections - content.code.copy From cb1ed2175331ca2d618df01ace0ef2bd65ab6b9e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 12:19:11 +0330 Subject: [PATCH 336/401] add more social link --- mkdocs.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index afd6d28ba..70f4492a1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,9 +41,19 @@ extra: generator: false social: - - icon: fontawesome/brands/github + - icon: material/github link: https://github.com/codeigniter4/shield name: GitHub + - icon: material/twitter + link: https://twitter.com/CodeIgniterPhp + name: X + - icon: material/forum + link: https://forum.codeigniter.com + name: Forum Codeigniter + - icon: material/slack + link: https://join.slack.com/t/codeigniterchat/shared_invite/zt-244xrrslc-l_I69AJSi5y2a2RVN~xIdQ + name: Slack + repo_url: https://github.com/codeigniter4/shield edit_uri: edit/develop/docs/ From b6a0e3c9bd8622313a6df7910516e8f054f18620 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Mon, 25 Sep 2023 12:37:15 +0330 Subject: [PATCH 337/401] improve sidbar nav --- mkdocs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 70f4492a1..d0ff9a41e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,7 +27,8 @@ theme: icon: material/brightness-4 name: Switch to light mode features: - - navigation.sections + - navigation.instant + - navigation.instant.prefetch - content.code.copy - navigation.footer - content.action.edit From 9ada80c11a45207be1fb86703c24ad6a5a66d09d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 25 Sep 2023 16:29:59 +0330 Subject: [PATCH 338/401] docs: remove space from code --- docs/references/authentication/auth_actions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/references/authentication/auth_actions.md b/docs/references/authentication/auth_actions.md index e7275b0b2..9f6379c17 100644 --- a/docs/references/authentication/auth_actions.md +++ b/docs/references/authentication/auth_actions.md @@ -45,13 +45,13 @@ $routes->post('auth/a/verify', 'ActionController::verify'); Views for all of these pages are defined in the `Auth` config file, with the `$views` array. ```php - public $views = [ - 'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show', - 'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify', - 'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email', - 'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show', - 'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email', - ]; +public $views = [ + 'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show', + 'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify', + 'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email', + 'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show', + 'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email', +]; ``` ## Defining New Actions From 47a6442a3e4e681f5e10a149d8e23550eb51b396 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 25 Sep 2023 17:39:42 +0330 Subject: [PATCH 339/401] remove space from jwt.md --- docs/addons/jwt.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index ae3d0d7e7..c2af69ad4 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -54,9 +54,9 @@ To use JWT Authentication, you need additional setup and configuration. You need to add the following constants: ```php - public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all - public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures - public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts + public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all + public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures + public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts ``` You need to add JWT Authenticator: @@ -65,20 +65,20 @@ To use JWT Authentication, you need additional setup and configuration. // ... - public array $authenticators = [ - 'tokens' => AccessTokens::class, - 'session' => Session::class, - 'jwt' => JWT::class, - ]; + public array $authenticators = [ + 'tokens' => AccessTokens::class, + 'session' => Session::class, + 'jwt' => JWT::class, + ]; ``` If you want to use JWT Authenticator in Authentication Chain, add `jwt`: ```php - public array $authenticationChain = [ - 'session', - 'tokens', - 'jwt' - ]; + public array $authenticationChain = [ + 'session', + 'tokens', + 'jwt' + ]; ``` ## Configuration @@ -95,9 +95,9 @@ Set the default payload items to the property `$defaultClaims`. E.g.: ```php - public array $defaultClaims = [ - 'iss' => 'https://codeigniter.com/', - ]; +public array $defaultClaims = [ + 'iss' => 'https://codeigniter.com/', +]; ``` The default claims will be included in all tokens issued by Shield. From b77aaf8922afd57f8bdfa68fda3b6190ebe7ee3f Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 25 Sep 2023 17:52:02 +0330 Subject: [PATCH 340/401] Update authorization.md --- docs/references/authorization.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/references/authorization.md b/docs/references/authorization.md index 74b56ba12..e43d89146 100644 --- a/docs/references/authorization.md +++ b/docs/references/authorization.md @@ -12,7 +12,6 @@ around features, like Beta feature access, or used to provide discrete groups of Groups are defined within the `Shield\Config\AuthGroups` config class. ```php - public array $groups = [ 'superadmin' => [ 'title' => 'Super Admin', From 18517d2eb911d2df5c585fcb205a621edb272495 Mon Sep 17 00:00:00 2001 From: Ronald Date: Tue, 26 Sep 2023 10:05:43 +0200 Subject: [PATCH 341/401] Add revokeAccessTokenBySecret --- docs/guides/api_tokens.md | 3 ++- docs/references/authentication/tokens.md | 6 ++++++ src/Authentication/Traits/HasAccessTokens.php | 11 +++++++++++ src/Models/UserIdentityModel.php | 15 +++++++++++++++ tests/Authentication/HasAccessTokensTest.php | 11 +++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/guides/api_tokens.md b/docs/guides/api_tokens.md index c3d6cc0f8..638b7595e 100644 --- a/docs/guides/api_tokens.md +++ b/docs/guides/api_tokens.md @@ -47,10 +47,11 @@ if ($user->tokenCan('users-read')) { ### Revoking Tokens -Tokens can be revoked by deleting them from the database with the `revokeAccessToken($rawToken)` or `revokeAllAccessTokens()` methods. +Tokens can be revoked by deleting them from the database with the `revokeAccessToken($rawToken)`, `revokeAccessTokenBySecret($secret)` or `revokeAllAccessTokens()` methods. ```php $user->revokeAccessToken($rawToken); +$user->revokeAccessTokenBySecret($secret); $user->revokeAllAccessTokens(); ``` diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index 9e50c9320..b97cb6769 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -56,6 +56,12 @@ Typically, the plain text token is retrieved from the request's headers as part process. If you need to revoke the token for another user as an admin, and don't have access to the token, you would need to get the user's access tokens and delete them manually. +If you don't have the raw token usable to remove the token there is the possibility to remove it using the tokens secret thats stored in the database. It's possible to get a list of all tokens with there secret using the `accessTokens()` function. + +```php +$user->revokeAccessTokenBySecret($secret); +``` + You can revoke all access tokens with the `revokeAllAccessTokens()` method. ```php diff --git a/src/Authentication/Traits/HasAccessTokens.php b/src/Authentication/Traits/HasAccessTokens.php index 389fe9576..e19aecaa2 100644 --- a/src/Authentication/Traits/HasAccessTokens.php +++ b/src/Authentication/Traits/HasAccessTokens.php @@ -47,6 +47,17 @@ public function revokeAccessToken(string $rawToken): void $identityModel->revokeAccessToken($this, $rawToken); } + /** + * Delete any access tokens for the given secret token. + */ + public function revokeAccessTokenBySecret(string $secretToken): void + { + /** @var UserIdentityModel $identityModel */ + $identityModel = model(UserIdentityModel::class); + + $identityModel->revokeAccessTokenBySecret($this, $secretToken); + } + /** * Revokes all access tokens for this user. */ diff --git a/src/Models/UserIdentityModel.php b/src/Models/UserIdentityModel.php index 45c5ee4f3..b91c48712 100644 --- a/src/Models/UserIdentityModel.php +++ b/src/Models/UserIdentityModel.php @@ -456,6 +456,21 @@ public function revokeAccessToken(User $user, string $rawToken): void $this->checkQueryReturn($return); } + /** + * Delete any access tokens for the given secret token. + */ + public function revokeAccessTokenBySecret(User $user, string $secretToken): void + { + $this->checkUserId($user); + + $return = $this->where('user_id', $user->id) + ->where('type', AccessTokens::ID_TYPE_ACCESS_TOKEN) + ->where('secret', $secretToken) + ->delete(); + + $this->checkQueryReturn($return); + } + /** * Revokes all access tokens for this user. */ diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 302e99906..9612b8136 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -101,6 +101,17 @@ public function testRevokeAccessToken(): void $this->assertCount(0, $this->user->accessTokens()); } + public function testRevokeAccessTokenBySecret(): void + { + $token = $this->user->generateAccessToken('foo'); + + $this->assertCount(1, $this->user->accessTokens()); + + $this->user->revokeAccessTokenBySecret($token->secret); + + $this->assertCount(0, $this->user->accessTokens()); + } + public function testRevokeAllAccessTokens(): void { $this->user->generateAccessToken('foo'); From 6ace36c95041b9026e8a8e7bfb82226326a33c88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:43:46 +0000 Subject: [PATCH 342/401] chore(deps-dev): update rector/rector requirement from 0.18.3 to 0.18.4 Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/0.18.3...0.18.4) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 72368f0e3..95127ab00 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "mockery/mockery": "^1.0", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-strict-rules": "^1.5", - "rector/rector": "0.18.3" + "rector/rector": "0.18.4" }, "provide": { "codeigniter4/authentication-implementation": "1.0" From 82f0ee5c9944a1fcd9ea596b849209d3ba1feb00 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 27 Sep 2023 06:39:13 +0330 Subject: [PATCH 343/401] docs: improve dark mode (#859) * add darkmode css file * add style for dark mode * move hljs to js/hljs.js * set use js/ dir for js files * remove - form extra_css --- docs/assets/css/dark_mode.css | 45 +++++++++++++++++++++++++++++++++++ docs/assets/{ => js}/hljs.js | 0 mkdocs.yml | 5 ++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/assets/css/dark_mode.css rename docs/assets/{ => js}/hljs.js (100%) diff --git a/docs/assets/css/dark_mode.css b/docs/assets/css/dark_mode.css new file mode 100644 index 000000000..108ef5263 --- /dev/null +++ b/docs/assets/css/dark_mode.css @@ -0,0 +1,45 @@ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #6a290d; + --md-primary-fg-color--light: #8d7474; + --md-primary-fg-color--dark: #6d554d; + + .hljs-title, + .hljs-title.class_, + .hljs-title.class_.inherited__, + .hljs-title.function_ { + color: #c9a69b; + } + + .hljs-meta .hljs-string, + .hljs-regexp, + .hljs-string { + color: #a3b4c7; + } + + .hljs-attr, + .hljs-attribute, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-operator, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-id, + .hljs-variable { + color: #c1b79f; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-meta .hljs-keyword, + .hljs-template-tag, + .hljs-template-variable, + .hljs-type, + .hljs-variable.language_ { + color: #c97100; + } + + .hljs-subst { + color: #ddba52 + } +} diff --git a/docs/assets/hljs.js b/docs/assets/js/hljs.js similarity index 100% rename from docs/assets/hljs.js rename to docs/assets/js/hljs.js diff --git a/mkdocs.yml b/mkdocs.yml index d0ff9a41e..98e3cc197 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,7 +54,7 @@ extra: - icon: material/slack link: https://join.slack.com/t/codeigniterchat/shared_invite/zt-244xrrslc-l_I69AJSi5y2a2RVN~xIdQ name: Slack - + repo_url: https://github.com/codeigniter4/shield edit_uri: edit/develop/docs/ @@ -67,10 +67,11 @@ markdown_extensions: extra_css: - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/github.min.css + - assets/css/dark_mode.css extra_javascript: - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/highlight.min.js - - assets/hljs.js + - assets/js/hljs.js nav: - Home: index.md From 19bbfbb3e3c5e580b83739bdd329d94deba5084c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Tue, 26 Sep 2023 22:22:00 +0330 Subject: [PATCH 344/401] use new style for note and warning --- docs/addons/jwt.md | 17 +++++----- docs/customization/login_identifier.md | 5 +-- docs/customization/table_names.md | 5 +-- docs/customization/validation_rules.md | 15 +++++---- docs/getting_started/install.md | 16 +++++----- docs/guides/api_hmac_keys.md | 24 ++++++++------ docs/guides/api_tokens.md | 15 +++++---- docs/guides/mobile_apps.md | 9 +++--- docs/guides/strengthen_password.md | 19 ++++++------ docs/quick_start_guide/using_authorization.md | 5 +-- docs/quick_start_guide/using_session_auth.md | 20 +++++++----- .../authentication/authentication.md | 7 +++-- docs/references/authentication/hmac.md | 7 +++-- docs/references/authorization.md | 31 +++++++++++-------- docs/references/controller_filters.md | 10 +++--- docs/references/magic_link_login.md | 5 +-- docs/user_management/managing_users.md | 5 +-- 17 files changed, 120 insertions(+), 95 deletions(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index c2af69ad4..de087dcf7 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -1,7 +1,8 @@ # JWT Authentication -> **Note** -> Shield now supports only JWS (Singed JWT). JWE (Encrypted JWT) is not supported. +!!! note + + Shield now supports only JWS (Singed JWT). JWE (Encrypted JWT) is not supported. ## What is JWT? @@ -87,9 +88,10 @@ Configure **app/Config/AuthJWT.php** for your needs. ### Set the Default Claims -> **Note** -> A payload contains the actual data being transmitted, such as user ID, role, -> or expiration time. Items in a payload is called *claims*. +!!! note + + A payload contains the actual data being transmitted, such as user ID, role, + or expiration time. Items in a payload is called *claims*. Set the default payload items to the property `$defaultClaims`. @@ -121,8 +123,9 @@ with the following command: php -r 'echo base64_encode(random_bytes(32));' ``` -> **Note** -> The secret key is used for signing and validating tokens. +!!! note + + The secret key is used for signing and validating tokens. ## Issuing JWTs diff --git a/docs/customization/login_identifier.md b/docs/customization/login_identifier.md index 2239d3ad8..5fe3195c3 100644 --- a/docs/customization/login_identifier.md +++ b/docs/customization/login_identifier.md @@ -21,8 +21,9 @@ This only works with the Session authenticator. 'employee_id' ]; ``` - > **Warning** - > It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. + !!! warning + + It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. 3. Edit the login form to change the name of the default `email` input to the new field name. diff --git a/docs/customization/table_names.md b/docs/customization/table_names.md index 03eca1c98..daad7d7f8 100644 --- a/docs/customization/table_names.md +++ b/docs/customization/table_names.md @@ -17,5 +17,6 @@ public array $tables = [ Set the table names that you want in the array values. -> **Note** -> You must change the table names before running database migrations. +!!! note + + You must change the table names before running database migrations. diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md index 1eec89fe7..c630fd505 100644 --- a/docs/customization/validation_rules.md +++ b/docs/customization/validation_rules.md @@ -39,11 +39,10 @@ Shield has the following rules for registration by default: ]; ``` -> **Note** -> If you customize the table names, the table names -> (`users` and `auth_identities`) in the above rules will be automatically -> changed. The rules are implemented in -> `RegisterController::getValidationRules()`. +!!! note + + If you customize the table names, the table names(`users` and `auth_identities`) in the above rules will be automatically changed. + The rules are implemented in `RegisterController::getValidationRules()`. If you need a different set of rules for registration, you can specify them in your `Validation` configuration (**app/Config/Validation.php**) like: @@ -85,9 +84,9 @@ If you need a different set of rules for registration, you can specify them in y ]; ``` -> **Note** -> If you customize the table names, set the correct table names in the -> rules. +!!! note + + If you customize the table names, set the correct table names in the rules. ## Login diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index 8a23656b0..379752902 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -62,10 +62,10 @@ Require it with an explicit version constraint allowing its desired stability. php spark shield:setup ``` - > **Note** - > If you want to customize table names, you must change the table names - > before running database migrations. - > See [Customizing Table Names](../customization/table_names.md). + !!! note + + If you want to customize table names, you must change the table names before running database migrations. + See [Customizing Table Names](../customization/table_names.md). 2. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). @@ -140,10 +140,10 @@ your project. 5. **Migration** Run the migrations. - > **Note** - > If you want to customize table names, you must change the table names - > before running database migrations. - > See [Customizing Table Names](../customization/table_names.md). + !!! note + + If you want to customize table names, you must change the table names before running database migrations. + See [Customizing Table Names](../customization/table_names.md). ```console php spark migrate --all diff --git a/docs/guides/api_hmac_keys.md b/docs/guides/api_hmac_keys.md index d324378e7..f64825d0b 100644 --- a/docs/guides/api_hmac_keys.md +++ b/docs/guides/api_hmac_keys.md @@ -1,16 +1,18 @@ # Protecting an API with HMAC Keys -> **Note** -> For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, +!!! note + + For the purpose of this documentation and to maintain a level of consistency with the Authorization Tokens, the term "Token" will be used to represent a set of API Keys (key and secretKey). HMAC Keys can be used to authenticate users for your own site, or when allowing third-party developers to access your API. When making requests using HMAC keys, the token should be included in the `Authorization` header as an `HMAC-SHA256` token. -> **Note** -> By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by -> setting the `$authenticatorHeader['hmac']` value in the **app/Config/AuthToken.php** config file. +!!! note + + By default, `$authenticatorHeader['hmac']` is set to `Authorization`. You can change this value by + setting the `$authenticatorHeader['hmac']` value in the **app/Config/AuthToken.php** config file. Tokens are issued with the `generateHmacToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. These shared keys are saved to the database in plain text. The @@ -63,9 +65,10 @@ $token = $user->generateHmacToken('token-name', ['users-read']); return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); ``` -> **Note** -> At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being -> correctly recognized. +!!! note + + At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being + correctly recognized. When handling incoming requests you can check if the token has been granted access to the scope with the `hmacTokenCan()` method. @@ -111,5 +114,6 @@ parses the raw token and looks it up the `key` portion in the database. Once fou to validate the remainder of the Authorization raw token. If it passes the signature test it can determine the correct user, which will then be available through an `auth()->user()` call. -> **Note** -> Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked. +!!! note + + Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked. diff --git a/docs/guides/api_tokens.md b/docs/guides/api_tokens.md index c3d6cc0f8..64dbd48c3 100644 --- a/docs/guides/api_tokens.md +++ b/docs/guides/api_tokens.md @@ -2,8 +2,9 @@ Access Tokens can be used to authenticate users for your own site, or when allowing third-party developers to access your API. When making requests using access tokens, the token should be included in the `Authorization` header as a `Bearer` token. -> **Note** -> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. +!!! note + + By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change this value by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. Tokens are issued with the `generateAccessToken()` method on the user. This returns a `CodeIgniter\Shield\Entities\AccessToken` instance. Tokens are hashed using a SHA-256 algorithm before being saved to the database. The access token returned when you generate it will include a `raw_token` field that contains the plain-text, un-hashed, token. You should display this to your user at once so they have a chance to copy it somewhere safe, as this is the only time this will be available. After this request, there is no way to get the raw token. @@ -34,8 +35,9 @@ Access tokens can be given `scopes`, which are basically permission strings, for return $user->generateAccessToken('token-name', ['users-read'])->raw_token; ``` -> **Note** -> At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being correctly recognized. +!!! note + + At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being correctly recognized. When handling incoming requests you can check if the token has been granted access to the scope with the `tokenCan()` method. @@ -77,5 +79,6 @@ $routes->get('users', 'UserController::list', ['filter' => 'tokens:users-read']) When the filter runs, it checks the `Authorization` header for a `Bearer` value that has the raw token. It then hashes the raw token and looks it up in the database. Once found, it can determine the correct user, which will then be available through an `auth()->user()` call. -> **Note** -> Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked. +!!! note + + Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked. diff --git a/docs/guides/mobile_apps.md b/docs/guides/mobile_apps.md index 6698527f5..c974bf496 100644 --- a/docs/guides/mobile_apps.md +++ b/docs/guides/mobile_apps.md @@ -68,8 +68,7 @@ class LoginController extends BaseController When making all future requests to the API, the mobile client should return the raw token in the `Authorization` header as a `Bearer` token. -> **Note** -> -> By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change the header name by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. -> -> e.g. if `$authenticatorHeader['tokens']` is set to `PersonalAccessCodes` then the mobile client should return the raw token in the `PersonalAccessCodes` header as a `Bearer` token. +!!! note + + By default, `$authenticatorHeader['tokens']` is set to `Authorization`. You can change the header name by setting the `$authenticatorHeader['tokens']` value in the **app/Config/AuthToken.php** config file. + e.g. if `$authenticatorHeader['tokens']` is set to `PersonalAccessCodes` then the mobile client should return the raw token in the `PersonalAccessCodes` header as a `Bearer` token. diff --git a/docs/guides/strengthen_password.md b/docs/guides/strengthen_password.md index 6fad8ea5c..fbf6dc9a0 100644 --- a/docs/guides/strengthen_password.md +++ b/docs/guides/strengthen_password.md @@ -15,13 +15,12 @@ It is the recommended minimum value by NIST. However, some organizations recomme The longer the password, the stronger it is. Consider increasing the value. -> **Note** -> -> This checking works when you validate passwords with the `strong_password[]` -> validation rule. -> -> If you disable `CompositionValidator` (enabled by default) in `$passwordValidators`, -> this checking will not work. +!!! note + + This checking works when you validate passwords with the `strong_password[]` + validation rule. + If you disable `CompositionValidator` (enabled by default) in `$passwordValidators`, + this checking will not work. ## Password Hashing Algorithm @@ -117,6 +116,6 @@ setting for using passwords stored in older versions of Shield that were [vulner This setting is deprecated. If you have this setting set to `true`, you should change it to `false` as soon as possible, and remove old hashed password in your database. -> **Note** -> -> This setting will be removed in v1.0.0 official release. +!!! note + + This setting will be removed in v1.0.0 official release. diff --git a/docs/quick_start_guide/using_authorization.md b/docs/quick_start_guide/using_authorization.md index c4d6d9f3a..36bd918e0 100644 --- a/docs/quick_start_guide/using_authorization.md +++ b/docs/quick_start_guide/using_authorization.md @@ -79,8 +79,9 @@ if (! auth()->user()->can('users.create')) { } ``` -> **Note** -> The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. +!!! note + + The example above can also be done through a [controller filter](https://codeigniter.com/user_guide/incoming/filters.html) if you want to apply it to multiple pages of your site. ## Adding a Group To a User diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index 76086a855..645d2a1b2 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -4,8 +4,9 @@ Learning any new authentication system can be difficult, especially as they get more flexible and sophisticated. This guide is intended to provide short examples for common actions you'll take when working with Shield. It is not intended to be the exhaustive documentation for each section. That's better handled through the area-specific doc files. -> **Note** -> The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. +!!! note + + The examples assume that you have run the setup script and that you have copies of the `Auth` and `AuthGroups` config files in your application's **app/Config** folder. ## Configuration @@ -24,8 +25,9 @@ public array $redirects = [ ]; ``` -> **Note** -> This redirect happens after the specified action is complete. In the case of register or login, it might not happen immediately. For example, if you have any Auth Actions specified, they will be redirected when those actions are completed successfully. If no Auth Actions are specified, they will be redirected immediately after registration or login. +!!! note + + This redirect happens after the specified action is complete. In the case of register or login, it might not happen immediately. For example, if you have any Auth Actions specified, they will be redirected when those actions are completed successfully. If no Auth Actions are specified, they will be redirected immediately after registration or login. ### Configure Remember-me Functionality @@ -42,8 +44,9 @@ public array $sessionConfig = [ ### Enable Account Activation via Email -> **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). +!!! note + + You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the `Auth` config file. @@ -56,8 +59,9 @@ public array $actions = [ ### Enable Two-Factor Authentication -> **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). +!!! note + + You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). Turned off by default, Shield's Email-based 2FA can be enabled by specifying the class to use in the `Auth` config file. diff --git a/docs/references/authentication/authentication.md b/docs/references/authentication/authentication.md index 69174d75c..1ade0ffed 100644 --- a/docs/references/authentication/authentication.md +++ b/docs/references/authentication/authentication.md @@ -46,9 +46,10 @@ user_id(); auth()->getProvider(); ``` -> **Note** -> The `auth_helper` is autoloaded by Composer. If you want to *override* the functions, -> you need to define them in **app/Common.php**. +!!! note + + The `auth_helper` is autoloaded by Composer. If you want to *override* the functions, + you need to define them in **app/Common.php**. ## Authenticator Responses diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index b581e0a1c..c8c504453 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -10,9 +10,10 @@ with their email/password. The application would create a new access token for t name, like John's iPhone 12, and return it to the mobile application, where it is stored and used in all future requests. -> **Note** -> For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, -> the term "Token" will be used to represent a set of API Keys (key and secretKey). +!!! note + + For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, + the term "Token" will be used to represent a set of API Keys (key and secretKey). ## Usage diff --git a/docs/references/authorization.md b/docs/references/authorization.md index e43d89146..81c1e6a14 100644 --- a/docs/references/authorization.md +++ b/docs/references/authorization.md @@ -56,8 +56,9 @@ public array $permissions = [ In order to grant any permissions to a group, they must have the permission assigned to the group, within the `AuthGroups` config file, under the `$matrix` property. -> **Note** -> This defines **group-level permissons**. +!!! note + + This defines **group-level permissons**. The matrix is an associative array with the group name as the key, and an array of permissions that should be applied to that group. @@ -121,10 +122,11 @@ if (! $user->hasPermission('users.create')) { } ``` -> **Note** -> This method checks only **user-level permissions**, and does not check -> group-level permissions. If you want to check if the user can do something, -> use the `$user->can()` method instead. +!!! note + + This method checks only **user-level permissions**, and does not check + group-level permissions. If you want to check if the user can do something, + use the `$user->can()` method instead. #### Authorizing via Routes @@ -151,9 +153,10 @@ $routes->group('admin', ['filter' => 'group:admin,superadmin'], static function Note that the options (`filter`) passed to the outer `group()` are not merged with the inner `group()` options. -> **Note** -> If you set more than one filter to a route, you need to enable -> [Multiple Filters](https://codeigniter.com/user_guide/incoming/routing.html#multiple-filters). +!!! note + + If you set more than one filter to a route, you need to enable + [Multiple Filters](https://codeigniter.com/user_guide/incoming/routing.html#multiple-filters). ## Managing User Permissions @@ -195,8 +198,9 @@ Returns all **user-level** permissions this user has assigned directly to them. $user->getPermissions(); ``` -> **Note** -> This method does not return **group-level permissions**. +!!! note + + This method does not return **group-level permissions**. ## Managing User Groups @@ -249,8 +253,9 @@ if ($user->isActivated()) { } ``` -> **Note** -> If no activator is specified in the `Auth` config file, `actions['register']` property, then this will always return `true`. +!!! note + + If no activator is specified in the `Auth` config file, `actions['register']` property, then this will always return `true`. You can check if a user has not been activated yet via the `isNotActivated()` method. diff --git a/docs/references/controller_filters.md b/docs/references/controller_filters.md index e10adc888..88dfe4c1a 100644 --- a/docs/references/controller_filters.md +++ b/docs/references/controller_filters.md @@ -2,8 +2,9 @@ ## Provided Filters -> **Note** -> These filters are already loaded for you by the [registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) class located at **src/Config/Registrar.php**. +!!! note + + These filters are already loaded for you by the [registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) class located at **src/Config/Registrar.php**. The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: @@ -83,8 +84,9 @@ public $globals = [ ``` In the example above, it is assumed that the page you have created for users to change their password after successful login is **change-password**. -> **Note** -> If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. +!!! note + + If you have grouped or changed the default format of the routes, ensure that your code matches the new format(s) in the **app/Config/Filter.php** file. For example, if you configured your routes like so: diff --git a/docs/references/magic_link_login.md b/docs/references/magic_link_login.md index 361c7aa38..942f08aa8 100644 --- a/docs/references/magic_link_login.md +++ b/docs/references/magic_link_login.md @@ -25,8 +25,9 @@ public int $magicLinkLifetime = HOUR; ## Responding to Magic Link Logins -> **Note** -> You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). +!!! note + + You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup). Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password. diff --git a/docs/user_management/managing_users.md b/docs/user_management/managing_users.md index 57f67c32d..834670ac4 100644 --- a/docs/user_management/managing_users.md +++ b/docs/user_management/managing_users.md @@ -39,8 +39,9 @@ $users = auth()->getProvider(); $users->delete($user->id, true); ``` -> **Note** -> The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. +!!! note + + The User rows use [soft deletes](https://codeigniter.com/user_guide/models/model.html#usesoftdeletes) so they are not actually deleted from the database unless the second parameter is `true`, like above. ### Editing a User From fa091c811d495507b4c0f9720b4046bc3788f6a1 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 08:51:55 +0330 Subject: [PATCH 345/401] fix note format --- docs/customization/login_identifier.md | 2 +- docs/getting_started/concepts.md | 21 ++++++++++----------- docs/getting_started/install.md | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/customization/login_identifier.md b/docs/customization/login_identifier.md index 5fe3195c3..aa51d4b4d 100644 --- a/docs/customization/login_identifier.md +++ b/docs/customization/login_identifier.md @@ -23,7 +23,7 @@ This only works with the Session authenticator. ``` !!! warning - It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. + It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. 3. Edit the login form to change the name of the default `email` input to the new field name. diff --git a/docs/getting_started/concepts.md b/docs/getting_started/concepts.md index 58d4641c9..04ad6db9f 100644 --- a/docs/getting_started/concepts.md +++ b/docs/getting_started/concepts.md @@ -80,14 +80,13 @@ public $passwordValidators = [ You use `strong_password` rule for password validation explained above. -> **Note** -> The `strong_password` rule only supports use cases to check the user's own password. -> It fetches the authenticated user's data for **NothingPersonalValidator** -> if the visitor is authenticated. -> -> If you want to have use cases that set and check another user's password, -> you can't use `strong_password`. You need to use `service('passwords')` directly -> to check the password. -> -> But remember, it is not good practice to set passwords for other users. -> This is because the password should be known only by that user. +!!! note + + The `strong_password` rule only supports use cases to check the user's own password. + It fetches the authenticated user's data for **NothingPersonalValidator** + if the visitor is authenticated. + If you want to have use cases that set and check another user's password, + you can't use `strong_password`. You need to use `service('passwords')` directly + to check the password. + But remember, it is not good practice to set passwords for other users. + This is because the password should be known only by that user. diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index 379752902..cc37f52fb 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -64,8 +64,8 @@ Require it with an explicit version constraint allowing its desired stability. !!! note - If you want to customize table names, you must change the table names before running database migrations. - See [Customizing Table Names](../customization/table_names.md). + If you want to customize table names, you must change the table names before running database migrations. + See [Customizing Table Names](../customization/table_names.md). 2. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). @@ -142,8 +142,8 @@ your project. !!! note - If you want to customize table names, you must change the table names before running database migrations. - See [Customizing Table Names](../customization/table_names.md). + If you want to customize table names, you must change the table names before running database migrations. + See [Customizing Table Names](../customization/table_names.md). ```console php spark migrate --all From d148d8735016ab29ef31292a5aaf011d966833ac Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 08:53:17 +0330 Subject: [PATCH 346/401] use new style for note/warning format --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 98e3cc197..c7fffc7c8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,8 @@ markdown_extensions: - pymdownx.superfences - pymdownx.highlight: use_pygments: false + - admonition + - pymdownx.details extra_css: - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/github.min.css From cecc5ff6b2f488244452cf15aac1d77587a47d97 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 08:56:51 +0330 Subject: [PATCH 347/401] update dark mode css for note/warning --- docs/assets/css/dark_mode.css | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/assets/css/dark_mode.css b/docs/assets/css/dark_mode.css index 108ef5263..426f245a1 100644 --- a/docs/assets/css/dark_mode.css +++ b/docs/assets/css/dark_mode.css @@ -42,4 +42,33 @@ .hljs-subst { color: #ddba52 } -} + + .md-typeset .note>.admonition-title, + .md-typeset .note>summary { + background-color: #0000001a; + } + + .md-typeset .admonition.note, + .md-typeset details.note { + border-color: #675647; + } + + .md-typeset .note>.admonition-title:before, + .md-typeset .note>summary:before { + background-color: #65686d; + -webkit-mask-image: var(--md-admonition-icon--note); + mask-image: var(--md-admonition-icon--note); + } + + .md-typeset .admonition.warning, + .md-typeset details.warning { + border-color: #776144; + } + + .md-typeset .warning>.admonition-title:before, + .md-typeset .warning>summary:before { + background-color: #d9913bc2; + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-image: var(--md-admonition-icon--warning); + } +} \ No newline at end of file From ddfc52b00df395f564d4a0b74a8b0b45af6f5a6e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 09:06:02 +0330 Subject: [PATCH 348/401] use LF --- docs/assets/css/dark_mode.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/assets/css/dark_mode.css b/docs/assets/css/dark_mode.css index 426f245a1..8bc72d6a8 100644 --- a/docs/assets/css/dark_mode.css +++ b/docs/assets/css/dark_mode.css @@ -43,18 +43,18 @@ color: #ddba52 } - .md-typeset .note>.admonition-title, - .md-typeset .note>summary { + .md-typeset .note > .admonition-title, + .md-typeset .note > summary { background-color: #0000001a; } - + .md-typeset .admonition.note, .md-typeset details.note { border-color: #675647; } - .md-typeset .note>.admonition-title:before, - .md-typeset .note>summary:before { + .md-typeset .note > .admonition-title:before, + .md-typeset .note > summary:before { background-color: #65686d; -webkit-mask-image: var(--md-admonition-icon--note); mask-image: var(--md-admonition-icon--note); @@ -65,10 +65,10 @@ border-color: #776144; } - .md-typeset .warning>.admonition-title:before, - .md-typeset .warning>summary:before { + .md-typeset .warning > .admonition-title:before, + .md-typeset .warning > summary:before { background-color: #d9913bc2; -webkit-mask-image: var(--md-admonition-icon--warning); mask-image: var(--md-admonition-icon--warning); } -} \ No newline at end of file +} From e5217b5f0592c3fbc9095373b0d65667dec77065 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 09:15:39 +0330 Subject: [PATCH 349/401] use curl for beter code show --- docs/addons/jwt.md | 4 ++-- docs/assets/js/curl.min.js | 14 ++++++++++++++ mkdocs.yml | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 docs/assets/js/curl.min.js diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index de087dcf7..09f0ac1c6 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -234,7 +234,7 @@ class LoginController extends BaseController You could send a request with the existing user's credentials by curl like this: -```console +```curl curl --location 'http://localhost:8080/auth/jwt' \ --header 'Content-Type: application/json' \ --data-raw '{"email" : "admin@example.jp" , "password" : "passw0rd!"}' @@ -245,7 +245,7 @@ the `Authorization` header as a `Bearer` token. You could send a request with the `Authorization` header by curl like this: -```console +```curl curl --location --request GET 'http://localhost:8080/api/users' \ --header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTaGllbGQgVGVzdCBBcHAiLCJzdWIiOiIxIiwiaWF0IjoxNjgxODA1OTMwLCJleHAiOjE2ODE4MDk1MzB9.DGpOmRPOBe45whVtEOSt53qJTw_CpH0V8oMoI_gm2XI' ``` diff --git a/docs/assets/js/curl.min.js b/docs/assets/js/curl.min.js new file mode 100644 index 000000000..924af722a --- /dev/null +++ b/docs/assets/js/curl.min.js @@ -0,0 +1,14 @@ +/*! `curl` grammar compiled for Highlight.js 11.3.1 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={className:"string",begin:/"/, +end:/"/,contains:[e.BACKSLASH_ESCAPE,{className:"variable",begin:/\$\(/, +end:/\)/,contains:[e.BACKSLASH_ESCAPE]}],relevance:0},a={className:"number", +variants:[{begin:e.C_NUMBER_RE}],relevance:0};return{name:"curl", +aliases:["curl"],keywords:"curl",case_insensitive:!0,contains:[{ +className:"literal",begin:/(--request|-X)\s/,contains:[{className:"symbol", +begin:/(get|post|delete|options|head|put|patch|trace|connect)/,end:/\s/, +returnEnd:!0}],returnEnd:!0,relevance:10},{className:"literal",begin:/--/, +end:/[\s"]/,returnEnd:!0,relevance:0},{className:"literal",begin:/-\w/, +end:/[\s"]/,returnEnd:!0,relevance:0},n,{className:"string",begin:/\\"/, +relevance:0},{className:"string",begin:/'/,end:/'/,relevance:0 +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,{match:/(\/[a-z._-]+)+/}]}}})() +;hljs.registerLanguage("curl",e)})(); \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index c7fffc7c8..097f5ac5c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,7 @@ extra_css: extra_javascript: - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/highlight.min.js - assets/js/hljs.js + - assets/js/curl.min.js nav: - Home: index.md From ae1449ea0453f7ad7272bfef97838072e1e16a06 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 09:19:03 +0330 Subject: [PATCH 350/401] update highlightjs to 11.8.0 --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 097f5ac5c..5db2edea3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,11 +68,11 @@ markdown_extensions: - pymdownx.details extra_css: - - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/styles/github.min.css + - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/styles/default.min.css - assets/css/dark_mode.css extra_javascript: - - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/highlight.min.js + - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/highlight.min.js - assets/js/hljs.js - assets/js/curl.min.js From 1c6854291d57cff01e7f0e94c625682d64b7f148 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 09:25:17 +0330 Subject: [PATCH 351/401] show Last update/Created date --- .github/workflows/docs.yml | 1 + mkdocs.yml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 520a60791..e0433efc1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,4 +14,5 @@ jobs: with: python-version: 3.x - run: pip install mkdocs-material + - run: pip install mkdocs-git-revision-date-localized-plugin - run: mkdocs gh-deploy --force diff --git a/mkdocs.yml b/mkdocs.yml index 5db2edea3..ed02dc125 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,6 +76,10 @@ extra_javascript: - assets/js/hljs.js - assets/js/curl.min.js +plugins: + - search + - git-revision-date-localized: + enable_creation_date: true nav: - Home: index.md - Getting Started: From 292e0a344e1f219e90cf0945b4371278966ee7fb Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Wed, 27 Sep 2023 09:42:34 +0330 Subject: [PATCH 352/401] fix: remiove space codeblock --- docs/customization/validation_rules.md | 112 ++++++++++++------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md index c630fd505..7e9a7afdc 100644 --- a/docs/customization/validation_rules.md +++ b/docs/customization/validation_rules.md @@ -47,41 +47,41 @@ Shield has the following rules for registration by default: If you need a different set of rules for registration, you can specify them in your `Validation` configuration (**app/Config/Validation.php**) like: ```php - //-------------------------------------------------------------------- - // Rules For Registration - //-------------------------------------------------------------------- - public $registration = [ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', - 'is_unique[users.username]', - ], - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email', - 'is_unique[auth_identities.secret]', - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|max_byte[72]|strong_password[]', - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes' - ] +//-------------------------------------------------------------------- +// Rules For Registration +//-------------------------------------------------------------------- +public $registration = [ + 'username' => [ + 'label' => 'Auth.username', + 'rules' => [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + 'is_unique[users.username]', ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', + ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email', + 'is_unique[auth_identities.secret]', ], - ]; + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required|max_byte[72]|strong_password[]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes' + ] + ], + 'password_confirm' => [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ], +]; ``` !!! note @@ -93,28 +93,28 @@ If you need a different set of rules for registration, you can specify them in y Similar to the process for validation rules in the **Registration** section, you can add rules for the login form to **app/Config/Validation.php** and change the rules. ```php - //-------------------------------------------------------------------- - // Rules For Login - //-------------------------------------------------------------------- - public $login = [ - // 'username' => [ - // 'label' => 'Auth.username', - // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', - // ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email' - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|max_byte[72]', - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ] +//-------------------------------------------------------------------- +// Rules For Login +//-------------------------------------------------------------------- +public $login = [ + // 'username' => [ + // 'label' => 'Auth.username', + // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', + // ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email' ], - ]; + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => 'required|max_byte[72]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes', + ] + ], +]; ``` From eefd8888ab965eaf37942ebf5f0ab04963842d1b Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Sep 2023 15:15:47 +0900 Subject: [PATCH 353/401] chore: add phpunit-lowest.yml See https://github.com/codeigniter4/.github/pull/16 --- .github/workflows/phpunit-lowest.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/phpunit-lowest.yml diff --git a/.github/workflows/phpunit-lowest.yml b/.github/workflows/phpunit-lowest.yml new file mode 100644 index 000000000..242a89803 --- /dev/null +++ b/.github/workflows/phpunit-lowest.yml @@ -0,0 +1,23 @@ +name: PHPUnit Lowest + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit-lowest.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit-lowest.yml' + +jobs: + phpunit: + uses: codeigniter4/.github/.github/workflows/phpunit-lowest.yml@main From 48d01180791545398bd9c7905e3e54896ee6d411 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Dadashi Date: Thu, 28 Sep 2023 14:08:30 +0330 Subject: [PATCH 354/401] use pip3 instance pip --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e0433efc1..1781e8651 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,6 +13,6 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.x - - run: pip install mkdocs-material - - run: pip install mkdocs-git-revision-date-localized-plugin + - run: pip3 install mkdocs-material + - run: pip3 install mkdocs-git-revision-date-localized-plugin - run: mkdocs gh-deploy --force From 9376575098783d0c5c31e4c70a8b53eef7c6d8e1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 29 Sep 2023 05:34:18 +0900 Subject: [PATCH 355/401] docs: change dotenv to text highlight.js does not support dotenv. --- docs/addons/jwt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index c2af69ad4..bd852f5a1 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -107,7 +107,7 @@ The default claims will be included in all tokens issued by Shield. Set your secret key in the `$keys` property, or set it in your `.env` file. E.g.: -```dotenv +```text authjwt.keys.default.0.secret = 8XBFsF6HThIa7OV/bSynahEch+WbKrGcuiJVYPiwqPE= ``` From 16fb1e4c4121885223452717e9cacd179f6471fa Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Sep 2023 10:47:09 +0900 Subject: [PATCH 356/401] docs: add "How to Build Shield Documentation" --- admin/how_to_build_docs.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 admin/how_to_build_docs.md diff --git a/admin/how_to_build_docs.md b/admin/how_to_build_docs.md new file mode 100644 index 000000000..8a84cfee7 --- /dev/null +++ b/admin/how_to_build_docs.md @@ -0,0 +1,38 @@ +# How to Build Shield Documentation + +We use Material for MkDocs for our documentation. +See https://squidfunk.github.io/mkdocs-material/getting-started/. + +## Requirements + +- Python3 +- pip + +## Installation + +```console +pip3 install mkdocs +pip3 install mkdocs-material +pip3 install mkdocs-git-revision-date-localized-plugin +``` + +## Build the Docs + +```consolse +mkdocs build +``` + +## See the Docs + +```consolse +mkdocs serve +``` + +## Deploy Manually + +The documentation is built and deployed automatically by GitHub Actions. But if +you need to deploy manually, run the following command: + +```console +mkdocs gh-deploy +``` From c5a4b4575536e889fb4cbea23f997d35336214c6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:19:23 +0900 Subject: [PATCH 357/401] refactor: move `label` for validation rules to Config\Auth --- src/Config/Auth.php | 20 ++++++++++------ src/Controllers/LoginController.php | 10 ++------ src/Controllers/MagicLinkController.php | 5 +--- .../RegistrationValidationRules.php | 23 +++++++++---------- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index c5e15b035..fc1dd7b86 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -206,10 +206,13 @@ class Auth extends BaseConfig * @var string[] */ public array $usernameValidationRules = [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + 'label' => 'Auth.username', + 'rules' => [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + ], ]; /** @@ -220,9 +223,12 @@ class Auth extends BaseConfig * @var string[] */ public array $emailValidationRules = [ - 'required', - 'max_length[254]', - 'valid_email', + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email', + ], ]; /** diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index ba7efe56f..d9306e85c 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -83,14 +83,8 @@ public function loginAction(): RedirectResponse protected function getValidationRules(): array { return setting('Validation.login') ?? [ - // 'username' => [ - // 'label' => 'Auth.username', - // 'rules' => config('Auth')->usernameValidationRules, - // ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => config('Auth')->emailValidationRules, - ], + // 'username' => config('Auth')->usernameValidationRules, + 'email' => config('Auth')->emailValidationRules, 'password' => [ 'label' => 'Auth.password', 'rules' => 'required|' . Passwords::getMaxLengthRule(), diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index 45ac55a43..ebdeda0ca 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -234,10 +234,7 @@ private function recordLoginAttempt( protected function getValidationRules(): array { return [ - 'email' => [ - 'label' => 'Auth.email', - 'rules' => config('Auth')->emailValidationRules, - ], + 'email' => config('Auth')->emailValidationRules, ]; } } diff --git a/src/Validation/RegistrationValidationRules.php b/src/Validation/RegistrationValidationRules.php index ca2c5a44f..9200fbfe1 100644 --- a/src/Validation/RegistrationValidationRules.php +++ b/src/Validation/RegistrationValidationRules.php @@ -23,26 +23,25 @@ public function __construct() public function get(): array { - $registrationUsernameRules = array_merge( - config('Auth')->usernameValidationRules, + $config = config('Auth'); + + $usernameValidationRules = $config->usernameValidationRules; + $emailValidationRules = $config->emailValidationRules; + + $usernameValidationRules['rules'] = array_merge( + $usernameValidationRules['rules'], [sprintf('is_unique[%s.username]', $this->tables['users'])] ); - $registrationEmailRules = array_merge( - config('Auth')->emailValidationRules, + $emailValidationRules['rules'] = array_merge( + $emailValidationRules['rules'], [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); helper('setting'); return setting('Validation.registration') ?? [ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => $registrationUsernameRules, - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => $registrationEmailRules, - ], + 'username' => $usernameValidationRules, + 'email' => $emailValidationRules, 'password' => [ 'label' => 'Auth.password', 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', From 84df605b20dc33bbf966e007aea7ba9fce0361f1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:22:06 +0900 Subject: [PATCH 358/401] refactor: rename classname and method name --- src/Commands/User.php | 6 +++--- src/Controllers/RegisterController.php | 6 +++--- ...{RegistrationValidationRules.php => ValidationRules.php} | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/Validation/{RegistrationValidationRules.php => ValidationRules.php} (95%) diff --git a/src/Commands/User.php b/src/Commands/User.php index 9a2b39c1f..4a2d1b1e1 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -13,7 +13,7 @@ use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\UserNotFoundException; use CodeIgniter\Shield\Models\UserModel; -use CodeIgniter\Shield\Validation\RegistrationValidationRules; +use CodeIgniter\Shield\Validation\ValidationRules; use Config\Services; class User extends BaseCommand @@ -219,9 +219,9 @@ private function setTables(): void private function setValidationRules(): void { - $validationRules = new RegistrationValidationRules(); + $validationRules = new ValidationRules(); - $rules = $validationRules->get(); + $rules = $validationRules->getRegistrationRules(); // Remove `strong_password` because it only supports use cases // to check the user's own password. diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index 0e49e3851..e0eea5014 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -14,7 +14,7 @@ use CodeIgniter\Shield\Exceptions\ValidationException; use CodeIgniter\Shield\Models\UserModel; use CodeIgniter\Shield\Traits\Viewable; -use CodeIgniter\Shield\Validation\RegistrationValidationRules; +use CodeIgniter\Shield\Validation\ValidationRules; use Psr\Log\LoggerInterface; /** @@ -167,8 +167,8 @@ protected function getUserEntity(): User */ protected function getValidationRules(): array { - $rules = new RegistrationValidationRules(); + $rules = new ValidationRules(); - return $rules->get(); + return $rules->getRegistrationRules(); } } diff --git a/src/Validation/RegistrationValidationRules.php b/src/Validation/ValidationRules.php similarity index 95% rename from src/Validation/RegistrationValidationRules.php rename to src/Validation/ValidationRules.php index 9200fbfe1..66da07714 100644 --- a/src/Validation/RegistrationValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -7,7 +7,7 @@ use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Config\Auth; -class RegistrationValidationRules +class ValidationRules { /** * Auth Table names @@ -21,7 +21,7 @@ public function __construct() $this->tables = $authConfig->tables; } - public function get(): array + public function getRegistrationRules(): array { $config = config('Auth'); From cc481f5e9c950774f40d9a3496c519c74337d6fc Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:26:17 +0900 Subject: [PATCH 359/401] refactor: extract methods for password fields --- src/Validation/ValidationRules.php | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index 66da07714..5794373a8 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -40,19 +40,29 @@ public function getRegistrationRules(): array helper('setting'); return setting('Validation.registration') ?? [ - 'username' => $usernameValidationRules, - 'email' => $emailValidationRules, - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ], - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', + 'username' => $usernameValidationRules, + 'email' => $emailValidationRules, + 'password' => $this->getPasswordRules(), + 'password_confirm' => $this->getPasswordConfirmRules(), + ]; + } + + public function getPasswordRules(): array + { + return [ + 'label' => 'Auth.password', + 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], ]; } + + public function getPasswordConfirmRules(): array + { + return [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ]; + } } From 82fe260de811eb2b7e35762b611641a6e169b88a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:31:34 +0900 Subject: [PATCH 360/401] refactor: add private property --- src/Validation/ValidationRules.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index 5794373a8..91d68a46a 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -9,6 +9,8 @@ class ValidationRules { + private Auth $config; + /** * Auth Table names */ @@ -17,16 +19,16 @@ class ValidationRules public function __construct() { /** @var Auth $authConfig */ - $authConfig = config('Auth'); - $this->tables = $authConfig->tables; + $authConfig = config('Auth'); + + $this->config = $authConfig; + $this->tables = $this->config->tables; } public function getRegistrationRules(): array { - $config = config('Auth'); - - $usernameValidationRules = $config->usernameValidationRules; - $emailValidationRules = $config->emailValidationRules; + $usernameValidationRules = $this->config->usernameValidationRules; + $emailValidationRules = $this->config->emailValidationRules; $usernameValidationRules['rules'] = array_merge( $usernameValidationRules['rules'], From d1c065805772afe1657fd7466503c544e2782cd9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:38:02 +0900 Subject: [PATCH 361/401] refactor: move validation rules to ValidationRules --- src/Controllers/LoginController.php | 16 ++++------------ src/Validation/ValidationRules.php | 11 +++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index d9306e85c..e38fa3817 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -7,8 +7,8 @@ use App\Controllers\BaseController; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\Shield\Authentication\Authenticators\Session; -use CodeIgniter\Shield\Authentication\Passwords; use CodeIgniter\Shield\Traits\Viewable; +use CodeIgniter\Shield\Validation\ValidationRules; class LoginController extends BaseController { @@ -82,17 +82,9 @@ public function loginAction(): RedirectResponse */ protected function getValidationRules(): array { - return setting('Validation.login') ?? [ - // 'username' => config('Auth')->usernameValidationRules, - 'email' => config('Auth')->emailValidationRules, - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule(), - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ], - ], - ]; + $rules = new ValidationRules(); + + return $rules->getLoginRules(); } /** diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index 91d68a46a..d85949a4a 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -49,6 +49,17 @@ public function getRegistrationRules(): array ]; } + public function getLoginRules(): array + { + helper('setting'); + + return setting('Validation.login') ?? [ + // 'username' => $this->config->usernameValidationRules, + 'email' => $this->config->emailValidationRules, + 'password' => $this->getPasswordRules(), + ]; + } + public function getPasswordRules(): array { return [ From e25fe078840ce91f0857f7d2c57a5c3f207bc818 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:45:02 +0900 Subject: [PATCH 362/401] refactor: if setting returns values, returns it --- src/Validation/ValidationRules.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index d85949a4a..20d0a5117 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -27,6 +27,13 @@ public function __construct() public function getRegistrationRules(): array { + helper('setting'); + + $setting = setting('Validation.registration'); + if ($setting !== null) { + return $setting; + } + $usernameValidationRules = $this->config->usernameValidationRules; $emailValidationRules = $this->config->emailValidationRules; @@ -39,9 +46,7 @@ public function getRegistrationRules(): array [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); - helper('setting'); - - return setting('Validation.registration') ?? [ + return [ 'username' => $usernameValidationRules, 'email' => $emailValidationRules, 'password' => $this->getPasswordRules(), From 95ecdc1f227b31b85e9fc414b02252dc558b3b9d Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 16:53:38 +0900 Subject: [PATCH 363/401] docs: update doc comments --- src/Config/Auth.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Config/Auth.php b/src/Config/Auth.php index fc1dd7b86..e7ad04189 100644 --- a/src/Config/Auth.php +++ b/src/Config/Auth.php @@ -203,7 +203,9 @@ class Auth extends BaseConfig * The validation rules for username * -------------------------------------------------------------------- * - * @var string[] + * Do not use string rules like `required|valid_email`. + * + * @var array|string> */ public array $usernameValidationRules = [ 'label' => 'Auth.username', @@ -220,7 +222,9 @@ class Auth extends BaseConfig * The validation rules for email * -------------------------------------------------------------------- * - * @var string[] + * Do not use string rules like `required|valid_email`. + * + * @var array|string> */ public array $emailValidationRules = [ 'label' => 'Auth.email', From 114cfba7beeffd7acef5356f2a7073eaeea7f180 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 17:30:20 +0900 Subject: [PATCH 364/401] fix: wrong password validation rules for login --- src/Validation/ValidationRules.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index 20d0a5117..8612d0c65 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -46,10 +46,13 @@ public function getRegistrationRules(): array [sprintf('is_unique[%s.secret]', $this->tables['identities'])] ); + $passwordRules = $this->getPasswordRules(); + $passwordRules['rules'][] = 'strong_password[]'; + return [ 'username' => $usernameValidationRules, 'email' => $emailValidationRules, - 'password' => $this->getPasswordRules(), + 'password' => $passwordRules, 'password_confirm' => $this->getPasswordConfirmRules(), ]; } @@ -69,7 +72,7 @@ public function getPasswordRules(): array { return [ 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule() . '|strong_password[]', + 'rules' => ['required', Passwords::getMaxLengthRule()], 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ], From 76f8c977644c7fd38159ea1559f481bf3f9d92c6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 17:33:22 +0900 Subject: [PATCH 365/401] refactor: do not use array_merge() --- src/Validation/ValidationRules.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index 8612d0c65..c0827bc79 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -34,16 +34,16 @@ public function getRegistrationRules(): array return $setting; } - $usernameValidationRules = $this->config->usernameValidationRules; - $emailValidationRules = $this->config->emailValidationRules; - - $usernameValidationRules['rules'] = array_merge( - $usernameValidationRules['rules'], - [sprintf('is_unique[%s.username]', $this->tables['users'])] + $usernameValidationRules = $this->config->usernameValidationRules; + $usernameValidationRules['rules'][] = sprintf( + 'is_unique[%s.username]', + $this->tables['users'] ); - $emailValidationRules['rules'] = array_merge( - $emailValidationRules['rules'], - [sprintf('is_unique[%s.secret]', $this->tables['identities'])] + + $emailValidationRules = $this->config->emailValidationRules; + $emailValidationRules['rules'][] = sprintf( + 'is_unique[%s.secret]', + $this->tables['identities'] ); $passwordRules = $this->getPasswordRules(); From b080a71b0bbf876e3eaa09cf7ff6a40de555289b Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 27 Sep 2023 17:37:20 +0900 Subject: [PATCH 366/401] refactor: shorten variable names --- src/Validation/ValidationRules.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index c0827bc79..c0d8da438 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -34,14 +34,14 @@ public function getRegistrationRules(): array return $setting; } - $usernameValidationRules = $this->config->usernameValidationRules; - $usernameValidationRules['rules'][] = sprintf( + $usernameRules = $this->config->usernameValidationRules; + $usernameRules['rules'][] = sprintf( 'is_unique[%s.username]', $this->tables['users'] ); - $emailValidationRules = $this->config->emailValidationRules; - $emailValidationRules['rules'][] = sprintf( + $emailRules = $this->config->emailValidationRules; + $emailRules['rules'][] = sprintf( 'is_unique[%s.secret]', $this->tables['identities'] ); @@ -50,8 +50,8 @@ public function getRegistrationRules(): array $passwordRules['rules'][] = 'strong_password[]'; return [ - 'username' => $usernameValidationRules, - 'email' => $emailValidationRules, + 'username' => $usernameRules, + 'email' => $emailRules, 'password' => $passwordRules, 'password_confirm' => $this->getPasswordConfirmRules(), ]; From 8badb41bc7752445c285e6910a0eb1b027d1dd25 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 30 Sep 2023 15:59:59 +0900 Subject: [PATCH 367/401] docs: update default validation rules --- docs/customization/validation_rules.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/customization/validation_rules.md b/docs/customization/validation_rules.md index 7e9a7afdc..074009d23 100644 --- a/docs/customization/validation_rules.md +++ b/docs/customization/validation_rules.md @@ -27,7 +27,11 @@ Shield has the following rules for registration by default: ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|max_byte[72]|strong_password[]', + 'rules' => [ + 'required', + 'max_byte[72]', + 'strong_password[]', + ], 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes' ] @@ -98,8 +102,13 @@ Similar to the process for validation rules in the **Registration** section, you //-------------------------------------------------------------------- public $login = [ // 'username' => [ - // 'label' => 'Auth.username', - // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', + // 'label' => 'Auth.username', + // 'rules' => [ + // 'required', + // 'max_length[30]', + // 'min_length[3]', + // 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + // ], // ], 'email' => [ 'label' => 'Auth.email', @@ -111,7 +120,10 @@ public $login = [ ], 'password' => [ 'label' => 'Auth.password', - 'rules' => 'required|max_byte[72]', + 'rules' => [ + 'required', + 'max_byte[72]', + ], 'errors' => [ 'max_byte' => 'Auth.errorPasswordTooLongBytes', ] From fafa9a99faa478b713a64255214e533a1d0e4b0d Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 30 Sep 2023 15:43:05 +0100 Subject: [PATCH 368/401] changed the visibility of properties for easier inheritance --- src/Validation/ValidationRules.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Validation/ValidationRules.php b/src/Validation/ValidationRules.php index c0d8da438..33ab171f2 100644 --- a/src/Validation/ValidationRules.php +++ b/src/Validation/ValidationRules.php @@ -9,12 +9,12 @@ class ValidationRules { - private Auth $config; + protected Auth $config; /** * Auth Table names */ - private array $tables; + protected array $tables; public function __construct() { From 164bce403e10c5fccc5a7ca148b4b8193c8fa9c9 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 30 Sep 2023 20:52:55 +0100 Subject: [PATCH 369/401] redirect inactive account to auth action --- src/Filters/SessionAuth.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Filters/SessionAuth.php b/src/Filters/SessionAuth.php index 6a8a0d8fe..b7d449666 100644 --- a/src/Filters/SessionAuth.php +++ b/src/Filters/SessionAuth.php @@ -61,10 +61,12 @@ public function before(RequestInterface $request, $arguments = null) } if ($user !== null && ! $user->isActivated()) { - $authenticator->logout(); - - return redirect()->route('login') - ->with('error', lang('Auth.activationBlocked')); + // If an action has been defined for register, start it up. + $hasAction = $authenticator->startUpAction('register', $user); + if ($hasAction) { + return redirect()->route('auth-action-show') + ->with('error', lang('Auth.activationBlocked')); + } } return; From 91c00259bbe0ea9e205da88e946efed6011943b1 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sat, 30 Sep 2023 20:53:55 +0100 Subject: [PATCH 370/401] added tests --- tests/Authentication/Filters/AbstractFilterTestCase.php | 1 + tests/Authentication/Filters/SessionFilterTest.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Authentication/Filters/AbstractFilterTestCase.php b/tests/Authentication/Filters/AbstractFilterTestCase.php index 23ece0925..d44f9e931 100644 --- a/tests/Authentication/Filters/AbstractFilterTestCase.php +++ b/tests/Authentication/Filters/AbstractFilterTestCase.php @@ -69,6 +69,7 @@ static function ($routes): void { echo 'Open'; }); $routes->get('login', 'AuthController::login', ['as' => 'login']); + $routes->get('auth/a/show', 'AuthActionController::show', ['as' => 'auth-action-show']); $routes->get('protected-user-route', static function (): void { echo 'Protected'; }, ['filter' => $this->alias . ':users-read']); diff --git a/tests/Authentication/Filters/SessionFilterTest.php b/tests/Authentication/Filters/SessionFilterTest.php index 1b379ccc3..a217d7a74 100644 --- a/tests/Authentication/Filters/SessionFilterTest.php +++ b/tests/Authentication/Filters/SessionFilterTest.php @@ -58,7 +58,7 @@ public function testRecordActiveDate(): void $this->assertGreaterThan(auth('session')->user()->updated_at, auth('session')->user()->last_active); } - public function testBlocksInactiveUsers(): void + public function testBlocksInactiveUsersAndRedirectsToAuthAction(): void { $user = fake(UserModel::class, ['active' => false]); @@ -77,7 +77,7 @@ public function testBlocksInactiveUsers(): void $result = $this->actingAs($user) ->get('protected-route'); - $result->assertRedirectTo('/login'); + $result->assertRedirectTo('/auth/a/show'); // User should be logged out $this->assertNull(auth('session')->id()); From 679a56900b2640b67b3f81fef283be5485bca6ba Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 1 Oct 2023 09:54:57 +0900 Subject: [PATCH 371/401] docs: remove config method in .env It is not recommended. --- docs/getting_started/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index cc37f52fb..3d9c4442a 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -136,7 +136,7 @@ your project. service('auth')->routes($routes); ``` -4. **Security Setup** Set `Config\Security::$csrfProtection` to `'session'` (or set `security.csrfProtection = session` in your **.env** file) for security reasons, if you use Session Authenticator. +4. **Security Setup** Set `Config\Security::$csrfProtection` to `'session'` for security reasons, if you use Session Authenticator. 5. **Migration** Run the migrations. From fe04c11e1ea51d601ceb245fd39dc891093d1b2d Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 2 Oct 2023 11:20:03 +0900 Subject: [PATCH 372/401] refactor: remove Composer autoloading of email helper The Email helper is used in a limited number of places, and it would be inefficient to load it on every page. --- composer.json | 3 +-- src/Authentication/Actions/Email2FA.php | 1 + src/Authentication/Actions/EmailActivator.php | 1 + src/Controllers/MagicLinkController.php | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 95127ab00..773526456 100644 --- a/composer.json +++ b/composer.json @@ -53,8 +53,7 @@ "CodeIgniter\\Shield\\": "src" }, "files": [ - "src/Helpers/auth_helper.php", - "src/Helpers/email_helper.php" + "src/Helpers/auth_helper.php" ], "exclude-from-classmap": [ "**/Database/Migrations/**" diff --git a/src/Authentication/Actions/Email2FA.php b/src/Authentication/Actions/Email2FA.php index 52827c99a..54883b7be 100644 --- a/src/Authentication/Actions/Email2FA.php +++ b/src/Authentication/Actions/Email2FA.php @@ -78,6 +78,7 @@ public function handle(IncomingRequest $request) $date = Time::now()->toDateTimeString(); // Send the user an email with the code + helper('email'); $email = emailer()->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? ''); $email->setTo($user->email); $email->setSubject(lang('Auth.email2FASubject')); diff --git a/src/Authentication/Actions/EmailActivator.php b/src/Authentication/Actions/EmailActivator.php index 8e5216074..5017798f0 100644 --- a/src/Authentication/Actions/EmailActivator.php +++ b/src/Authentication/Actions/EmailActivator.php @@ -55,6 +55,7 @@ public function show(): string $date = Time::now()->toDateTimeString(); // Send the email + helper('email'); $email = emailer()->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? ''); $email->setTo($userEmail); $email->setSubject(lang('Auth.emailActivateSubject')); diff --git a/src/Controllers/MagicLinkController.php b/src/Controllers/MagicLinkController.php index ebdeda0ca..25ab09d99 100644 --- a/src/Controllers/MagicLinkController.php +++ b/src/Controllers/MagicLinkController.php @@ -113,6 +113,7 @@ public function loginAction() $date = Time::now()->toDateTimeString(); // Send the user an email with the code + helper('email'); $email = emailer()->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? ''); $email->setTo($user->email); $email->setSubject(lang('Auth.magicLinkSubject')); From a8b48ca353fc9e7a9c4fdf0884a1a951a8844a4d Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 2 Oct 2023 16:53:48 +0900 Subject: [PATCH 373/401] docs: update sample LoginController --- docs/addons/jwt.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index f26067cb7..ad87cfb4e 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -150,8 +150,7 @@ use CodeIgniter\API\ResponseTrait; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\JWTManager; -use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\AuthSession; +use CodeIgniter\Shield\Validation\ValidationRules; class LoginController extends BaseController { @@ -215,19 +214,9 @@ class LoginController extends BaseController */ protected function getValidationRules(): array { - return setting('Validation.login') ?? [ - 'email' => [ - 'label' => 'Auth.email', - 'rules' => config('Auth')->emailValidationRules, - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLengthRule(), - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ], - ], - ]; + $rules = new ValidationRules(); + + return $rules->getLoginRules(); } } ``` From 4a25453dbf18ccefe83d2dbc8d332b1c0545e3d3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 2 Oct 2023 10:50:22 +0900 Subject: [PATCH 374/401] chore: update minimum CI4 version to 4.3.5 --- README.md | 2 +- UPGRADING.md | 6 ++++++ composer.json | 2 +- docs/getting_started/install.md | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2aeab4954..8df491a39 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ See the [An Official Auth Library](https://codeigniter.com/news/shield) for more Usage of Shield requires the following: -- A [CodeIgniter 4.2.7+](https://github.com/codeigniter4/CodeIgniter4/) based project +- A [CodeIgniter 4.3.5+](https://github.com/codeigniter4/CodeIgniter4/) based project - [Composer](https://getcomposer.org/) for package management - PHP 7.4.3+ diff --git a/UPGRADING.md b/UPGRADING.md index 4b6b32480..1a6bcd5cd 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,12 @@ ## Version 1.0.0-beta.6 to 1.0.0-beta.7 +### The minimum CodeIgniter version + +Shield requires CodeIgniter 4.3.5 or later. +Versions prior to 4.3.5 have known vulnerabilities. +See https://github.com/codeigniter4/CodeIgniter4/security/advisories + ### Mandatory Config Changes #### New Config\AuthToken diff --git a/composer.json b/composer.json index 773526456..f1cce5feb 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require-dev": { "codeigniter/phpstan-codeigniter": "^1.3", "codeigniter4/devkit": "^1.0", - "codeigniter4/framework": "^4.2.7", + "codeigniter4/framework": "^4.3.5", "firebase/php-jwt": "^6.4", "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index 3d9c4442a..a8ec9d8d9 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -5,7 +5,7 @@ These instructions assume that you have already [installed the CodeIgniter 4 app ## Requirements - [Composer](https://getcomposer.org) -- Codeigniter **v4.2.7** or later +- Codeigniter **v4.3.5** or later - A created database that you can access via the Spark CLI script - InnoDB (not MyISAM) is required if MySQL is used. From 4c381f47ec5d69708ebb039993ba5b0b33255fa7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 07:50:12 +0900 Subject: [PATCH 375/401] refactor: remove code for 4.2.x or before --- src/Test/MockInputOutput.php | 41 ++++++++--------------- tests/Commands/SetupTest.php | 20 +++-------- tests/Commands/UserModelGeneratorTest.php | 24 +++---------- tests/Controllers/LoginTest.php | 13 ++----- 4 files changed, 25 insertions(+), 73 deletions(-) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 6829e25b5..8d268964a 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -5,7 +5,6 @@ namespace CodeIgniter\Shield\Test; use CodeIgniter\CLI\CLI; -use CodeIgniter\CodeIgniter; use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Exceptions\LogicException; use CodeIgniter\Test\Filters\CITestStreamFilter; @@ -52,23 +51,21 @@ public function prompt(string $field, $options = null, $validation = null): stri { $input = array_shift($this->inputs); - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); - PhpStreamWrapper::register(); - PhpStreamWrapper::setContent($input); + PhpStreamWrapper::register(); + PhpStreamWrapper::setContent($input); - $userInput = CLI::prompt($field, $options, $validation); + $userInput = CLI::prompt($field, $options, $validation); - PhpStreamWrapper::restore(); + PhpStreamWrapper::restore(); - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); - if ($input !== $userInput) { - throw new LogicException($input . '!==' . $userInput); - } + if ($input !== $userInput) { + throw new LogicException($input . '!==' . $userInput); } return $input; @@ -79,23 +76,13 @@ public function write( ?string $foreground = null, ?string $background = null ): void { - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); - } else { - CITestStreamFilter::$buffer = ''; - - $streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); - } + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); CLI::write($text, $foreground, $background); $this->outputs[] = CITestStreamFilter::$buffer; - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); - } else { - stream_filter_remove($streamFilter); - } + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); } } diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 9a4281715..3333fac2e 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -4,7 +4,6 @@ namespace Tests\Commands; -use CodeIgniter\CodeIgniter; use CodeIgniter\Shield\Commands\Setup; use CodeIgniter\Test\Filters\CITestStreamFilter; use Config\Services; @@ -16,31 +15,20 @@ */ final class SetupTest extends TestCase { - private $streamFilter; - protected function setUp(): void { parent::setUp(); - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); - } else { - CITestStreamFilter::$buffer = ''; - $this->streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); - } + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); } protected function tearDown(): void { parent::tearDown(); - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); - } else { - stream_filter_remove($this->streamFilter); - } + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); } public function testRun(): void diff --git a/tests/Commands/UserModelGeneratorTest.php b/tests/Commands/UserModelGeneratorTest.php index 41c539593..7b8ebcb3e 100644 --- a/tests/Commands/UserModelGeneratorTest.php +++ b/tests/Commands/UserModelGeneratorTest.php @@ -4,7 +4,6 @@ namespace Tests\Commands; -use CodeIgniter\CodeIgniter; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Filters\CITestStreamFilter; @@ -13,22 +12,13 @@ */ final class UserModelGeneratorTest extends CIUnitTestCase { - private $streamFilter; - protected function setUp(): void { parent::setUp(); - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); - CITestStreamFilter::addErrorFilter(); - } else { - CITestStreamFilter::$buffer = ''; - - $this->streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); - $this->streamFilter = stream_filter_append(STDERR, 'CITestStreamFilter'); - } + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + CITestStreamFilter::addErrorFilter(); if (is_file(HOMEPATH . 'src/Models/UserModel.php')) { copy(HOMEPATH . 'src/Models/UserModel.php', HOMEPATH . 'src/Models/UserModel.php.bak'); @@ -41,12 +31,8 @@ protected function tearDown(): void { parent::tearDown(); - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); - } else { - stream_filter_remove($this->streamFilter); - } + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); $this->deleteTestFiles(); diff --git a/tests/Controllers/LoginTest.php b/tests/Controllers/LoginTest.php index f813568c4..f4c7bc5f7 100644 --- a/tests/Controllers/LoginTest.php +++ b/tests/Controllers/LoginTest.php @@ -4,7 +4,6 @@ namespace Tests\Controllers; -use CodeIgniter\CodeIgniter; use CodeIgniter\Config\Factories; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Actions\Email2FA; @@ -110,11 +109,7 @@ public function testLoginTooLongPasswordArgon2id(): void public function testLoginActionEmailSuccess(): void { - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - Time::setTestNow('March 10, 2017', 'UTC'); - } else { - Time::setTestNow('March 10, 2017', 'America/Chicago'); - } + Time::setTestNow('March 10, 2017', 'UTC'); $this->user->createEmailIdentity([ 'email' => 'foo@example.com', @@ -166,11 +161,7 @@ public function testAfterLoggedInNotDisplayLoginPage(): void public function testLoginActionUsernameSuccess(): void { - if (version_compare(CodeIgniter::CI_VERSION, '4.3.0', '>=')) { - Time::setTestNow('March 10, 2017', 'UTC'); - } else { - Time::setTestNow('March 10, 2017', 'America/Chicago'); - } + Time::setTestNow('March 10, 2017', 'UTC'); // Add 'username' to $validFields $authConfig = config('Auth'); From 0b05a96d937f52388d56f40dfd6456ed8b04210a Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 09:07:22 +0900 Subject: [PATCH 376/401] chore: configure page redirects --- .github/workflows/docs.yml | 1 + admin/how_to_build_docs.md | 1 + mkdocs.yml | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1781e8651..e6e623ce8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,4 +15,5 @@ jobs: python-version: 3.x - run: pip3 install mkdocs-material - run: pip3 install mkdocs-git-revision-date-localized-plugin + - run: pip3 install mkdocs-redirects - run: mkdocs gh-deploy --force diff --git a/admin/how_to_build_docs.md b/admin/how_to_build_docs.md index 8a84cfee7..9162fd73b 100644 --- a/admin/how_to_build_docs.md +++ b/admin/how_to_build_docs.md @@ -14,6 +14,7 @@ See https://squidfunk.github.io/mkdocs-material/getting-started/. pip3 install mkdocs pip3 install mkdocs-material pip3 install mkdocs-git-revision-date-localized-plugin +pip3 install mkdocs-redirects ``` ## Build the Docs diff --git a/mkdocs.yml b/mkdocs.yml index ed02dc125..64917aeef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,20 @@ plugins: - search - git-revision-date-localized: enable_creation_date: true + - redirects: + redirect_maps: + 'auth_actions.md': 'references/authentication/auth_actions.md' + 'authentication.md': 'references/authentication/authentication.md' + 'authorization.md': 'references/authorization.md' + 'banning_users.md': 'user_management/banning_users.md' + 'concepts.md': 'getting_started/concepts.md' + 'customization.md': 'customization/table_names.md' + 'events.md': 'references/events.md' + 'forcing_password_reset.md': 'user_management/forcing_password_reset.md' + 'install.md': 'getting_started/install.md' + 'quickstart.md': 'quick_start_guide/using_session_auth.md' + 'session_auth_event_and_logging.md': 'references/authentication/session.md' + 'testing.md': 'references/testing.md' nav: - Home: index.md - Getting Started: From 9dee01ebe190d654845901b6b6af425e6f13ac52 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 09:56:21 +0900 Subject: [PATCH 377/401] docs: add emojis --- docs/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/index.md b/docs/index.md index ee28fc5a8..fa0e40304 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,12 @@ # Shield Documentation -## What is Shield? +## What is Shield? 🤔 Shield is the official authentication and authorization framework for CodeIgniter 4. While it does provide a base set of tools that are commonly used in websites, it is designed to be flexible and easily customizable. -### Primary Goals +### Primary Goals 🥅 The primary goals for Shield are: @@ -14,7 +14,7 @@ The primary goals for Shield are: 2. It must have security at its core. It is an auth lib after all. 3. To cover many auth needs right out of the box, but be simple to add additional functionality to. -### Important Features +### Important Features 🌠 * Session-based authentication (traditional ID/Password with Remember-me) * Stateless authentication using Personal Access Tokens @@ -24,11 +24,11 @@ The primary goals for Shield are: * Flexible Groups-based access control (think Roles, but more flexible) * Users can be granted additional Permissions -### License +### License 📑 Shield is licensed under the MIT License - see the [LICENSE](https://github.com/codeigniter4/shield/blob/develop/LICENSE) file for details. -### Acknowledgements +### Acknowledgements 🙌🏼 Every open-source project depends on it's contributors to be a success. The following users have contributed in one manner or another in making Shield: From c0cc2667d48983ea6ee1e4a9d394319eb6d3f655 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 09:56:35 +0900 Subject: [PATCH 378/401] docs: add features Based on https://forum.codeigniter.com/showthread.php?tid=82003 --- docs/index.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/index.md b/docs/index.md index fa0e40304..dc4865884 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,13 +16,19 @@ The primary goals for Shield are: ### Important Features 🌠 -* Session-based authentication (traditional ID/Password with Remember-me) -* Stateless authentication using Personal Access Tokens -* Optional Email verification on account registration -* Optional Email-based Two-Factor Authentication after login -* Magic Link Login when a user forgets their password -* Flexible Groups-based access control (think Roles, but more flexible) -* Users can be granted additional Permissions +* **Session-based Authentication** (traditional **ID/Password** with **Remember-me**) +* **Stateless Authentication** using **Access Token**, **HMAC SHA256 Token**, or **JWT** +* Optional **Email verification** on account registration +* Optional **Email-based Two-Factor Authentication** after login +* **Magic Link Login** when a user forgets their password +* Flexible **Group-based Access Control** (think Roles, but more flexible), and users can be granted additional **Permissions** +* A simple **Auth Helper** that provides access to the most common auth actions +* Save initial settings in your code, so it can be in version control, but can also be updated in the database, thanks to our [Settings](https://github.com/codeigniter4/settings) library +* Highly configurable +* **User Entity** and **User Provider** (`UserModel`) ready for you to use or extend +* Built to extend and modify + * Easily extendable controllers + * All required views that can be used as is or swapped out for your own ### License 📑 From 86fe095f0070fcd6d3e0b4f7d4bb1f88667ef179 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 13:29:13 +0900 Subject: [PATCH 379/401] feat: add Config\Email setup --- src/Commands/Setup.php | 130 ++++++++++++++++++++++++++++++++--- tests/Commands/SetupTest.php | 49 ++++++++----- 2 files changed, 150 insertions(+), 29 deletions(-) diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index ce052b714..32c931ac6 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -6,12 +6,18 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use CodeIgniter\CodeIgniter; use CodeIgniter\Commands\Database\Migrate; use CodeIgniter\Shield\Commands\Setup\ContentReplacer; +use CodeIgniter\Shield\Commands\Utils\InputOutput; +use CodeIgniter\Test\Filters\CITestStreamFilter; +use Config\Email as EmailConfig; use Config\Services; class Setup extends BaseCommand { + private static ?InputOutput $io = null; + /** * The group the command is lumped under * when listing commands. @@ -89,6 +95,7 @@ private function publishConfig(): void $this->setupRoutes(); $this->setSecurityCSRF(); + $this->setupEmail(); $this->runMigrations(); } @@ -166,7 +173,7 @@ protected function writeFile(string $file, string $content): void if ( ! $overwrite - && CLI::prompt(" File '{$cleanPath}' already exists in destination. Overwrite?", ['n', 'y']) === 'n' + && $this->prompt(" File '{$cleanPath}' already exists in destination. Overwrite?", ['n', 'y']) === 'n' ) { CLI::error(" Skipped {$cleanPath}. If you wish to overwrite, please use the '-f' option or reply 'y' to the prompt."); @@ -175,7 +182,7 @@ protected function writeFile(string $file, string $content): void } if (write_file($path, $content)) { - CLI::write(CLI::color(' Created: ', 'green') . $cleanPath); + $this->write(CLI::color(' Created: ', 'green') . $cleanPath); } else { CLI::error(" Error creating {$cleanPath}."); } @@ -206,7 +213,7 @@ protected function add(string $file, string $code, string $pattern, string $repl } if (write_file($path, $output)) { - CLI::write(CLI::color(' Updated: ', 'green') . $cleanPath); + $this->write(CLI::color(' Updated: ', 'green') . $cleanPath); } else { CLI::error(" Error updating {$cleanPath}."); } @@ -232,7 +239,7 @@ private function replace(string $file, array $replaces): bool } if (write_file($path, $output)) { - CLI::write(CLI::color(' Updated: ', 'green') . $cleanPath); + $this->write(CLI::color(' Updated: ', 'green') . $cleanPath); return true; } @@ -297,13 +304,71 @@ private function setSecurityCSRF(): void // check $csrfProtection = 'session' if ($output === $content) { - CLI::write(CLI::color(' Security Setup: ', 'green') . 'Everything is fine.'); + $this->write(CLI::color(' Security Setup: ', 'green') . 'Everything is fine.'); return; } if (write_file($path, $output)) { - CLI::write(CLI::color(' Updated: ', 'green') . "We have updated file '{$cleanPath}' for security reasons."); + $this->write(CLI::color(' Updated: ', 'green') . "We have updated file '{$cleanPath}' for security reasons."); + } else { + CLI::error(" Error updating file '{$cleanPath}'."); + } + } + + private function setupEmail(): void + { + $file = 'Config/Email.php'; + + $path = $this->distPath . $file; + $cleanPath = clean_path($path); + + if (! is_file($path)) { + CLI::error(" Not found file '{$cleanPath}'."); + + return; + } + + $config = new EmailConfig(); + $fromEmail = $config->fromEmail; + $fromName = $config->fromName; + + if ($fromEmail !== '' && $fromName !== '') { + $this->write(CLI::color(' Email Setup: ', 'green') . 'Everything is fine.'); + + return; + } + + $content = file_get_contents($path); + $output = $content; + + if ($fromEmail === '') { + $set = $this->prompt(' The required Config\Email::$fromEmail is not set. Do you set now?', ['y', 'n']); + + if ($set === 'y') { + // Input from email + $fromEmail = $this->prompt(' What is your email?', null, 'required|valid_email'); + + $pattern = '/^ public .*\$fromEmail\s+= \'\';/mu'; + $replace = ' public string $fromEmail = \'' . $fromEmail . '\';'; + $output = preg_replace($pattern, $replace, $content); + } + } + + if ($fromName === '') { + $set = $this->prompt(' The required Config\Email::$fromName is not set. Do you set now?', ['y', 'n']); + + if ($set === 'y') { + $fromName = $this->prompt(' What is your name?', null, 'required'); + + $pattern = '/^ public .*\$fromName\s+= \'\';/mu'; + $replace = ' public string $fromName = \'' . $fromName . '\';'; + $output = preg_replace($pattern, $replace, $output); + } + } + + if (write_file($path, $output)) { + $this->write(CLI::color(' Updated: ', 'green') . $cleanPath); } else { CLI::error(" Error updating file '{$cleanPath}'."); } @@ -312,20 +377,65 @@ private function setSecurityCSRF(): void private function runMigrations(): void { if ( - $this->cliPrompt(' Run `spark migrate --all` now?', ['y', 'n']) === 'n' + $this->prompt(' Run `spark migrate --all` now?', ['y', 'n']) === 'n' ) { return; } $command = new Migrate(Services::logger(), Services::commands()); + + // This is a hack for testing. + // @TODO Remove CITestStreamFilter and refactor when CI 4.5.0 or later is supported. + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + CITestStreamFilter::addErrorFilter(); + $command->run(['all' => null]); + + CITestStreamFilter::removeOutputFilter(); + CITestStreamFilter::removeErrorFilter(); + + // Capture the output, and write for testing. + // @TODO Remove CITestStreamFilter and refactor when CI 4.5.0 or later is supported. + $output = CITestStreamFilter::$buffer; + $this->write($output); + + CITestStreamFilter::$buffer = ''; + } + + private function prompt(string $field, $options = null, $validation = null): string + { + return self::$io->prompt($field, $options, $validation); + } + + private function write( + string $text = '', + ?string $foreground = null, + ?string $background = null + ): void { + self::$io->write($text, $foreground, $background); + } + + private function ensureInputOutput(): void + { + if (self::$io === null) { + self::$io = new InputOutput(); + } + } + + /** + * @internal Testing purpose only + */ + public static function setInputOutput(InputOutput $io): void + { + self::$io = $io; } /** - * This method is for testing. + * @internal Testing purpose only */ - protected function cliPrompt(string $field, array $options): string + public static function resetInputOutput(): void { - return CLI::prompt($field, $options); + self::$io = null; } } diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 3333fac2e..bc26bfb72 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -5,7 +5,7 @@ namespace Tests\Commands; use CodeIgniter\Shield\Commands\Setup; -use CodeIgniter\Test\Filters\CITestStreamFilter; +use CodeIgniter\Shield\Test\MockInputOutput; use Config\Services; use org\bovigo\vfs\vfsStream; use Tests\Support\TestCase; @@ -15,24 +15,40 @@ */ final class SetupTest extends TestCase { - protected function setUp(): void - { - parent::setUp(); - - CITestStreamFilter::registration(); - CITestStreamFilter::addOutputFilter(); - } + private ?MockInputOutput $io = null; + private $streamFilter; protected function tearDown(): void { parent::tearDown(); - CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); + Setup::resetInputOutput(); + } + + /** + * Set MockInputOutput and user inputs. + * + * @param array $inputs User inputs + * @phpstan-param list $inputs + */ + private function setMockIo(array $inputs): void + { + $this->io = new MockInputOutput(); + $this->io->setInputs($inputs); + Setup::setInputOutput($this->io); } public function testRun(): void { + // Set MockIO and your inputs. + $this->setMockIo([ + 'y', + 'admin@example.com', + 'y', + 'Site Administrator', + 'y', + ]); + $root = vfsStream::setup('root'); vfsStream::copyFromFileSystem( APPPATH, @@ -40,13 +56,7 @@ public function testRun(): void ); $appFolder = $root->url() . '/'; - $command = $this->getMockBuilder(Setup::class) - ->setConstructorArgs([Services::logger(), Services::commands()]) - ->onlyMethods(['cliPrompt']) - ->getMock(); - $command - ->method('cliPrompt') - ->willReturn('y'); + $command = new Setup(Services::logger(), Services::commands()); $this->setPrivateProperty($command, 'distPath', $appFolder); @@ -66,7 +76,7 @@ public function testRun(): void $security = file_get_contents($appFolder . 'Config/Security.php'); $this->assertStringContainsString('$csrfProtection = \'session\';', $security); - $result = str_replace(["\033[0;32m", "\033[0m"], '', CITestStreamFilter::$buffer); + $result = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); $this->assertStringContainsString( ' Created: vfs://root/Config/Auth.php @@ -74,7 +84,8 @@ public function testRun(): void Created: vfs://root/Config/AuthToken.php Updated: vfs://root/Controllers/BaseController.php Updated: vfs://root/Config/Routes.php - Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons.', + Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons. + Updated: vfs://root/Config/Email.php', $result ); $this->assertStringContainsString( From 0ea66041c94cf9127f7f6476e7b34168472fa335 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 13:35:49 +0900 Subject: [PATCH 380/401] feat: add InputOutput::error() for testing --- src/Commands/Utils/InputOutput.php | 8 ++++++++ src/Test/MockInputOutput.php | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Commands/Utils/InputOutput.php b/src/Commands/Utils/InputOutput.php index 5541eb7d3..5bbfb4474 100644 --- a/src/Commands/Utils/InputOutput.php +++ b/src/Commands/Utils/InputOutput.php @@ -32,4 +32,12 @@ public function write( ): void { CLI::write($text, $foreground, $background); } + + /** + * Outputs an error to the CLI using STDERR instead of STDOUT + */ + public function error(string $text, string $foreground = 'light_red', ?string $background = null): void + { + CLI::error($text, $foreground, $background); + } } diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 8d268964a..e5bcd5ab5 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -85,4 +85,15 @@ public function write( CITestStreamFilter::removeOutputFilter(); CITestStreamFilter::removeErrorFilter(); } + + public function error(string $text, string $foreground = 'light_red', ?string $background = null): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addErrorFilter(); + + CLI::error($text, $foreground, $background); + $this->outputs[] = CITestStreamFilter::$buffer; + + CITestStreamFilter::removeErrorFilter(); + } } From 2196278f101d8d4dbc10a8a799f2e5572a60f977 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 13:38:55 +0900 Subject: [PATCH 381/401] refactor: use InputOutput::error() --- src/Commands/Setup.php | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index 32c931ac6..bca63b6aa 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -175,7 +175,7 @@ protected function writeFile(string $file, string $content): void ! $overwrite && $this->prompt(" File '{$cleanPath}' already exists in destination. Overwrite?", ['n', 'y']) === 'n' ) { - CLI::error(" Skipped {$cleanPath}. If you wish to overwrite, please use the '-f' option or reply 'y' to the prompt."); + $this->error(" Skipped {$cleanPath}. If you wish to overwrite, please use the '-f' option or reply 'y' to the prompt."); return; } @@ -184,7 +184,7 @@ protected function writeFile(string $file, string $content): void if (write_file($path, $content)) { $this->write(CLI::color(' Created: ', 'green') . $cleanPath); } else { - CLI::error(" Error creating {$cleanPath}."); + $this->error(" Error creating {$cleanPath}."); } } @@ -202,12 +202,12 @@ protected function add(string $file, string $code, string $pattern, string $repl $output = $this->replacer->add($content, $code, $pattern, $replace); if ($output === true) { - CLI::error(" Skipped {$cleanPath}. It has already been updated."); + $this->error(" Skipped {$cleanPath}. It has already been updated."); return; } if ($output === false) { - CLI::error(" Error checking {$cleanPath}."); + $this->error(" Error checking {$cleanPath}."); return; } @@ -215,7 +215,7 @@ protected function add(string $file, string $code, string $pattern, string $repl if (write_file($path, $output)) { $this->write(CLI::color(' Updated: ', 'green') . $cleanPath); } else { - CLI::error(" Error updating {$cleanPath}."); + $this->error(" Error updating {$cleanPath}."); } } @@ -244,7 +244,7 @@ private function replace(string $file, array $replaces): bool return true; } - CLI::error(" Error updating {$cleanPath}."); + $this->error(" Error updating {$cleanPath}."); return false; } @@ -294,7 +294,7 @@ private function setSecurityCSRF(): void $cleanPath = clean_path($path); if (! is_file($path)) { - CLI::error(" Not found file '{$cleanPath}'."); + $this->error(" Not found file '{$cleanPath}'."); return; } @@ -312,7 +312,7 @@ private function setSecurityCSRF(): void if (write_file($path, $output)) { $this->write(CLI::color(' Updated: ', 'green') . "We have updated file '{$cleanPath}' for security reasons."); } else { - CLI::error(" Error updating file '{$cleanPath}'."); + $this->error(" Error updating file '{$cleanPath}'."); } } @@ -324,7 +324,7 @@ private function setupEmail(): void $cleanPath = clean_path($path); if (! is_file($path)) { - CLI::error(" Not found file '{$cleanPath}'."); + $this->error(" Not found file '{$cleanPath}'."); return; } @@ -370,7 +370,7 @@ private function setupEmail(): void if (write_file($path, $output)) { $this->write(CLI::color(' Updated: ', 'green') . $cleanPath); } else { - CLI::error(" Error updating file '{$cleanPath}'."); + $this->error(" Error updating file '{$cleanPath}'."); } } @@ -416,6 +416,14 @@ private function write( self::$io->write($text, $foreground, $background); } + private function error( + string $text, + string $foreground = 'light_red', + ?string $background = null + ): void { + self::$io->error($text, $foreground, $background); + } + private function ensureInputOutput(): void { if (self::$io === null) { From 57986ba6af9fc2004b98a7ee7a94db1b050ffcef Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 13:51:30 +0900 Subject: [PATCH 382/401] refactor: extract BaseCommand --- src/Commands/BaseCommand.php | 80 ++++++++++++++++++++++++++++++++++++ src/Commands/Setup.php | 57 ------------------------- src/Commands/User.php | 61 +-------------------------- 3 files changed, 81 insertions(+), 117 deletions(-) create mode 100644 src/Commands/BaseCommand.php diff --git a/src/Commands/BaseCommand.php b/src/Commands/BaseCommand.php new file mode 100644 index 000000000..0311b63af --- /dev/null +++ b/src/Commands/BaseCommand.php @@ -0,0 +1,80 @@ +prompt($field, $options, $validation); + } + + /** + * Outputs a string to the cli on its own line. + */ + protected function write( + string $text = '', + ?string $foreground = null, + ?string $background = null + ): void { + self::$io->write($text, $foreground, $background); + } + + /** + * Outputs an error to the CLI using STDERR instead of STDOUT + */ + protected function error( + string $text, + string $foreground = 'light_red', + ?string $background = null + ): void { + self::$io->error($text, $foreground, $background); + } + + protected function ensureInputOutput(): void + { + if (self::$io === null) { + self::$io = new InputOutput(); + } + } + + /** + * @internal Testing purpose only + */ + public static function setInputOutput(InputOutput $io): void + { + self::$io = $io; + } + + /** + * @internal Testing purpose only + */ + public static function resetInputOutput(): void + { + self::$io = null; + } +} diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index bca63b6aa..20cc5edac 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -4,28 +4,15 @@ namespace CodeIgniter\Shield\Commands; -use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use CodeIgniter\CodeIgniter; use CodeIgniter\Commands\Database\Migrate; use CodeIgniter\Shield\Commands\Setup\ContentReplacer; -use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Test\Filters\CITestStreamFilter; use Config\Email as EmailConfig; use Config\Services; class Setup extends BaseCommand { - private static ?InputOutput $io = null; - - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Shield'; - /** * The Command's name * @@ -402,48 +389,4 @@ private function runMigrations(): void CITestStreamFilter::$buffer = ''; } - - private function prompt(string $field, $options = null, $validation = null): string - { - return self::$io->prompt($field, $options, $validation); - } - - private function write( - string $text = '', - ?string $foreground = null, - ?string $background = null - ): void { - self::$io->write($text, $foreground, $background); - } - - private function error( - string $text, - string $foreground = 'light_red', - ?string $background = null - ): void { - self::$io->error($text, $foreground, $background); - } - - private function ensureInputOutput(): void - { - if (self::$io === null) { - self::$io = new InputOutput(); - } - } - - /** - * @internal Testing purpose only - */ - public static function setInputOutput(InputOutput $io): void - { - self::$io = $io; - } - - /** - * @internal Testing purpose only - */ - public static function resetInputOutput(): void - { - self::$io = null; - } } diff --git a/src/Commands/User.php b/src/Commands/User.php index 4a2d1b1e1..172f20fb1 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -4,11 +4,9 @@ namespace CodeIgniter\Shield\Commands; -use CodeIgniter\CLI\BaseCommand; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Commands\Exceptions\BadInputException; use CodeIgniter\Shield\Commands\Exceptions\CancelException; -use CodeIgniter\Shield\Commands\Utils\InputOutput; use CodeIgniter\Shield\Config\Auth; use CodeIgniter\Shield\Entities\User as UserEntity; use CodeIgniter\Shield\Exceptions\UserNotFoundException; @@ -18,20 +16,11 @@ class User extends BaseCommand { - private static ?InputOutput $io = null; - private array $validActions = [ + private array $validActions = [ 'create', 'activate', 'deactivate', 'changename', 'changeemail', 'delete', 'password', 'list', 'addgroup', 'removegroup', ]; - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group = 'Shield'; - /** * Command's name * @@ -251,31 +240,6 @@ private function setValidationRules(): void ]; } - /** - * Asks the user for input. - * - * @param string $field Output "field" question - * @param array|string $options String to a default value, array to a list of options (the first option will be the default value) - * @param array|string $validation Validation rules - * - * @return string The user input - */ - private function prompt(string $field, $options = null, $validation = null): string - { - return self::$io->prompt($field, $options, $validation); - } - - /** - * Outputs a string to the cli on its own line. - */ - private function write( - string $text = '', - ?string $foreground = null, - ?string $background = null - ): void { - self::$io->write($text, $foreground, $background); - } - /** * Create a new user * @@ -692,27 +656,4 @@ private function findUser($question = '', $username = null, $email = null): User return $userModel->findById($user['id']); } - - private function ensureInputOutput(): void - { - if (self::$io === null) { - self::$io = new InputOutput(); - } - } - - /** - * @internal Testing purpose only - */ - public static function setInputOutput(InputOutput $io): void - { - self::$io = $io; - } - - /** - * @internal Testing purpose only - */ - public static function resetInputOutput(): void - { - self::$io = null; - } } From af9df72e31e8dd6140287e9f93b6c594fe307a7a Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 14:11:55 +0900 Subject: [PATCH 383/401] docs: update docs --- docs/getting_started/install.md | 71 +++++++++++---------------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index a8ec9d8d9..d372b2f1c 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -54,9 +54,12 @@ Require it with an explicit version constraint allowing its desired stability. ## Initial Setup +There are a few setup items to do before you can start using Shield in +your project. + ### Command Setup -1. Run the following command. This command handles steps 1-5 of *Manual Setup* and runs the migrations. +1. Run the following command. This command handles steps 1-6 of *Manual Setup*. ```console php spark shield:setup @@ -67,36 +70,8 @@ Require it with an explicit version constraint allowing its desired stability. If you want to customize table names, you must change the table names before running database migrations. See [Customizing Table Names](../customization/table_names.md). -2. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). - - ```php - Date: Tue, 3 Oct 2023 14:17:08 +0900 Subject: [PATCH 384/401] docs: update sample code --- docs/getting_started/install.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index d372b2f1c..c608bd3f3 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -124,16 +124,8 @@ your project. class Email extends BaseConfig { - /** - * @var string - */ - public $fromEmail = 'your_mail@example.com'; - - /** - * @var string - */ - public $fromName = 'your name'; - + public string $fromEmail = 'your_mail@example.com'; + public string $fromName = 'your name'; // ... } ``` From c27ec8878185eae93f85ab24d71c782ee783e312 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 3 Oct 2023 14:19:40 +0900 Subject: [PATCH 385/401] refactor: remove unused property --- tests/Commands/SetupTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index bc26bfb72..a5f01938e 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -16,7 +16,6 @@ final class SetupTest extends TestCase { private ?MockInputOutput $io = null; - private $streamFilter; protected function tearDown(): void { From 02188a7d7ced0befbf8633609032e8d0c3935fe5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:02:58 +0900 Subject: [PATCH 386/401] fix: add null check --- src/Commands/Setup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index 20cc5edac..eb92d0ebc 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -317,8 +317,8 @@ private function setupEmail(): void } $config = new EmailConfig(); - $fromEmail = $config->fromEmail; - $fromName = $config->fromName; + $fromEmail = (string) $config->fromEmail; // Old Config may return null. + $fromName = (string) $config->fromName; if ($fromEmail !== '' && $fromName !== '') { $this->write(CLI::color(' Email Setup: ', 'green') . 'Everything is fine.'); From b78854513e414b5b396df401c4705ef3a43dd660 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:15:20 +0900 Subject: [PATCH 387/401] refactor: use config() --- src/Commands/Setup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index eb92d0ebc..a39e7de10 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -316,7 +316,7 @@ private function setupEmail(): void return; } - $config = new EmailConfig(); + $config = config(EmailConfig::class); $fromEmail = (string) $config->fromEmail; // Old Config may return null. $fromName = (string) $config->fromName; From 41fb8c8c75bfad6d944c5361b1f45d110d993bd3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:15:35 +0900 Subject: [PATCH 388/401] test: add test for EmailConfig is fine --- tests/Commands/SetupTest.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index a5f01938e..504475c92 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -6,6 +6,7 @@ use CodeIgniter\Shield\Commands\Setup; use CodeIgniter\Shield\Test\MockInputOutput; +use Config\Email as EmailConfig; use Config\Services; use org\bovigo\vfs\vfsStream; use Tests\Support\TestCase; @@ -92,4 +93,39 @@ public function testRun(): void $result ); } + + public function testRunEmailConfigIsFine(): void + { + // Set MockIO and your inputs. + $this->setMockIo(['y']); + + $config = config(EmailConfig::class); + $config->fromEmail = 'admin@example.com'; + $config->fromName = 'Site Admin'; + + $root = vfsStream::setup('root'); + vfsStream::copyFromFileSystem( + APPPATH, + $root + ); + $appFolder = $root->url() . '/'; + + $command = new Setup(Services::logger(), Services::commands()); + + $this->setPrivateProperty($command, 'distPath', $appFolder); + + $command->run([]); + + $result = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + + $this->assertStringContainsString( + ' Created: vfs://root/Config/Auth.php + Created: vfs://root/Config/AuthGroups.php + Created: vfs://root/Config/AuthToken.php + Updated: vfs://root/Controllers/BaseController.php + Updated: vfs://root/Config/Routes.php + Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons.', + $result + ); + } } From 7eb669727b608c51c55ce6297f8d34a8cbf52461 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:20:52 +0900 Subject: [PATCH 389/401] test: extract methods --- tests/Commands/SetupTest.php | 37 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 504475c92..60813e774 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -49,12 +49,7 @@ public function testRun(): void 'y', ]); - $root = vfsStream::setup('root'); - vfsStream::copyFromFileSystem( - APPPATH, - $root - ); - $appFolder = $root->url() . '/'; + $appFolder = $this->createFilesystem(); $command = new Setup(Services::logger(), Services::commands()); @@ -76,7 +71,7 @@ public function testRun(): void $security = file_get_contents($appFolder . 'Config/Security.php'); $this->assertStringContainsString('$csrfProtection = \'session\';', $security); - $result = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + $result = $this->getOutputWithoutColorCode(); $this->assertStringContainsString( ' Created: vfs://root/Config/Auth.php @@ -103,12 +98,7 @@ public function testRunEmailConfigIsFine(): void $config->fromEmail = 'admin@example.com'; $config->fromName = 'Site Admin'; - $root = vfsStream::setup('root'); - vfsStream::copyFromFileSystem( - APPPATH, - $root - ); - $appFolder = $root->url() . '/'; + $appFolder = $this->createFilesystem(); $command = new Setup(Services::logger(), Services::commands()); @@ -116,7 +106,7 @@ public function testRunEmailConfigIsFine(): void $command->run([]); - $result = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + $result = $this->getOutputWithoutColorCode(); $this->assertStringContainsString( ' Created: vfs://root/Config/Auth.php @@ -128,4 +118,23 @@ public function testRunEmailConfigIsFine(): void $result ); } + + /** + * @return string app folder path + */ + private function createFilesystem(): string + { + $root = vfsStream::setup('root'); + vfsStream::copyFromFileSystem( + APPPATH, + $root + ); + + return $root->url() . '/'; + } + + private function getOutputWithoutColorCode(): string + { + return str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + } } From fe0da3de32754fb438b98d9d29d4010c1853f5b3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:27:45 +0900 Subject: [PATCH 390/401] chore: add RecastingRemovalRector in skip() --- rector.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rector.php b/rector.php index 862da0f20..12a24007d 100644 --- a/rector.php +++ b/rector.php @@ -20,6 +20,7 @@ use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector; use Rector\Config\RectorConfig; use Rector\Core\ValueObject\PhpVersion; +use Rector\DeadCode\Rector\Cast\RecastingRemovalRector; use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPromotedPropertyRector; use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector; use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector; @@ -109,6 +110,11 @@ __DIR__ . '/tests/Commands/UserModelGeneratorTest.php', __DIR__ . '/tests/Controllers/LoginTest.php', ], + + RecastingRemovalRector::class => [ + // To check old Email Config file + __DIR__ . '/src/Commands/Setup.php', + ], ]); // auto import fully qualified class names From a3f947d9d17f72dc3e36b211ca7be4368fb1f497 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:32:27 +0900 Subject: [PATCH 391/401] fix: remove unneeded CITestStreamFilter removal --- src/Test/MockInputOutput.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index e5bcd5ab5..65574a6fd 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -83,7 +83,6 @@ public function write( $this->outputs[] = CITestStreamFilter::$buffer; CITestStreamFilter::removeOutputFilter(); - CITestStreamFilter::removeErrorFilter(); } public function error(string $text, string $foreground = 'light_red', ?string $background = null): void From a7ad37e96f446bd71cda6393223dbc4776ee1114 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 05:34:42 +0900 Subject: [PATCH 392/401] fix: add missing CITestStreamFilter::addErrorFilter() --- src/Test/MockInputOutput.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Test/MockInputOutput.php b/src/Test/MockInputOutput.php index 65574a6fd..e4ce93dda 100644 --- a/src/Test/MockInputOutput.php +++ b/src/Test/MockInputOutput.php @@ -53,6 +53,7 @@ public function prompt(string $field, $options = null, $validation = null): stri CITestStreamFilter::registration(); CITestStreamFilter::addOutputFilter(); + CITestStreamFilter::addErrorFilter(); PhpStreamWrapper::register(); PhpStreamWrapper::setContent($input); From a7481fcce4c19a1425531c3fd4cf41fca1993eab Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 06:24:04 +0900 Subject: [PATCH 393/401] refactor: remove outdated code for CI 4.2 or before --- .../Generators/UserModelGenerator.php | 3 +-- src/Models/CheckQueryReturnTrait.php | 23 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/Commands/Generators/UserModelGenerator.php b/src/Commands/Generators/UserModelGenerator.php index 2e9680aa7..3a9b63590 100644 --- a/src/Commands/Generators/UserModelGenerator.php +++ b/src/Commands/Generators/UserModelGenerator.php @@ -73,8 +73,7 @@ public function run(array $params): int $params[0] = $class; - // @TODO execute() is deprecated in CI v4.3.0. - $this->execute($params); // @phpstan-ignore-line suppress deprecated error. + $this->generateClass($params); return 0; } diff --git a/src/Models/CheckQueryReturnTrait.php b/src/Models/CheckQueryReturnTrait.php index 2c21441b1..687d7da96 100644 --- a/src/Models/CheckQueryReturnTrait.php +++ b/src/Models/CheckQueryReturnTrait.php @@ -32,7 +32,7 @@ protected function checkQueryReturn($return): void protected function checkValidationError(): void { - $validationErrors = $this->getValidationErrors(); + $validationErrors = $this->validation->getErrors(); if ($validationErrors !== []) { $message = 'Validation error:'; @@ -45,27 +45,6 @@ protected function checkValidationError(): void } } - /** - * Gets real validation errors that are not saved in the Session. - * - * @return string[] - */ - protected function getValidationErrors(): array - { - // @TODO When CI v4.3 is released, you don't need this hack. - // See https://github.com/codeigniter4/CodeIgniter4/pull/6384 - return $this->getValidationPropertyErrors(); - } - - protected function getValidationPropertyErrors(): array - { - $refClass = new ReflectionObject($this->validation); - $refProperty = $refClass->getProperty('errors'); - $refProperty->setAccessible(true); - - return $refProperty->getValue($this->validation); - } - protected function disableDBDebug(): void { if (! $this->db->DBDebug) { From d03c793a53fe149a82542d9a946379a1a0c487a6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 07:59:42 +0900 Subject: [PATCH 394/401] fix: set InputOutput when constructing --- src/Commands/BaseCommand.php | 9 +++++++++ src/Commands/User.php | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Commands/BaseCommand.php b/src/Commands/BaseCommand.php index 0311b63af..2b30856f0 100644 --- a/src/Commands/BaseCommand.php +++ b/src/Commands/BaseCommand.php @@ -5,7 +5,9 @@ namespace CodeIgniter\Shield\Commands; use CodeIgniter\CLI\BaseCommand as FrameworkBaseCommand; +use CodeIgniter\CLI\Commands; use CodeIgniter\Shield\Commands\Utils\InputOutput; +use Psr\Log\LoggerInterface; abstract class BaseCommand extends FrameworkBaseCommand { @@ -19,6 +21,13 @@ abstract class BaseCommand extends FrameworkBaseCommand */ protected $group = 'Shield'; + public function __construct(LoggerInterface $logger, Commands $commands) + { + parent::__construct($logger, $commands); + + $this->ensureInputOutput(); + } + /** * Asks the user for input. * diff --git a/src/Commands/User.php b/src/Commands/User.php index 172f20fb1..e5d42daa9 100644 --- a/src/Commands/User.php +++ b/src/Commands/User.php @@ -126,7 +126,6 @@ class User extends BaseCommand */ public function run(array $params): int { - $this->ensureInputOutput(); $this->setTables(); $this->setValidationRules(); From f6c1a4dfd49997120b598c5f116a56aaab699eeb Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 08:04:57 +0900 Subject: [PATCH 395/401] test: add logic to normalize line break code For Windows. --- tests/Commands/SetupTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 60813e774..648f0f12e 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -135,6 +135,12 @@ private function createFilesystem(): string private function getOutputWithoutColorCode(): string { - return str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + $output = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + + if (is_windows()) { + $output = str_replace("\r\n", "\n", $output); + } + + return $output; } } From 3a24d28cf58acc22081df4d019d47b4b4ab4c563 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 Oct 2023 08:11:55 +0900 Subject: [PATCH 396/401] test: fix assertions for Windows --- tests/Commands/UserTest.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 668ef19e2..98a706563 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -37,6 +37,17 @@ private function setMockIo(array $inputs): void User::setInputOutput($this->io); } + private function getOutputWithoutColorCode(): string + { + $output = str_replace(["\033[0;32m", "\033[0m"], '', $this->io->getOutputs()); + + if (is_windows()) { + $output = str_replace("\r\n", "\n", $output); + } + + return $output; + } + public function testNoAction(): void { $this->setMockIo([]); @@ -466,7 +477,7 @@ public function testList(): void 1 user8 (user8@example.com) 2 user9 (user9@example.com) ', - $this->io->getOutputs() + $this->getOutputWithoutColorCode() ); } @@ -491,7 +502,7 @@ public function testListByEmail(): void 'Id User 2 user9 (user9@example.com) ', - $this->io->getOutputs() + $this->getOutputWithoutColorCode() ); } From b968a39d528352bb60b34cad5fd1794cb1184d82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:51:52 +0000 Subject: [PATCH 397/401] chore(deps-dev): update rector/rector requirement from 0.18.4 to 0.18.5 Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](https://github.com/rectorphp/rector/compare/0.18.4...0.18.5) --- updated-dependencies: - dependency-name: rector/rector dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f1cce5feb..d7a4327bd 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "mockery/mockery": "^1.0", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-strict-rules": "^1.5", - "rector/rector": "0.18.4" + "rector/rector": "0.18.5" }, "provide": { "codeigniter4/authentication-implementation": "1.0" From 0cf7e5909098423d72881469670919ae055d1f63 Mon Sep 17 00:00:00 2001 From: Samuel Asor <8720569+sammyskills@users.noreply.github.com> Date: Sun, 8 Oct 2023 15:58:05 +0100 Subject: [PATCH 398/401] used named routes for redirects and form submission --- src/Controllers/RegisterController.php | 2 +- src/Views/email_activate_show.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controllers/RegisterController.php b/src/Controllers/RegisterController.php index e0eea5014..20daf5f73 100644 --- a/src/Controllers/RegisterController.php +++ b/src/Controllers/RegisterController.php @@ -126,7 +126,7 @@ public function registerAction(): RedirectResponse // If an action has been defined for register, start it up. $hasAction = $authenticator->startUpAction('register', $user); if ($hasAction) { - return redirect()->to('auth/a/show'); + return redirect()->route('auth-action-show'); } // Set the user active diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index 9d29ef8d8..066bd0b1c 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -15,7 +15,7 @@

-
+ From bd0bf21a0b79cced71fa35f727b21eacf1bde7ef Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 9 Oct 2023 09:34:13 +0900 Subject: [PATCH 399/401] docs: add about locale routes --- docs/customization/route_config.md | 39 +++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/customization/route_config.md b/docs/customization/route_config.md index 01f59c412..0e69dfb80 100644 --- a/docs/customization/route_config.md +++ b/docs/customization/route_config.md @@ -1,6 +1,10 @@ # Customizing Routes -If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: +## Change Some Routes + +If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. + +You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: ```php service('auth')->routes($routes, ['except' => ['login', 'register']]); @@ -12,3 +16,36 @@ Then add the routes to your customized controllers: $routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); $routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); ``` + +After customization, check your routes with the [spark routes](https://codeigniter.com/user_guide/incoming/routing.html#spark-routes) command. + +## Use Locale Routes + +You can use the `{locale}` placeholder in your routes +(see [Locale Detection](https://codeigniter.com/user_guide/outgoing/localization.html#in-routes)). + +```php +$routes->group('{locale}', static function($routes) { + service('auth')->routes($routes); +}); +``` + +The above code registers the following routes: + +```text ++--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ +| Method | Route | Name | Handler | Before Filters | After Filters | ++--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ +| GET | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerView | session | toolbar | +| GET | {locale}/login | login | \CodeIgniter\Shield\Controllers\LoginController::loginView | session | toolbar | +| GET | {locale}/login/magic-link | magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::loginView | session | toolbar | +| GET | {locale}/login/verify-magic-link | verify-magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::verify | session | toolbar | +| GET | {locale}/logout | logout | \CodeIgniter\Shield\Controllers\LoginController::logoutAction | session | toolbar | +| GET | {locale}/auth/a/show | auth-action-show | \CodeIgniter\Shield\Controllers\ActionController::show | session | toolbar | +| POST | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerAction | session | toolbar | +| POST | {locale}/login | » | \CodeIgniter\Shield\Controllers\LoginController::loginAction | session | toolbar | +| POST | {locale}/login/magic-link | » | \CodeIgniter\Shield\Controllers\MagicLinkController::loginAction | session | toolbar | +| POST | {locale}/auth/a/handle | auth-action-handle | \CodeIgniter\Shield\Controllers\ActionController::handle | session | toolbar | +| POST | {locale}/auth/a/verify | auth-action-verify | \CodeIgniter\Shield\Controllers\ActionController::verify | session | toolbar | ++--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ +``` From d22564066880a9cdff9f13d1d7b368ff2fb4fa96 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 9 Oct 2023 09:47:11 +0900 Subject: [PATCH 400/401] docs: add about global filter config update --- docs/customization/route_config.md | 35 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/customization/route_config.md b/docs/customization/route_config.md index 0e69dfb80..46d1c6597 100644 --- a/docs/customization/route_config.md +++ b/docs/customization/route_config.md @@ -36,16 +36,29 @@ The above code registers the following routes: +--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ | Method | Route | Name | Handler | Before Filters | After Filters | +--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ -| GET | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerView | session | toolbar | -| GET | {locale}/login | login | \CodeIgniter\Shield\Controllers\LoginController::loginView | session | toolbar | -| GET | {locale}/login/magic-link | magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::loginView | session | toolbar | -| GET | {locale}/login/verify-magic-link | verify-magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::verify | session | toolbar | -| GET | {locale}/logout | logout | \CodeIgniter\Shield\Controllers\LoginController::logoutAction | session | toolbar | -| GET | {locale}/auth/a/show | auth-action-show | \CodeIgniter\Shield\Controllers\ActionController::show | session | toolbar | -| POST | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerAction | session | toolbar | -| POST | {locale}/login | » | \CodeIgniter\Shield\Controllers\LoginController::loginAction | session | toolbar | -| POST | {locale}/login/magic-link | » | \CodeIgniter\Shield\Controllers\MagicLinkController::loginAction | session | toolbar | -| POST | {locale}/auth/a/handle | auth-action-handle | \CodeIgniter\Shield\Controllers\ActionController::handle | session | toolbar | -| POST | {locale}/auth/a/verify | auth-action-verify | \CodeIgniter\Shield\Controllers\ActionController::verify | session | toolbar | +| GET | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerView | | toolbar | +| GET | {locale}/login | login | \CodeIgniter\Shield\Controllers\LoginController::loginView | | toolbar | +| GET | {locale}/login/magic-link | magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::loginView | | toolbar | +| GET | {locale}/login/verify-magic-link | verify-magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::verify | | toolbar | +| GET | {locale}/logout | logout | \CodeIgniter\Shield\Controllers\LoginController::logoutAction | | toolbar | +| GET | {locale}/auth/a/show | auth-action-show | \CodeIgniter\Shield\Controllers\ActionController::show | | toolbar | +| POST | {locale}/register | register | \CodeIgniter\Shield\Controllers\RegisterController::registerAction | | toolbar | +| POST | {locale}/login | » | \CodeIgniter\Shield\Controllers\LoginController::loginAction | | toolbar | +| POST | {locale}/login/magic-link | » | \CodeIgniter\Shield\Controllers\MagicLinkController::loginAction | | toolbar | +| POST | {locale}/auth/a/handle | auth-action-handle | \CodeIgniter\Shield\Controllers\ActionController::handle | | toolbar | +| POST | {locale}/auth/a/verify | auth-action-verify | \CodeIgniter\Shield\Controllers\ActionController::verify | | toolbar | +--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+ ``` + +If you set the global filter in the **app/Config/Filters.php** file, you need to +update the paths for `except`: + +```php +public $globals = [ + 'before' => [ + // ... + 'session' => ['except' => ['*/login*', '*/register', '*/auth/a/*']], + ], + // ... +]; +``` From 9e95081e0434784ac01b5bb83a1ff7cc97377e97 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 9 Oct 2023 11:51:08 +0900 Subject: [PATCH 401/401] Prep for 1.0.0-beta.7 release --- src/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth.php b/src/Auth.php index 41163479a..32b163db0 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -28,7 +28,7 @@ class Auth /** * The current version of CodeIgniter Shield */ - public const SHIELD_VERSION = '1.0.0-beta.6'; + public const SHIELD_VERSION = '1.0.0-beta.7'; protected Authentication $authenticate;