├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── UPGRADING.md
├── composer.json
├── deptrac.yaml
├── docs
├── CNAME
├── addons
│ └── jwt.md
├── assets
│ ├── css
│ │ ├── codeigniter.css
│ │ └── codeigniter_dark_mode.css
│ ├── favicon.ico
│ ├── flame.svg
│ └── js
│ │ ├── curl.min.js
│ │ └── hljs.js
├── customization
│ ├── adding_attributes_to_users.md
│ ├── extending_controllers.md
│ ├── integrating_custom_view_libs.md
│ ├── login_identifier.md
│ ├── redirect_urls.md
│ ├── route_config.md
│ ├── table_names.md
│ ├── user_provider.md
│ ├── validation_rules.md
│ └── views.md
├── getting_started
│ ├── authenticators.md
│ ├── concepts.md
│ ├── configuration.md
│ └── install.md
├── guides
│ ├── api_hmac_keys.md
│ ├── api_tokens.md
│ ├── mobile_apps.md
│ └── strengthen_password.md
├── index.md
├── quick_start_guide
│ ├── using_authorization.md
│ └── using_session_auth.md
├── references
│ ├── authentication
│ │ ├── auth_actions.md
│ │ ├── authentication.md
│ │ ├── hmac.md
│ │ ├── session.md
│ │ └── tokens.md
│ ├── authorization.md
│ ├── controller_filters.md
│ ├── events.md
│ ├── magic_link_login.md
│ └── testing.md
└── user_management
│ ├── banning_users.md
│ ├── forcing_password_reset.md
│ └── managing_users.md
├── roave-bc-check.yaml
└── src
├── Auth.php
├── Authentication
├── Actions
│ ├── ActionInterface.php
│ ├── Email2FA.php
│ └── EmailActivator.php
├── Authentication.php
├── AuthenticationException.php
├── AuthenticatorInterface.php
├── Authenticators
│ ├── AccessTokens.php
│ ├── HmacSha256.php
│ ├── JWT.php
│ └── Session.php
├── HMAC
│ └── HmacEncrypter.php
├── JWT
│ ├── Adapters
│ │ └── FirebaseAdapter.php
│ ├── Exceptions
│ │ └── InvalidTokenException.php
│ ├── JWSAdapterInterface.php
│ ├── JWSDecoder.php
│ └── JWSEncoder.php
├── JWTManager.php
├── Passwords.php
├── Passwords
│ ├── BaseValidator.php
│ ├── CompositionValidator.php
│ ├── DictionaryValidator.php
│ ├── NothingPersonalValidator.php
│ ├── PwnedValidator.php
│ ├── ValidationRules.php
│ ├── ValidatorInterface.php
│ └── _dictionary.txt
└── Traits
│ ├── HasAccessTokens.php
│ └── HasHmacTokens.php
├── Authorization
├── AuthorizationException.php
├── Groups.php
└── Traits
│ └── Authorizable.php
├── Collectors
└── Auth.php
├── Commands
├── BaseCommand.php
├── Exceptions
│ ├── BadInputException.php
│ └── CancelException.php
├── Generators
│ ├── UserModelGenerator.php
│ └── Views
│ │ └── usermodel.tpl.php
├── Hmac.php
├── Setup.php
├── Setup
│ └── ContentReplacer.php
├── User.php
└── Utils
│ └── InputOutput.php
├── Config
├── Auth.php
├── AuthGroups.php
├── AuthJWT.php
├── AuthRoutes.php
├── AuthToken.php
├── BaseAuthToken.php
├── Registrar.php
└── Services.php
├── Controllers
├── ActionController.php
├── LoginController.php
├── MagicLinkController.php
└── RegisterController.php
├── Database
└── Migrations
│ └── 2020-12-28-223112_create_auth_tables.php
├── Entities
├── AccessToken.php
├── Group.php
├── Login.php
├── User.php
└── UserIdentity.php
├── Exceptions
├── BaseException.php
├── GroupException.php
├── InvalidArgumentException.php
├── LogicException.php
├── PermissionException.php
├── RuntimeException.php
├── SecurityException.php
├── UserNotFoundException.php
└── ValidationException.php
├── Filters
├── AbstractAuthFilter.php
├── AuthRates.php
├── ChainAuth.php
├── ForcePasswordResetFilter.php
├── GroupFilter.php
├── HmacAuth.php
├── JWTAuth.php
├── PermissionFilter.php
├── SessionAuth.php
└── TokenAuth.php
├── Helpers
├── auth_helper.php
└── email_helper.php
├── Language
├── ar
│ └── Auth.php
├── bg
│ └── Auth.php
├── cs
│ └── Auth.php
├── de
│ └── Auth.php
├── en
│ └── Auth.php
├── es
│ └── Auth.php
├── fa
│ └── Auth.php
├── fr
│ └── Auth.php
├── id
│ └── Auth.php
├── it
│ └── Auth.php
├── ja
│ └── Auth.php
├── lt
│ └── Auth.php
├── nl
│ └── Auth.php
├── pl
│ └── Auth.php
├── pt-BR
│ └── Auth.php
├── pt
│ └── Auth.php
├── ru
│ └── Auth.php
├── sk
│ └── Auth.php
├── sr
│ └── Auth.php
├── sv-SE
│ └── Auth.php
├── tr
│ └── Auth.php
└── uk
│ └── Auth.php
├── Models
├── BaseModel.php
├── CheckQueryReturnTrait.php
├── DatabaseException.php
├── GroupModel.php
├── LoginModel.php
├── PermissionModel.php
├── RememberModel.php
├── TokenLoginModel.php
├── UserIdentityModel.php
└── UserModel.php
├── Result.php
├── Test
├── AuthenticationTesting.php
└── MockInputOutput.php
├── Traits
├── Activatable.php
├── Bannable.php
├── Resettable.php
└── Viewable.php
├── Validation
└── ValidationRules.php
└── Views
├── Email
├── email_2fa_email.php
├── email_activate_email.php
└── magic_link_email.php
├── email_2fa_show.php
├── email_2fa_verify.php
├── email_activate_show.php
├── layout.php
├── login.php
├── magic_link_form.php
├── magic_link_message.php
└── register.php
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CodeIgniter Shield
2 |
3 | CodeIgniter is a community driven project and accepts contributions of
4 | code and documentation from the community.
5 |
6 | If you'd like to contribute, please read [Contributing to CodeIgniter](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md)
7 | in the [main repository](https://github.com/codeigniter4/CodeIgniter4).
8 |
9 | If you are going to contribute to this repository, please [report bugs](https://github.com/codeigniter4/shield/issues/new?assignees=&labels=bug&template=bug_report.yml&title=Bug%3A+) or [send PRs](https://github.com/codeigniter4/shield/compare)
10 | to this repository instead of the main repository.
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-2022 Lonnie Ezell
4 | Copyright (c) 2022-2025 CodeIgniter Foundation
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | The development team and community take all security issues seriously. **Please do not make public any uncovered flaws.**
4 |
5 | ## Reporting a Vulnerability
6 |
7 | Thank you for improving the security of our code! Any assistance in removing security flaws will be acknowledged.
8 |
9 | **Please report security flaws by emailing the development team directly: security@codeigniter.com**.
10 |
11 | The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating
12 | the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the
13 | progress towards a fix and full announcement, and may ask for additional information or guidance.
14 |
15 | ## Disclosure Policy
16 |
17 | When the security team receives a security bug report, they will assign it to a primary handler.
18 | This person will coordinate the fix and release process, involving the following steps:
19 |
20 | - Confirm the problem and determine the affected versions.
21 | - Audit code to find any potential similar problems.
22 | - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible.
23 |
24 | ## Comments on this Policy
25 |
26 | If you have suggestions on how this process could be improved please submit a Pull Request.
27 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codeigniter4/shield",
3 | "description": "Authentication and Authorization for CodeIgniter 4",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "codeigniter",
8 | "codeigniter4",
9 | "authentication",
10 | "authorization"
11 | ],
12 | "authors": [
13 | {
14 | "name": "Lonnie Ezell",
15 | "email": "lonnieje@gmail.com",
16 | "role": "Developer"
17 | }
18 | ],
19 | "homepage": "https://github.com/codeigniter4/shield",
20 | "support": {
21 | "issues": "https://github.com/codeigniter4/shield/issues",
22 | "forum": "https://github.com/codeigniter4/shield/discussions",
23 | "source": "https://github.com/codeigniter4/shield",
24 | "docs": "https://codeigniter4.github.io/shield/",
25 | "slack": "https://codeigniterchat.slack.com"
26 | },
27 | "require": {
28 | "php": "^8.1",
29 | "codeigniter4/settings": "^2.1"
30 | },
31 | "require-dev": {
32 | "codeigniter/phpstan-codeigniter": "^1.3",
33 | "codeigniter4/devkit": "^1.3",
34 | "codeigniter4/framework": ">=4.3.5 <4.5.0 || ^4.5.1",
35 | "firebase/php-jwt": "^6.4",
36 | "mikey179/vfsstream": "^1.6.7",
37 | "mockery/mockery": "^1.0",
38 | "phpstan/phpstan-strict-rules": "^2.0"
39 | },
40 | "provide": {
41 | "codeigniter4/authentication-implementation": "1.0"
42 | },
43 | "suggest": {
44 | "ext-curl": "Required to use the password validation rule via PwnedValidator class.",
45 | "ext-openssl": "Required to use the JWT Authenticator."
46 | },
47 | "minimum-stability": "dev",
48 | "prefer-stable": true,
49 | "autoload": {
50 | "psr-4": {
51 | "CodeIgniter\\Shield\\": "src"
52 | },
53 | "exclude-from-classmap": [
54 | "**/Database/Migrations/**"
55 | ]
56 | },
57 | "autoload-dev": {
58 | "psr-4": {
59 | "Tests\\": "tests",
60 | "Tests\\Support\\": "tests/_support"
61 | }
62 | },
63 | "config": {
64 | "allow-plugins": {
65 | "phpstan/extension-installer": true
66 | },
67 | "sort-packages": true
68 | },
69 | "scripts": {
70 | "post-update-cmd": [
71 | "bash admin/setup.sh"
72 | ],
73 | "analyze": [
74 | "phpstan analyze",
75 | "psalm",
76 | "rector process --dry-run"
77 | ],
78 | "ci": [
79 | "Composer\\Config::disableProcessTimeout",
80 | "@cs",
81 | "@deduplicate",
82 | "@inspect",
83 | "@analyze",
84 | "@test"
85 | ],
86 | "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff",
87 | "cs-fix": "php-cs-fixer fix --ansi --verbose --diff",
88 | "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php",
89 | "inspect": "deptrac analyze --cache-file=build/deptrac.cache",
90 | "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit",
91 | "sa": "@analyze",
92 | "style": "@cs-fix",
93 | "test": "phpunit"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | shield.codeigniter.com
2 |
--------------------------------------------------------------------------------
/docs/assets/css/codeigniter.css:
--------------------------------------------------------------------------------
1 | [data-md-color-scheme="codeigniter"] {
2 | --md-primary-fg-color: #dd4814;
3 | --md-primary-fg-color--light: #ECB7B7;
4 | --md-primary-fg-color--dark: #90030C;
5 |
6 | --md-default-bg-color: #fcfcfc;
7 |
8 | --md-typeset-a-color: #e74c3c;
9 | --md-accent-fg-color: #97310e;
10 |
11 | --md-accent-fg-color--transparent: #ECB7B7;
12 |
13 | --md-code-bg-color: #ffffff;
14 |
15 | .md-typeset code {
16 | border: 1px solid #e1e4e5;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/docs/assets/css/codeigniter_dark_mode.css:
--------------------------------------------------------------------------------
1 | [data-md-color-scheme="slate"] {
2 | --md-primary-fg-color: #b13a10;
3 | --md-primary-fg-color--light: #8d7474;
4 | --md-primary-fg-color--dark: #6d554d;
5 |
6 | --md-default-bg-color: #1e2129;
7 |
8 | --md-typeset-a-color: #ed6436;
9 | --md-accent-fg-color: #f18a67;
10 |
11 | --md-accent-fg-color--transparent: #625151;
12 |
13 | --md-code-bg-color: #282b2d;
14 |
15 | .hljs-title,
16 | .hljs-title.class_,
17 | .hljs-title.class_.inherited__,
18 | .hljs-title.function_ {
19 | color: #c9a69b;
20 | }
21 |
22 | .hljs-meta .hljs-string,
23 | .hljs-regexp,
24 | .hljs-string {
25 | color: #a3b4c7;
26 | }
27 |
28 | .hljs-attr,
29 | .hljs-attribute,
30 | .hljs-literal,
31 | .hljs-meta,
32 | .hljs-number,
33 | .hljs-operator,
34 | .hljs-selector-attr,
35 | .hljs-selector-class,
36 | .hljs-selector-id,
37 | .hljs-variable {
38 | color: #c1b79f;
39 | }
40 |
41 | .hljs-doctag,
42 | .hljs-keyword,
43 | .hljs-meta .hljs-keyword,
44 | .hljs-template-tag,
45 | .hljs-template-variable,
46 | .hljs-type,
47 | .hljs-variable.language_ {
48 | color: #c97100;
49 | }
50 |
51 | .hljs-subst {
52 | color: #ddba52
53 | }
54 |
55 | .md-typeset code {
56 | border: 1px solid #3f4547;
57 | }
58 |
59 | .md-typeset .admonition.note,
60 | .md-typeset details.note {
61 | border-color: #2c5293;
62 | }
63 |
64 | .md-typeset .note > .admonition-title:before,
65 | .md-typeset .note > summary:before {
66 | background-color: #2c5293;
67 | -webkit-mask-image: var(--md-admonition-icon--note);
68 | mask-image: var(--md-admonition-icon--note);
69 | }
70 |
71 | .md-typeset .admonition.warning,
72 | .md-typeset details.warning {
73 | border-color: #97631e;
74 | }
75 |
76 | .md-typeset .warning > .admonition-title:before,
77 | .md-typeset .warning > summary:before {
78 | background-color: #97631e;
79 | -webkit-mask-image: var(--md-admonition-icon--warning);
80 | mask-image: var(--md-admonition-icon--warning);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/docs/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeigniter4/shield/81b69bf9985f71f3a64d9274980acc85d0d0ceb7/docs/assets/favicon.ico
--------------------------------------------------------------------------------
/docs/assets/flame.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/docs/assets/js/curl.min.js:
--------------------------------------------------------------------------------
1 | /*! `curl` grammar compiled for Highlight.js 11.3.1 */
2 | (()=>{var e=(()=>{"use strict";return e=>{const n={className:"string",begin:/"/,
3 | end:/"/,contains:[e.BACKSLASH_ESCAPE,{className:"variable",begin:/\$\(/,
4 | end:/\)/,contains:[e.BACKSLASH_ESCAPE]}],relevance:0},a={className:"number",
5 | variants:[{begin:e.C_NUMBER_RE}],relevance:0};return{name:"curl",
6 | aliases:["curl"],keywords:"curl",case_insensitive:!0,contains:[{
7 | className:"literal",begin:/(--request|-X)\s/,contains:[{className:"symbol",
8 | begin:/(get|post|delete|options|head|put|patch|trace|connect)/,end:/\s/,
9 | returnEnd:!0}],returnEnd:!0,relevance:10},{className:"literal",begin:/--/,
10 | end:/[\s"]/,returnEnd:!0,relevance:0},{className:"literal",begin:/-\w/,
11 | end:/[\s"]/,returnEnd:!0,relevance:0},n,{className:"string",begin:/\\"/,
12 | relevance:0},{className:"string",begin:/'/,end:/'/,relevance:0
13 | },e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,{match:/(\/[a-z._-]+)+/}]}}})()
14 | ;hljs.registerLanguage("curl",e)})();
--------------------------------------------------------------------------------
/docs/assets/js/hljs.js:
--------------------------------------------------------------------------------
1 | window.document$.subscribe(() => {
2 | hljs.highlightAll();
3 | });
4 |
--------------------------------------------------------------------------------
/docs/customization/extending_controllers.md:
--------------------------------------------------------------------------------
1 | # Extending the Controllers
2 |
3 | ## Provided Controllers
4 |
5 | Shield has the following controllers that can be extended to handle
6 | various parts of the authentication process:
7 |
8 | - **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification.
9 | - **LoginController** handles the login process.
10 | - **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules.
11 | - **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to
12 | 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.
13 |
14 | ## How to Extend
15 |
16 | 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
17 | the existing controller and then only override the methods needed. This allows the other methods to stay up to date with any security
18 | updates that might happen in the controllers.
19 |
20 | ```php
21 | themedView($view, $data, $options);
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/customization/login_identifier.md:
--------------------------------------------------------------------------------
1 | # Customizing Login Identifier
2 |
3 | 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.
4 |
5 | This allows you to easily use phone numbers, employee or school IDs, etc. as the user identifier.
6 | You must implement the following steps to set this up.
7 |
8 | This only works with the Session authenticator.
9 |
10 | !!! note
11 |
12 | By default, Shield requires users to register their email and password.
13 | Further customization is required beyond the steps on this page to remove
14 | emails from user registrations.
15 |
16 | ## Create Migration File
17 |
18 | Create a [migration](http://codeigniter.com/user_guide/dbmgmt/migration.html) that
19 | adds a new column to the `users` table.
20 |
21 | ## Change $validFields
22 |
23 | Edit **app/Config/Auth.php** so that the new column you just created is within the
24 | `$validFields` array.
25 |
26 | ```php
27 | public array $validFields = [
28 | 'employee_id'
29 | ];
30 | ```
31 |
32 | If you have multiple login forms on your site that use different credentials, you
33 | must have all of the valid identifying fields in the array.
34 |
35 | ```php
36 | public array $validFields = [
37 | 'email',
38 | 'employee_id'
39 | ];
40 | ```
41 |
42 | ## Update Validation Rules
43 |
44 | !!! warning
45 |
46 | This is very important for security.
47 |
48 | You must write new **Validation Rules** and then set them using the
49 | [Customizing Validation Rules](./validation_rules.md#login) description.
50 |
51 | !!! note
52 |
53 | Not only the Validation Rules for **login**, but also the rules for
54 | [registration](./validation_rules.md#registration) should be updated. If you do
55 | not add the new **Validation Rules**, the new field will not be saved to the database.
56 |
57 | ## Customize Login View
58 |
59 | 1. Change the `login` view file in the **app/Config/Auth.php** file.
60 |
61 | ```php
62 | public array $views = [
63 | 'login' => '\App\Views\Shield\login',
64 | // ...
65 | ];
66 | ```
67 |
68 | 2. Copy file **vendor/codeigniter4/shield/src/Views/login.php** to **app/Views/Shield/login.php**.
69 | 3. Customize the login form to change the name of the default `email` input to the new field name.
70 |
71 | ```php
72 |
73 |
74 |
75 |
76 | ```
77 |
--------------------------------------------------------------------------------
/docs/customization/redirect_urls.md:
--------------------------------------------------------------------------------
1 | # Customizing Redirect URLs
2 |
3 | ## Customize Login Redirect
4 |
5 | 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.
6 |
7 | ```php
8 | public function loginRedirect(): string
9 | {
10 | $url = auth()->user()->inGroup('admin')
11 | ? '/admin'
12 | : setting('Auth.redirects')['login'];
13 |
14 | return $this->getUrl($url);
15 | }
16 | ```
17 |
18 | Oftentimes, you will want to have different redirects for different user groups. A simple example
19 | might be that you want admins redirected to `/admin` while all other groups redirect to `/`.
20 | The **app/Config/Auth.php** config file also includes methods that you can add additional logic to in order to
21 | achieve this:
22 |
23 | ```php
24 | public function loginRedirect(): string
25 | {
26 | if (auth()->user()->can('admin.access')) {
27 | return '/admin';
28 | }
29 |
30 | $url = setting('Auth.redirects')['login'];
31 |
32 | return $this->getUrl($url);
33 | }
34 | ```
35 |
36 | ## Customize Register Redirect
37 |
38 | You can customize where a user is redirected to after registration in the `registerRedirect()` method of the **app/Config/Auth.php** config file.
39 |
40 | ```php
41 | public function registerRedirect(): string
42 | {
43 | $url = setting('Auth.redirects')['register'];
44 |
45 | return $this->getUrl($url);
46 | }
47 | ```
48 |
49 | ## Customize Logout Redirect
50 |
51 | 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.
52 |
53 | ```php
54 | public function logoutRedirect(): string
55 | {
56 | $url = setting('Auth.redirects')['logout'];
57 |
58 | return $this->getUrl($url);
59 | }
60 | ```
61 |
--------------------------------------------------------------------------------
/docs/customization/table_names.md:
--------------------------------------------------------------------------------
1 | # Customizing Table Names
2 |
3 | If you want to change the default table names, you can change the table names
4 | in **app/Config/Auth.php**.
5 |
6 | ```php
7 | public array $tables = [
8 | 'users' => 'users',
9 | 'identities' => 'auth_identities',
10 | 'logins' => 'auth_logins',
11 | 'token_logins' => 'auth_token_logins',
12 | 'remember_tokens' => 'auth_remember_tokens',
13 | 'groups_users' => 'auth_groups_users',
14 | 'permissions_users' => 'auth_permissions_users',
15 | ];
16 | ```
17 |
18 | Set the table names that you want in the array values.
19 |
20 | !!! note
21 |
22 | You must change the table names before running database migrations.
23 |
--------------------------------------------------------------------------------
/docs/customization/user_provider.md:
--------------------------------------------------------------------------------
1 | # Customizing User Provider
2 |
3 | ## Creating Your Own UserModel
4 |
5 | If you want to customize user attributes, you need to create your own
6 | [User Provider](../getting_started/concepts.md#user-providers) class.
7 | The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`.
8 |
9 | Shield has a CLI command to quickly create a custom `UserModel` class by running the following
10 | command in the terminal:
11 |
12 | ```console
13 | php spark shield:model UserModel
14 | ```
15 |
16 | The class name is optional. If none is provided, the generated class name would be `UserModel`.
17 |
18 | ## Configuring to Use Your UserModel
19 |
20 | After creating the class, set your model classname to the `$userProvider` property
21 | in **app/Config/Auth.php**:
22 |
23 | ```php
24 | public string $userProvider = \App\Models\UserModel::class;
25 | ```
26 |
27 | ## Customizing Your UserModel
28 |
29 | Customize your model as you like.
30 |
31 | If you add attributes, don't forget to add the attributes to the `$allowedFields`
32 | property.
33 |
34 | ```php
35 | allowedFields = [
50 | ...$this->allowedFields,
51 | 'first_name', // Added
52 | 'last_name', // Added
53 | ];
54 | }
55 | }
56 | ```
57 |
58 | ## Creating a Custom User Entity
59 |
60 | Starting from v1.2.0, `UserModel` in Shield has the `createNewUser()` method to
61 | create a new User Entity.
62 |
63 | ```php
64 | $user = $userModel->createNewUser($data);
65 | ```
66 |
67 | It takes an optional user data array as the first argument, and passes it to the
68 | constructor of the `$returnType` class.
69 |
70 | If your custom User entity cannot be instantiated in this way, override this method.
71 |
--------------------------------------------------------------------------------
/docs/customization/views.md:
--------------------------------------------------------------------------------
1 | # Customizing Views
2 |
3 | Shield provides the default view files, but they are sample files.
4 | Customization is recommended.
5 |
6 | If your application uses a different method to convert view files to HTML than
7 | CodeIgniter's built-in `view()` helper, see
8 | [Integrating Custom View Libraries](./integrating_custom_view_libs.md).
9 |
10 | ## Change $views
11 |
12 | Change values in `$views` in the **app/Config/Auth.php** file.
13 |
14 | For example, if you customize the login page, change the value for `'login'`:
15 |
16 | ```php
17 | public array $views = [
18 | 'login' => '\App\Views\Shield\login', // changed this line.
19 | 'register' => '\CodeIgniter\Shield\Views\register',
20 | 'layout' => '\CodeIgniter\Shield\Views\layout',
21 | 'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show',
22 | 'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify',
23 | 'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email',
24 | 'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show',
25 | 'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email',
26 | 'magic-link-login' => '\CodeIgniter\Shield\Views\magic_link_form',
27 | 'magic-link-message' => '\CodeIgniter\Shield\Views\magic_link_message',
28 | 'magic-link-email' => '\CodeIgniter\Shield\Views\Email\magic_link_email',
29 | ];
30 | ```
31 |
32 | ## Copy View File
33 |
34 | Copy the file you want to customize in **vendor/codeigniter4/shield/src/Views/**
35 | to the **app/Views/Shield/** folder.
36 |
37 | ## Customize Content
38 |
39 | Customize the content of the view file in **app/Views/Shield/** as you like.
40 |
41 | When customizing email templates in **app/Views/Shield/Email**, you have access to the User Entity object through the `$user` variable. Utilize `$user` to personalize the email messages according to individual user details.
42 |
--------------------------------------------------------------------------------
/docs/getting_started/authenticators.md:
--------------------------------------------------------------------------------
1 | # Authenticators
2 |
3 | ## Authenticator List
4 |
5 | Shield provides the following Authenticators:
6 |
7 | - **Session** authenticator provides traditional ID/Password authentication.
8 | It uses username/email/password to authenticate against and stores the user
9 | information in the session. See [Using Session Authenticator](../quick_start_guide/using_session_auth.md)
10 | and [Session Authenticator](../references/authentication/session.md) for usage.
11 | - **AccessTokens** authenticator provides stateless authentication using Personal
12 | Access Tokens passed in the HTTP headers.
13 | See [Protecting an API with Access Tokens](../guides/api_tokens.md) and
14 | [Access Token Authenticator](../references/authentication/tokens.md) for usage.
15 | - **HmacSha256** authenticator provides stateless authentication using HMAC Keys.
16 | See [Protecting an API with HMAC Keys](../guides/api_hmac_keys.md) and
17 | [HMAC SHA256 Token Authenticator](../references/authentication/hmac.md) for usage.
18 | - **JWT** authenticator provides stateless authentication using JSON Web Token. To use this,
19 | you need additional setup. See [JWT Authentication](../addons/jwt.md).
20 |
--------------------------------------------------------------------------------
/docs/getting_started/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | ## Config files
4 |
5 | Shield has a lot of Config items. Change the default values as needed.
6 |
7 | If you have completed the setup according to this documentation, you will have
8 | the following configuration files:
9 |
10 | - **app/Config/Auth.php**
11 | - **app/Config/AuthGroups.php** - For [Authorization](../references/authorization.md)
12 | - **app/Config/AuthToken.php** - For [AccessTokens](../references/authentication/tokens.md#configuration) and [HmacSha256](../references/authentication/hmac.md#configuration) Authentication
13 | - **app/Config/AuthJWT.php** - For [JWT Authentication](../addons/jwt.md#configuration)
14 |
15 | Note that you do not need to have configuration files for features you do not use.
16 |
--------------------------------------------------------------------------------
/docs/guides/api_tokens.md:
--------------------------------------------------------------------------------
1 | # Protecting an API with Access Tokens
2 |
3 | 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.
4 |
5 | !!! note
6 |
7 | 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.
8 |
9 | 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.
10 |
11 | The `generateAccessToken()` 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, like 'Johns MacBook Air'.
12 |
13 | ```php
14 | $routes->get('access/token', static function() {
15 | $token = auth()->user()->generateAccessToken(service('request')->getVar('token_name'));
16 |
17 | return json_encode(['token' => $token->raw_token]);
18 | });
19 | ```
20 |
21 | You can access all of the user's tokens with the `accessTokens()` method on that user.
22 |
23 | ```php
24 | $tokens = $user->accessTokens();
25 | foreach($tokens as $token) {
26 | //
27 | }
28 | ```
29 |
30 | ## Token Permissions
31 |
32 | Access tokens can be given `scopes`, which are basically permission strings, for the token. This is generally not the same as the permission the user has, but is used to specify the permissions on the API itself. If not specified, the token is granted all access to all scopes. This might be enough for a smaller API.
33 |
34 | ```php
35 | return $user->generateAccessToken('token-name', ['users-read'])->raw_token;
36 | ```
37 |
38 | !!! note
39 |
40 | At this time, scope names should avoid using a colon (`:`) as this causes issues with the route filters being correctly recognized.
41 |
42 | When handling incoming requests you can check if the token has been granted access to the scope with the `tokenCan()` method.
43 |
44 | ```php
45 | if ($user->tokenCan('users-read')) {
46 | //
47 | }
48 | ```
49 |
50 | ### Revoking Tokens
51 |
52 | Tokens can be revoked by deleting them from the database with the `revokeAccessToken($rawToken)`, `revokeAccessTokenBySecret($secret)` or `revokeAllAccessTokens()` methods.
53 |
54 | ```php
55 | $user->revokeAccessToken($rawToken);
56 | $user->revokeAccessTokenBySecret($secret);
57 | $user->revokeAllAccessTokens();
58 | ```
59 |
60 | ## Protecting Routes
61 |
62 | The first way to specify which routes are protected is to use the `tokens` controller filter.
63 |
64 | For example, to ensure it protects all routes under the `/api` route group, you would use the `$filters` setting on **app/Config/Filters.php**.
65 |
66 | ```php
67 | public $filters = [
68 | 'tokens' => ['before' => ['api/*']],
69 | ];
70 | ```
71 |
72 | You can also specify the filter should run on one or more routes within the routes file itself:
73 |
74 | ```php
75 | $routes->group('api', ['filter' => 'tokens'], function($routes) {
76 | //
77 | });
78 | $routes->get('users', 'UserController::list', ['filter' => 'tokens:users-read']);
79 | ```
80 |
81 | 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.
82 |
83 | !!! note
84 |
85 | Currently only a single scope can be used on a route filter. If multiple scopes are passed in, only the first one is checked.
86 |
--------------------------------------------------------------------------------
/docs/guides/mobile_apps.md:
--------------------------------------------------------------------------------
1 | # Mobile Authentication with Access Tokens
2 |
3 | Access Tokens can be used to authenticate mobile applications that are consuming your API. This is similar to how you would work with [third-party users](./api_tokens.md) of your API, but with small differences in how you would issue the tokens.
4 |
5 | ## Issuing the Tokens
6 |
7 | Typically, a mobile application would issue a request from their login screen, passing in the credentials to authenticate with. Once authenticated you would return the `raw token` within the response and that would be saved on the device to use in following API calls.
8 |
9 | Start by creating a route that would handle the request from the login screen on the mobile device. The device name can be any arbitrary string, but is typically used to identify the device the request is being made from, like "Johns iPhone 13".
10 |
11 | ```php
12 | // Routes.php
13 | $routes->post('auth/token', '\App\Controllers\Auth\LoginController::mobileLogin');
14 | ```
15 |
16 | ```php
17 | config('Auth')->emailValidationRules,
32 | 'password' => [
33 | 'label' => 'Auth.password',
34 | 'rules' => 'required',
35 | ],
36 | 'device_name' => [
37 | 'label' => 'Device Name',
38 | 'rules' => 'required|string',
39 | ],
40 | ];
41 |
42 | if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) {
43 | return $this->response
44 | ->setJSON(['errors' => $this->validator->getErrors()])
45 | ->setStatusCode(401);
46 | }
47 |
48 | // Get the credentials for login
49 | $credentials = $this->request->getPost(setting('Auth.validFields'));
50 | $credentials = array_filter($credentials);
51 | $credentials['password'] = $this->request->getPost('password');
52 |
53 | // Attempt to login
54 | $result = auth()->attempt($credentials);
55 | if (! $result->isOK()) {
56 | return $this->response
57 | ->setJSON(['error' => $result->reason()])
58 | ->setStatusCode(401);
59 | }
60 |
61 | // Generate token and return to client
62 | $token = auth()->user()->generateAccessToken(service('request')->getVar('device_name'));
63 |
64 | return $this->response
65 | ->setJSON(['token' => $token->raw_token]);
66 | }
67 | }
68 | ```
69 |
70 | When making all future requests to the API, the mobile client should return the raw token in the `Authorization` header as a `Bearer` token.
71 |
72 | !!! note
73 |
74 | 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.
75 | 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.
76 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Shield Documentation
2 |
3 | ## What is Shield?
4 |
5 | Shield is the official authentication and authorization framework for CodeIgniter 4. While
6 | it does provide a base set of tools that are commonly used in websites, it is
7 | designed to be flexible and easily customizable.
8 |
9 | ### Primary Goals
10 |
11 | The primary goals for Shield are:
12 |
13 | 1. It must be very flexible and allow developers to extend/override almost any part of it.
14 | 2. It must have security at its core. It is an auth lib after all.
15 | 3. To cover many auth needs right out of the box, but be simple to add additional functionality to.
16 |
17 | ### Important Features
18 |
19 | - **Session-based Authentication** (traditional **ID/Password** with **Remember-me**)
20 | - **Stateless Authentication** using **Access Token**, **HMAC SHA256 Token**, or **JWT**
21 | - Optional **Email verification** on account registration
22 | - Optional **Email-based Two-Factor Authentication** after login
23 | - **Magic Link Login** when a user forgets their password
24 | - Flexible **Group-based Access Control** (think Roles, but more flexible), and users can be granted additional **Permissions**
25 | - A simple **Auth Helper** that provides access to the most common auth actions
26 | - 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
27 | - Highly configurable
28 | - **User Entity** and **User Provider** (`UserModel`) ready for you to use or extend
29 | - Built to extend and modify
30 | - Easily extendable controllers
31 | - All required views that can be used as is or swapped out for your own
32 |
33 | ### License
34 |
35 | Shield is licensed under the MIT License - see the [LICENSE](https://github.com/codeigniter4/shield/blob/develop/LICENSE) file for details.
36 |
37 | ### Acknowledgements
38 |
39 | Every open-source project depends on it's contributors to be a success. The following users have
40 | contributed in one manner or another in making Shield:
41 |
42 |
43 |
44 |
45 |
46 | Made with [contrib.rocks](https://contrib.rocks).
47 |
48 | The following articles/sites have been fundamental in shaping the security and best practices used
49 | within this library, in no particular order:
50 |
51 | - [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)
52 | - [NIST Digital Identity Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html)
53 | - [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)
54 | - [Password Storage - OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
55 |
--------------------------------------------------------------------------------
/docs/references/authentication/auth_actions.md:
--------------------------------------------------------------------------------
1 | # Authentication Actions
2 |
3 | Authentication Actions are a way to group actions that can happen after login or registration.
4 | Shield ships with two actions you can use, and makes it simple for you to define your own.
5 |
6 | 1. **Email-based Account Activation** (EmailActivate) confirms a new user's email address by
7 | sending them an email with a link they must follow in order to have their account activated.
8 | 2. **Email-based Two Factor Authentication** (Email2FA) will send a 6-digit code to the user's
9 | email address that they must confirm before they can continue.
10 |
11 | ## Configuring Actions
12 |
13 | Actions are setup in the `Auth` config file, with the `$actions` variable.
14 |
15 | ```php
16 | public array $actions = [
17 | 'register' => null,
18 | 'login' => null,
19 | ];
20 | ```
21 |
22 | To define an action to happen you will specify the class name as the value for the appropriate task:
23 |
24 | ```php
25 | public array $actions = [
26 | 'register' => \CodeIgniter\Shield\Authentication\Actions\EmailActivator::class,
27 | 'login' => \CodeIgniter\Shield\Authentication\Actions\Email2FA::class,
28 | ];
29 | ```
30 |
31 | You must register actions in the order of the actions to be performed.
32 | Once configured, everything should work out of the box.
33 |
34 | The routes are added with the basic `auth()->routes($routes)`
35 | call, but can be manually added if you choose not to use this helper method.
36 |
37 | ```php
38 | use CodeIgniter\Shield\Controllers\ActionController;
39 |
40 | $routes->get('auth/a/show', 'ActionController::show');
41 | $routes->post('auth/a/handle', 'ActionController::handle');
42 | $routes->post('auth/a/verify', 'ActionController::verify');
43 | ```
44 |
45 | Views for all of these pages are defined in the `Auth` config file, with the `$views` array.
46 |
47 | ```php
48 | public $views = [
49 | 'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show',
50 | 'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify',
51 | 'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email',
52 | 'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show',
53 | 'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email',
54 | ];
55 | ```
56 |
57 | ## Defining New Actions
58 |
59 | While the provided email-based activation and 2FA will work for many sites, others will have different
60 | needs, like using SMS to verify or something completely different. Custom actions must adhere to the following requirements:
61 |
62 | 1. The class name for a "register" action must end with the suffix `Activator` (e.g., `SMSActivator`) to ensure consistency.
63 | 2. All custom actions must implement the `CodeIgniter\Shield\Authentication\Actions\ActionInterface`.
64 |
65 | The `ActionInterface` defines three required methods that must be implemented to ensure the action integrates properly with the `ActionController`.
66 |
67 | **show()** should display the initial page the user lands on immediately after the authentication task,
68 | like login. It will typically display instructions to the user and provide an action to take, like
69 | clicking a button to have an email or SMS message sent. You might verify email address or phone numbers
70 | here.
71 |
72 | **handle()** is the next page the user would land on and can be used to handle the action the `show()`
73 | told the user would be happening. For example, in the `Email2FA` class, this method generates the code,
74 | sends the email to the user, and then displays the form the user should enter the 6 digit code into.
75 |
76 | **verify()** is the final step in the action's journey. It verifies the information the user provided
77 | and provides feedback. In the `Email2FA` class, it verifies the code against what is saved in the
78 | database and either sends them back to the previous form to try again or redirects the user to the
79 | page that a `login` task would have redirected them to anyway.
80 |
81 | All methods should return either a `Response` or a view string (e.g. using the `view()` function).
--------------------------------------------------------------------------------
/docs/references/authentication/authentication.md:
--------------------------------------------------------------------------------
1 | # Authentication
2 |
3 | Authentication is the process of determining that a visitor actually belongs to your website,
4 | and identifying them. Shield provides a flexible and secure authentication system for your
5 | web apps and APIs.
6 |
7 | ## Available Authenticators
8 |
9 | Shield ships with 4 authenticators that will serve several typical situations within web app development.
10 | You can see the [Authenticator List](../../getting_started/authenticators.md).
11 |
12 | The available authenticators are defined in `Config\Auth`:
13 |
14 | ```php
15 | public array $authenticators = [
16 | // alias => classname
17 | 'session' => Session::class,
18 | 'tokens' => AccessTokens::class,
19 | 'hmac' => HmacSha256::class,
20 | // 'jwt' => JWT::class,
21 | ];
22 | ```
23 |
24 | The default authenticator is also defined in the configuration file, and uses the alias given above:
25 |
26 | ```php
27 | public string $defaultAuthenticator = 'session';
28 | ```
29 |
30 | ## Auth Helper
31 |
32 | The auth functionality is designed to be used with the `auth_helper` that comes
33 | with Shield.
34 |
35 | !!! note
36 |
37 | The `auth_helper` is autoloaded by CodeIgniter's autoloader if you follow the
38 | installation instruction. If you want to *override* the functions, create
39 | **app/Helpers/auth_helper.php**.
40 |
41 | ### Getting the Current User
42 |
43 | The `auth()` function returns a convenient interface to the most frequently used
44 | functionality within the auth libraries.
45 |
46 | You can get the current `User` entity.
47 |
48 | ```php
49 | // get the current user
50 | $user = auth()->user();
51 |
52 | // get the current user's id
53 | $user_id = auth()->id();
54 | // or
55 | $user_id = user_id();
56 | ```
57 |
58 | The `user_id()` function returns the current user's id.
59 |
60 | ### Getting the User Provider
61 |
62 | You can also get the User Provider.
63 |
64 | ```php
65 | // get the User Provider (UserModel by default)
66 | $users = auth()->getProvider();
67 | ```
68 |
69 | ## Authenticator Responses
70 |
71 | Many of the authenticator methods will return a `CodeIgniter\Shield\Result` class. This provides a consistent
72 | way of checking the results and can have additional information returned along with it. The class
73 | has the following methods:
74 |
75 | ### isOK()
76 |
77 | Returns a boolean value stating whether the check was successful or not.
78 |
79 | ### reason()
80 |
81 | Returns a message that can be displayed to the user when the check fails.
82 |
83 | ### extraInfo()
84 |
85 | Can return a custom bit of information. These will be detailed in the method descriptions below.
86 |
--------------------------------------------------------------------------------
/docs/references/events.md:
--------------------------------------------------------------------------------
1 | # Events
2 |
3 | Shield fires off several events during the lifecycle of the application that your code can tap into.
4 |
5 | ## Responding to Events
6 |
7 | When you want to respond to an event that Shield publishes, you will need to add it to your **app/Config/Events.php**
8 | file. Each of the following events provides a sample for responding that uses a class and method name.
9 | Other methods are available. See the [CodeIgniter 4 User Guide](https://codeigniter.com/user_guide/extending/events.html)
10 | for more information.
11 |
12 | ### Event List
13 |
14 | #### register
15 |
16 | Triggered when a new user has registered in the system. The only argument is the `User` entity itself.
17 |
18 | ```php
19 | Events::trigger('register', $user);
20 |
21 | Events::on('register', 'SomeLibrary::handleRegister');
22 | ```
23 |
24 | #### login
25 |
26 | Fired immediately after a successful login. The only argument is the `User` entity.
27 |
28 | ```php
29 | Events::trigger('login', $user);
30 |
31 | Events::on('login', 'SomeLibrary::handleLogin');
32 | ```
33 |
34 | #### failedLogin
35 |
36 | Triggered when a login attempt fails. It provides an array containing the credentials the user attempted to
37 | sign in with, with the password removed from the array.
38 |
39 | ```php
40 | // Original credentials array
41 | $credentials = ['email' => 'foo@example.com', 'password' => 'secret123'];
42 |
43 | Events::on('failedLogin', function($credentials) {
44 | dd($credentials);
45 | });
46 |
47 | // Outputs: ['email' => 'foo@example.com'];
48 | ```
49 |
50 | When the magic link login fails, the following array will be provided:
51 |
52 | ```php
53 | ['magicLinkToken' => 'the token value used']
54 | ```
55 |
56 | #### logout
57 |
58 | Fired immediately after a successful logout. The only argument is the `User` entity.
59 |
60 | #### magicLogin
61 |
62 | Fired when a user has been successfully logged in via a magic link. This event does not have any parameters passed in. The authenticated user can be discovered through the `auth()` helper.
63 |
64 | ```php
65 | Events::on('magicLogin', function() {
66 | $user = auth()->user();
67 |
68 | //
69 | })
70 | ```
71 |
72 | ### Event Timing
73 |
74 | To learn more about Event timing, please see the list below.
75 |
76 | - [Session Authenticator](./authentication/session.md#events-and-logging).
77 |
--------------------------------------------------------------------------------
/docs/references/magic_link_login.md:
--------------------------------------------------------------------------------
1 | # Magic Link Login
2 |
3 | Magic Link Login is a feature that allows users to log in if they forget their
4 | password.
5 |
6 | ## Configuration
7 |
8 | ### Configure Magic Link Login Functionality
9 |
10 | Magic Link Login functionality is enabled by default.
11 | You can change it within the **app/Config/Auth.php** file.
12 |
13 | ```php
14 | public bool $allowMagicLinkLogins = true;
15 | ```
16 |
17 | ### Magic Link Lifetime
18 |
19 | By default, Magic Link can be used for 1 hour. This can be easily modified
20 | in the **app/Config/Auth.php** file.
21 |
22 | ```php
23 | public int $magicLinkLifetime = HOUR;
24 | ```
25 |
26 | ## Responding to Magic Link Logins
27 |
28 | !!! note
29 |
30 | You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup).
31 |
32 | 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.
33 |
34 | ### Session Notification
35 |
36 | 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`.
37 |
38 | ```php
39 | if (session('magicLogin')) {
40 | return redirect()->route('set_password');
41 | }
42 | ```
43 |
44 | 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.
45 |
46 | ```php
47 | session()->removeTempdata('magicLogin');
48 | ```
49 |
50 | ### Event
51 |
52 | 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.
53 |
54 | ```php
55 | Events::on('magicLogin', static function () {
56 | // ...
57 | });
58 | ```
59 |
--------------------------------------------------------------------------------
/docs/references/testing.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | ## HTTP Feature Testing
4 |
5 | When performing [HTTP Feature Testing](https://codeigniter.com/user_guide/testing/feature.html) in your applications, you
6 | will often need to ensure you are logged in to check security, or simply to access protected locations. Shield
7 | provides the `AuthenticationTesting` trait to help you out. Use it within the test class and then you can use
8 | the `actingAs()` method that takes a User instance. This user will be logged in during the test.
9 |
10 | ```php
11 | actingAs($this->user)
26 | ->withSession([
27 | 'auth_action' => Email2FA::class,
28 | ])->get('/auth/a/show');
29 |
30 | $result->assertStatus(200);
31 | // Should auto-populate in the form
32 | $result->assertSee($this->user->email);
33 | }
34 | }
35 | ```
36 |
37 | ## Improving the Speed of Running Tests
38 |
39 | By default, Shield has set the `Config\Auth::$hashCost = 12` due to the greater security of passwords. However, to increase the test execution time, we have set the `$hashCost = 4` for the test environment.
40 |
41 | If you use Shield in your project and your tests execution time is high, just set the `$hashCost = 4` in file **phpunit.xml.dist** of your project as follows:
42 |
43 | ```
44 |
45 |
46 |
47 |
48 | ```
--------------------------------------------------------------------------------
/docs/user_management/banning_users.md:
--------------------------------------------------------------------------------
1 | # Banning Users
2 |
3 | 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.
4 |
5 | !!! note
6 |
7 | Before using the following methods, you need to get the `User` entity. See
8 | [Getting the Current User](../references/authentication/authentication.md#getting-the-current-user)
9 | or [Finding a User](./managing_users.md#finding-a-user) for details.
10 |
11 | ### Check if a User is Banned
12 |
13 | You can check if a user is banned using `isBanned()` method on the `User` entity. The method returns a boolean `true`/`false`.
14 |
15 | ```php
16 | if ($user->isBanned()) {
17 | //...
18 | }
19 | ```
20 |
21 | ### Banning a User
22 |
23 | To ban a user from the application, the `ban(?string $message = null)` method can be called on the `User` entity. The method takes an optional string as a parameter. The string acts as the reason for the ban.
24 |
25 | ```php
26 | // banning a user without passing a message
27 | $user->ban();
28 | // banning a user with a message and reason for the ban passed.
29 | $user->ban('Your reason for banning the user here');
30 | ```
31 |
32 | ### Unbanning a User
33 |
34 | Unbanning a user can be done using the `unBan()` method on the `User` entity. This method will also reset the `status_message` property.
35 |
36 | ```php
37 | $user->unBan();
38 | ```
39 |
40 | ### Getting the Reason for Ban
41 |
42 | The reason for the ban can be obtained user the `getBanMessage()` method on the `User` entity.
43 |
44 | ```php
45 | $user->getBanMessage();
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/user_management/forcing_password_reset.md:
--------------------------------------------------------------------------------
1 | # Forcing Password Reset
2 |
3 | Depending on the scope of your application, there may be times when you'll decide
4 | that it is absolutely necessary to force user(s) to reset their password. This
5 | practice is common when you find out that users of your application do not use
6 | strong passwords OR there is a reasonable suspicion that their passwords have been
7 | compromised.
8 |
9 | This guide provides you with ways to achieve this.
10 |
11 | !!! note
12 |
13 | Before using the following methods, you need to get the `User` entity. See
14 | [Getting the Current User](../references/authentication/authentication.md#getting-the-current-user)
15 | or [Finding a User](./managing_users.md#finding-a-user) for details.
16 |
17 | ## Available Methods
18 |
19 | Shield provides a way to enforce password resets throughout your application.
20 | The `Resettable` trait on the `User` entity and the `UserIdentityModel` provides
21 | the following methods to do so.
22 |
23 | !!! note
24 |
25 | If a user is put into the force reset state, Shield does nothing by default.
26 | You need to check if a user requires password reset (see below), and set the
27 | redirect URL for the reset page, and create the reset page.
28 |
29 | ### Check if a User Requires Password Reset
30 |
31 | When you need to check if a user requires password reset, you can do so using the `requiresPasswordReset()` method on the `User` entity. Returns boolean `true`/`false`.
32 |
33 | ```php
34 | if ($user->requiresPasswordReset()) {
35 | //...
36 | }
37 | ```
38 |
39 | !!! note
40 |
41 | You can use the [force-reset](../references/controller_filters.md/#forcing-password-reset)
42 | filter to check.
43 |
44 | ### Force Password Reset On a User
45 |
46 | To force password reset on a user, you can do so using the `forcePasswordReset()` method on the `User` entity.
47 |
48 | ```php
49 | $user->forcePasswordReset();
50 | ```
51 |
52 | ### Remove Force Password Reset Flag On a User
53 |
54 | Undoing or removing the force password reset flag on a user can be done using the `undoForcePasswordReset()` method on the `User` entity.
55 |
56 | ```php
57 | $user->undoForcePasswordReset();
58 | ```
59 |
60 | ### Force Password Reset On Multiple Users
61 |
62 | If you see the need to force password reset for more than one user, the `forceMultiplePasswordReset()` method of the `UserIdentityModel` allows you to do this easily. It accepts an `Array` of user IDs.
63 |
64 | ```php
65 | use CodeIgniter\Shield\Models\UserIdentityModel;
66 |
67 | // ...
68 | $identities = new UserIdentityModel();
69 | $identities->forceMultiplePasswordReset([1,2,3,4]);
70 | ```
71 |
72 | ### Force Password Reset On All Users
73 |
74 | If you suspect a security breach or compromise in the passwords of your users, you can easily force password reset on all the users of your application using the `forceGlobalPasswordReset()` method of the `UserIdentityModel`.
75 |
76 | ```php
77 | use CodeIgniter\Shield\Models\UserIdentityModel;
78 |
79 | // ...
80 | $identities = new UserIdentityModel();
81 | $identities->forceGlobalPasswordReset();
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/user_management/managing_users.md:
--------------------------------------------------------------------------------
1 | # Managing Users
2 |
3 | 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.
4 |
5 | ## Managing Users by Code
6 |
7 | ### Finding a User
8 |
9 | You can find an existing user from the User Provider. It returns a `User`
10 | [entity](https://codeigniter.com/user_guide/models/entities.html).
11 |
12 | ```php
13 | // Get the User Provider (UserModel by default)
14 | $users = auth()->getProvider();
15 |
16 | // Find by the user_id
17 | $user = $users->findById(123);
18 | // Find by the user email
19 | $user = $users->findByCredentials(['email' => 'user@example.com']);
20 | ```
21 |
22 | ### Creating Users
23 |
24 | By default, the only values stored in the users table is the username.
25 |
26 | 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.
27 |
28 | ```php
29 | use CodeIgniter\Shield\Entities\User;
30 |
31 | // Get the User Provider (UserModel by default)
32 | $users = auth()->getProvider();
33 |
34 | $user = new User([
35 | 'username' => 'foo-bar',
36 | 'email' => 'foo.bar@example.com',
37 | 'password' => 'secret plain text password',
38 | ]);
39 | $users->save($user);
40 |
41 | // To get the complete user object with ID, we need to get from the database
42 | $user = $users->findById($users->getInsertID());
43 |
44 | // Add to default group
45 | $users->addToDefaultGroup($user);
46 | ```
47 |
48 | ### Deleting Users
49 |
50 | 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.
51 |
52 | You can delete a user like any other entity.
53 |
54 | ```php
55 | // Get the User Provider (UserModel by default)
56 | $users = auth()->getProvider();
57 |
58 | $users->delete($user->id, true);
59 | ```
60 |
61 | !!! note
62 |
63 | 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.
64 |
65 | ### Editing a User
66 |
67 | 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.
68 |
69 | ```php
70 | // Get the User Provider (UserModel by default)
71 | $users = auth()->getProvider();
72 |
73 | $user = $users->findById(123);
74 | $user->fill([
75 | 'username' => 'JoeSmith111',
76 | 'email' => 'joe.smith@example.com',
77 | 'password' => 'secret123'
78 | ]);
79 | $users->save($user);
80 | ```
81 |
82 | ## Managing Users via CLI
83 |
84 | Shield has a CLI command to manage users. You can do the following actions:
85 |
86 | ```text
87 | create: Create a new user
88 | activate: Activate a user
89 | deactivate: Deactivate a user
90 | changename: Change user name
91 | changeemail: Change user email
92 | delete: Delete a user
93 | password: Change a user password
94 | list: List users
95 | addgroup: Add a user to a group
96 | removegroup: Remove a user from a group
97 | ```
98 |
99 | You can get help on how to use it by running the following command in a terminal:
100 |
101 | ```console
102 | php spark shield:user --help
103 | ```
104 |
--------------------------------------------------------------------------------
/roave-bc-check.yaml:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | - '#\[BC\] SKIPPED: .+ could not be found in the located source#'
4 |
--------------------------------------------------------------------------------
/src/Authentication/Actions/ActionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Actions;
15 |
16 | use CodeIgniter\HTTP\IncomingRequest;
17 | use CodeIgniter\HTTP\Response;
18 | use CodeIgniter\Shield\Entities\User;
19 |
20 | /**
21 | * Interface ActionInterface
22 | *
23 | * Authentication Actions are steps that can happen after
24 | * the main authentication steps, like registration and login.
25 | * They can be email activation steps, SMS-based 2FA, etc.
26 | */
27 | interface ActionInterface
28 | {
29 | /**
30 | * Shows the initial screen to the user to start the flow.
31 | * This might be asking for the user's email to reset a password,
32 | * or asking for a cell-number for a 2FA.
33 | *
34 | * @return Response|string
35 | */
36 | public function show();
37 |
38 | /**
39 | * Processes the form that was displayed in the previous form.
40 | *
41 | * @return Response|string
42 | */
43 | public function handle(IncomingRequest $request);
44 |
45 | /**
46 | * This handles the response after the user takes action
47 | * in response to the show/handle flow. This might be
48 | * from clicking the 'confirm my email' action or
49 | * following entering a code sent in an SMS.
50 | *
51 | * @return Response|string
52 | */
53 | public function verify(IncomingRequest $request);
54 |
55 | /**
56 | * Returns the string type of the action class.
57 | * E.g., 'email_2fa', 'email_activate'.
58 | */
59 | public function getType(): string;
60 |
61 | /**
62 | * Creates an identity for the action of the user.
63 | *
64 | * @return string secret
65 | */
66 | public function createIdentity(User $user): string;
67 | }
68 |
--------------------------------------------------------------------------------
/src/Authentication/Authentication.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication;
15 |
16 | use CodeIgniter\Shield\Config\Auth as AuthConfig;
17 | use CodeIgniter\Shield\Models\UserModel;
18 |
19 | /**
20 | * Factory for Authenticators.
21 | */
22 | class Authentication
23 | {
24 | /**
25 | * Instantiated Authenticator objects,
26 | * stored by Authenticator alias.
27 | *
28 | * @var array [Authenticator_alias => Authenticator_instance]
29 | */
30 | protected array $instances = [];
31 |
32 | protected ?UserModel $userProvider = null;
33 |
34 | public function __construct(protected AuthConfig $config)
35 | {
36 | }
37 |
38 | /**
39 | * Creates and returns the shared instance of the specified Authenticator.
40 | *
41 | * @param string|null $alias Authenticator alias. Passing `null` returns the
42 | * default authenticator.
43 | *
44 | * @throws AuthenticationException
45 | */
46 | public function factory(?string $alias = null): AuthenticatorInterface
47 | {
48 | // Determine actual Authenticator alias
49 | $alias ??= $this->config->defaultAuthenticator;
50 |
51 | // Return the cached instance if we have it
52 | if (! empty($this->instances[$alias])) {
53 | return $this->instances[$alias];
54 | }
55 |
56 | // Otherwise, try to create a new instance.
57 | if (! array_key_exists($alias, $this->config->authenticators)) {
58 | throw AuthenticationException::forUnknownAuthenticator($alias);
59 | }
60 |
61 | $className = $this->config->authenticators[$alias];
62 |
63 | assert($this->userProvider !== null, 'You must set $this->userProvider.');
64 |
65 | $this->instances[$alias] = new $className($this->userProvider);
66 |
67 | return $this->instances[$alias];
68 | }
69 |
70 | /**
71 | * Sets the User Provider to use.
72 | *
73 | * @return $this
74 | */
75 | public function setProvider(UserModel $provider): self
76 | {
77 | $this->userProvider = $provider;
78 |
79 | return $this;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Authentication/AuthenticationException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication;
15 |
16 | use CodeIgniter\HTTP\Exceptions\HTTPException;
17 | use CodeIgniter\Shield\Exceptions\RuntimeException;
18 |
19 | class AuthenticationException extends RuntimeException
20 | {
21 | protected $code = 403;
22 |
23 | /**
24 | * @param string $alias Authenticator alias
25 | */
26 | public static function forUnknownAuthenticator(string $alias): self
27 | {
28 | return new self(lang('Auth.unknownAuthenticator', [$alias]));
29 | }
30 |
31 | public static function forUnknownUserProvider(): self
32 | {
33 | return new self(lang('Auth.unknownUserProvider'));
34 | }
35 |
36 | public static function forInvalidUser(): self
37 | {
38 | return new self(lang('Auth.invalidUser'));
39 | }
40 |
41 | public static function forBannedUser(): self
42 | {
43 | return new self(lang('Auth.invalidUser'));
44 | }
45 |
46 | public static function forNoEntityProvided(): self
47 | {
48 | return new self(lang('Auth.noUserEntity'), 500);
49 | }
50 |
51 | /**
52 | * Fires when no minimumPasswordLength has been set
53 | * in the Auth config file.
54 | */
55 | public static function forUnsetPasswordLength(): self
56 | {
57 | return new self(lang('Auth.unsetPasswordLength'), 500);
58 | }
59 |
60 | /**
61 | * When the cURL request (to Have I Been Pwned) in PwnedValidator
62 | * throws a HTTPException it is re-thrown as this one
63 | */
64 | public static function forHIBPCurlFail(HTTPException $e): self
65 | {
66 | return new self($e->getMessage(), $e->getCode(), $e);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Authentication/AuthenticatorInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 | use CodeIgniter\Shield\Result;
18 |
19 | interface AuthenticatorInterface
20 | {
21 | /**
22 | * Attempts to authenticate a user with the given $credentials.
23 | * Logs the user in with a successful check.
24 | *
25 | * @throws AuthenticationException
26 | */
27 | public function attempt(array $credentials): Result;
28 |
29 | /**
30 | * Checks a user's $credentials to see if they match an
31 | * existing user.
32 | */
33 | public function check(array $credentials): Result;
34 |
35 | /**
36 | * Checks if the user is currently logged in.
37 | */
38 | public function loggedIn(): bool;
39 |
40 | /**
41 | * Logs the given user in.
42 | * On success this must trigger the "login" Event.
43 | *
44 | * @see https://codeigniter4.github.io/CodeIgniter4/extending/authentication.html
45 | */
46 | public function login(User $user): void;
47 |
48 | /**
49 | * Logs a user in based on their ID.
50 | * On success this must trigger the "login" Event.
51 | *
52 | * @see https://codeigniter4.github.io/CodeIgniter4/extending/authentication.html
53 | *
54 | * @param int|string $userId
55 | */
56 | public function loginById($userId): void;
57 |
58 | /**
59 | * Logs the current user out.
60 | * On success this must trigger the "logout" Event.
61 | *
62 | * @see https://codeigniter4.github.io/CodeIgniter4/extending/authentication.html
63 | */
64 | public function logout(): void;
65 |
66 | /**
67 | * Returns the currently logged in user.
68 | */
69 | public function getUser(): ?User;
70 |
71 | /**
72 | * Updates the user's last active date.
73 | */
74 | public function recordActiveDate(): void;
75 | }
76 |
--------------------------------------------------------------------------------
/src/Authentication/JWT/Exceptions/InvalidTokenException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\JWT\Exceptions;
15 |
16 | use CodeIgniter\Shield\Exceptions\ValidationException;
17 | use Exception;
18 |
19 | class InvalidTokenException extends ValidationException
20 | {
21 | public const INVALID_TOKEN = 1;
22 | public const EXPIRED_TOKEN = 2;
23 | public const BEFORE_VALID_TOKEN = 3;
24 |
25 | public static function forInvalidToken(Exception $e): self
26 | {
27 | return new self(lang('Auth.invalidJWT'), self::INVALID_TOKEN, $e);
28 | }
29 |
30 | public static function forExpiredToken(Exception $e): self
31 | {
32 | return new self(lang('Auth.expiredJWT'), self::EXPIRED_TOKEN, $e);
33 | }
34 |
35 | public static function forBeforeValidToken(Exception $e): self
36 | {
37 | return new self(lang('Auth.beforeValidJWT'), self::BEFORE_VALID_TOKEN, $e);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Authentication/JWT/JWSAdapterInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\JWT;
15 |
16 | use stdClass;
17 |
18 | interface JWSAdapterInterface
19 | {
20 | /**
21 | * Issues Signed JWT (JWS)
22 | *
23 | * @param array $payload The payload.
24 | * @param string $keyset The key group.
25 | * The array key of Config\AuthJWT::$keys.
26 | * @param array|null $headers An array with header elements to attach.
27 | *
28 | * @return string JWT (JWS)
29 | */
30 | public function encode(array $payload, $keyset, ?array $headers = null): string;
31 |
32 | /**
33 | * Decode Signed JWT (JWS)
34 | *
35 | * @param string $keyset The key group. The array key of Config\AuthJWT::$keys.
36 | *
37 | * @return stdClass Payload
38 | */
39 | public function decode(string $encodedToken, $keyset): stdClass;
40 | }
41 |
--------------------------------------------------------------------------------
/src/Authentication/JWT/JWSDecoder.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\JWT;
15 |
16 | use CodeIgniter\Shield\Authentication\JWT\Adapters\FirebaseAdapter;
17 | use stdClass;
18 |
19 | class JWSDecoder
20 | {
21 | /**
22 | * @var string The key group. The array key of Config\AuthJWT::$keys.
23 | */
24 | protected $keyset = 'default';
25 |
26 | public function __construct(private ?JWSAdapterInterface $jwsAdapter = null)
27 | {
28 | $this->jwsAdapter = $jwsAdapter ?? new FirebaseAdapter();
29 | }
30 |
31 | /**
32 | * Returns payload of the JWT
33 | *
34 | * @param string $keyset The key group. The array key of Config\AuthJWT::$keys.
35 | */
36 | public function decode(string $encodedToken, $keyset = 'default'): stdClass
37 | {
38 | return $this->jwsAdapter->decode($encodedToken, $keyset);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Authentication/JWT/JWSEncoder.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\JWT;
15 |
16 | use CodeIgniter\I18n\Time;
17 | use CodeIgniter\Shield\Authentication\JWT\Adapters\FirebaseAdapter;
18 | use CodeIgniter\Shield\Config\AuthJWT;
19 |
20 | class JWSEncoder
21 | {
22 | public function __construct(protected ?JWSAdapterInterface $jwsAdapter = null, protected ?Time $clock = null)
23 | {
24 | $this->jwsAdapter = $jwsAdapter ?? new FirebaseAdapter();
25 | $this->clock = $clock ?? new Time();
26 | }
27 |
28 | /**
29 | * Issues Signed JWT (JWS)
30 | *
31 | * @param array $claims The payload items.
32 | * @param int|null $ttl Time to live in seconds.
33 | * @param string $keyset The key group.
34 | * The array key of Config\AuthJWT::$keys.
35 | * @param array|null $headers An array with header elements to attach.
36 | */
37 | public function encode(
38 | array $claims,
39 | ?int $ttl = null,
40 | $keyset = 'default',
41 | ?array $headers = null,
42 | ): string {
43 | assert(
44 | (array_key_exists('exp', $claims) && ($ttl !== null)) === false,
45 | 'Cannot pass $claims[\'exp\'] and $ttl at the same time.',
46 | );
47 |
48 | /** @var AuthJWT $config */
49 | $config = config('AuthJWT');
50 |
51 | $payload = array_merge(
52 | $config->defaultClaims,
53 | $claims,
54 | );
55 |
56 | if (! array_key_exists('iat', $claims)) {
57 | $payload['iat'] = $this->clock->now()->getTimestamp();
58 | }
59 |
60 | if (! array_key_exists('exp', $claims)) {
61 | $payload['exp'] = $payload['iat'] + $config->timeToLive;
62 | }
63 |
64 | if ($ttl !== null) {
65 | $payload['exp'] = $payload['iat'] + $ttl;
66 | }
67 |
68 | return $this->jwsAdapter->encode(
69 | $payload,
70 | $keyset,
71 | $headers,
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Authentication/JWTManager.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication;
15 |
16 | use CodeIgniter\I18n\Time;
17 | use CodeIgniter\Shield\Authentication\JWT\JWSDecoder;
18 | use CodeIgniter\Shield\Authentication\JWT\JWSEncoder;
19 | use CodeIgniter\Shield\Entities\User;
20 | use stdClass;
21 |
22 | /**
23 | * JWT Manager
24 | */
25 | class JWTManager
26 | {
27 | public function __construct(
28 | protected ?Time $clock = null,
29 | protected ?JWSEncoder $jwsEncoder = null,
30 | protected ?JWSDecoder $jwsDecoder = null,
31 | ) {
32 | $this->clock = $clock ?? new Time();
33 | $this->jwsEncoder = $jwsEncoder ?? new JWSEncoder(null, $this->clock);
34 | $this->jwsDecoder = $jwsDecoder ?? new JWSDecoder();
35 | }
36 |
37 | /**
38 | * Issues Signed JWT (JWS) for a User
39 | *
40 | * @param array $claims The payload items.
41 | * @param int|null $ttl Time to live in seconds.
42 | * @param string $keyset The key group.
43 | * The array key of Config\AuthJWT::$keys.
44 | * @param array|null $headers An array with header elements to attach.
45 | */
46 | public function generateToken(
47 | User $user,
48 | array $claims = [],
49 | ?int $ttl = null,
50 | $keyset = 'default',
51 | ?array $headers = null,
52 | ): string {
53 | $payload = array_merge(
54 | $claims,
55 | [
56 | 'sub' => (string) $user->id, // subject
57 | ],
58 | );
59 |
60 | return $this->issue($payload, $ttl, $keyset, $headers);
61 | }
62 |
63 | /**
64 | * Issues Signed JWT (JWS)
65 | *
66 | * @param array $claims The payload items.
67 | * @param int|null $ttl Time to live in seconds.
68 | * @param string $keyset The key group.
69 | * The array key of Config\AuthJWT::$keys.
70 | * @param array|null $headers An array with header elements to attach.
71 | */
72 | public function issue(
73 | array $claims,
74 | ?int $ttl = null,
75 | $keyset = 'default',
76 | ?array $headers = null,
77 | ): string {
78 | return $this->jwsEncoder->encode($claims, $ttl, $keyset, $headers);
79 | }
80 |
81 | /**
82 | * Returns payload of the JWT
83 | *
84 | * @param string $keyset The key group. The array key of Config\AuthJWT::$keys.
85 | */
86 | public function parse(string $encodedToken, $keyset = 'default'): stdClass
87 | {
88 | return $this->jwsDecoder->decode($encodedToken, $keyset);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/BaseValidator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\Shield\Config\Auth as AuthConfig;
17 |
18 | class BaseValidator
19 | {
20 | protected ?string $error = null;
21 | protected ?string $suggestion = null;
22 |
23 | public function __construct(protected AuthConfig $config)
24 | {
25 | }
26 |
27 | /**
28 | * Returns the error string that should be displayed to the user.
29 | */
30 | public function error(): ?string
31 | {
32 | return $this->error;
33 | }
34 |
35 | /**
36 | * Returns a suggestion that may be displayed to the user
37 | * to help them choose a better password. The method is
38 | * required, but a suggestion is optional. May return
39 | * null instead.
40 | */
41 | public function suggestion(): ?string
42 | {
43 | return $this->suggestion;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/CompositionValidator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\Shield\Authentication\AuthenticationException;
17 | use CodeIgniter\Shield\Entities\User;
18 | use CodeIgniter\Shield\Result;
19 |
20 | /**
21 | * Class CompositionValidator
22 | *
23 | * Checks the general makeup of the password.
24 | *
25 | * While older composition checks might have included different character
26 | * groups that you had to include, current NIST standards prefer to simply
27 | * set a minimum length and a long maximum (128+ chars).
28 | *
29 | * @see https://pages.nist.gov/800-63-3/sp800-63b.html#sec5
30 | */
31 | class CompositionValidator extends BaseValidator implements ValidatorInterface
32 | {
33 | /**
34 | * Returns true when the password passes this test.
35 | * The password will be passed to any remaining validators.
36 | * False will immediately stop validation process
37 | */
38 | public function check(string $password, ?User $user = null): Result
39 | {
40 | if ($this->config->minimumPasswordLength === 0) {
41 | throw AuthenticationException::forUnsetPasswordLength();
42 | }
43 |
44 | $passed = mb_strlen($password, 'UTF-8') >= $this->config->minimumPasswordLength;
45 |
46 | if (! $passed) {
47 | return new Result([
48 | 'success' => false,
49 | 'reason' => lang('Auth.errorPasswordLength', [$this->config->minimumPasswordLength]),
50 | 'extraInfo' => lang('Auth.suggestPasswordLength'),
51 | ]);
52 | }
53 |
54 | return new Result([
55 | 'success' => true,
56 | ]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/DictionaryValidator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 | use CodeIgniter\Shield\Result;
18 |
19 | /**
20 | * Class DictionaryValidator
21 | *
22 | * Checks passwords against a list of 65k commonly used passwords
23 | * that was compiled by InfoSec.
24 | */
25 | class DictionaryValidator extends BaseValidator implements ValidatorInterface
26 | {
27 | /**
28 | * Checks the password against the words in the file and returns false
29 | * if a match is found. Returns true if no match is found.
30 | * If true is returned the password will be passed to next validator.
31 | * If false is returned the validation process will be immediately stopped.
32 | */
33 | public function check(string $password, ?User $user = null): Result
34 | {
35 | // Loop over our file
36 | $fp = fopen(__DIR__ . '/_dictionary.txt', 'rb');
37 | if ($fp) {
38 | while (($line = fgets($fp, 4096)) !== false) {
39 | if ($password === trim($line)) {
40 | fclose($fp);
41 |
42 | return new Result([
43 | 'success' => false,
44 | 'reason' => lang('Auth.errorPasswordCommon'),
45 | 'extraInfo' => lang('Auth.suggestPasswordCommon'),
46 | ]);
47 | }
48 | }
49 | }
50 |
51 | fclose($fp);
52 |
53 | return new Result([
54 | 'success' => true,
55 | ]);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/PwnedValidator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\Config\Services;
17 | use CodeIgniter\HTTP\Exceptions\HTTPException;
18 | use CodeIgniter\Shield\Authentication\AuthenticationException;
19 | use CodeIgniter\Shield\Entities\User;
20 | use CodeIgniter\Shield\Result;
21 |
22 | /**
23 | * Class PwnedValidator
24 | *
25 | * Checks if the password has been compromised by checking against
26 | * an online database of over 555 million stolen passwords.
27 | *
28 | * @see https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/
29 | *
30 | * NIST recommend to check passwords against those obtained from previous data breaches.
31 | * @see https://pages.nist.gov/800-63-3/sp800-63b.html#sec5
32 | */
33 | class PwnedValidator extends BaseValidator implements ValidatorInterface
34 | {
35 | /**
36 | * Checks the password against the online database and
37 | * returns false if a match is found. Returns true if no match is found.
38 | * If true is returned the password will be passed to next validator.
39 | * If false is returned the validation process will be immediately stopped.
40 | *
41 | * @throws AuthenticationException
42 | */
43 | public function check(string $password, ?User $user = null): Result
44 | {
45 | $hashedPword = strtoupper(sha1($password));
46 | $rangeHash = substr($hashedPword, 0, 5);
47 | /** @var string $searchHash */
48 | $searchHash = substr($hashedPword, 5);
49 |
50 | try {
51 | $client = Services::curlrequest([
52 | 'base_uri' => 'https://api.pwnedpasswords.com/',
53 | ]);
54 |
55 | $response = $client->get(
56 | 'range/' . $rangeHash,
57 | ['headers' => ['Accept' => 'text/plain']],
58 | );
59 | } catch (HTTPException $e) {
60 | $exception = AuthenticationException::forHIBPCurlFail($e);
61 | log_message('error', '[ERROR] {exception}', ['exception' => $exception]);
62 |
63 | throw $exception;
64 | }
65 |
66 | $range = $response->getBody();
67 | $startPos = strpos((string) $range, $searchHash);
68 | if ($startPos === false) {
69 | return new Result([
70 | 'success' => true,
71 | ]);
72 | }
73 |
74 | $startPos += 36; // right after the delimiter (:)
75 | $endPos = strpos((string) $range, "\r\n", $startPos);
76 | $hits = $endPos !== false ? (int) substr((string) $range, $startPos, $endPos - $startPos) : (int) substr((string) $range, $startPos);
77 |
78 | $wording = $hits > 1 ? 'databases' : 'a database';
79 |
80 | return new Result([
81 | 'success' => false,
82 | 'reason' => lang('Auth.errorPasswordPwned', [$password, $hits, $wording]),
83 | 'extraInfo' => lang('Auth.suggestPasswordPwned', [$password]),
84 | ]);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/ValidationRules.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\HTTP\IncomingRequest;
17 | use CodeIgniter\Shield\Authentication\Passwords;
18 | use CodeIgniter\Shield\Entities\User;
19 |
20 | /**
21 | * Class ValidationRules
22 | *
23 | * Provides auth-related validation rules for CodeIgniter 4.
24 | *
25 | * To use, add this class to Config/Validation.php, in the
26 | * $rulesets array.
27 | */
28 | class ValidationRules
29 | {
30 | /**
31 | * A validation helper method to check if the passed in
32 | * password will pass all of the validators currently defined.
33 | *
34 | * Handy for use in validation, but you will get a slightly
35 | * better security if this is done manually, since you can
36 | * personalize based on a specific user at that point.
37 | *
38 | * @param string $value Field value
39 | * @param string $error1 Error that will be returned (for call without validation data array)
40 | * @param array $data Validation data array
41 | * @param string $error2 Error that will be returned (for call with validation data array)
42 | */
43 | public function strong_password(string $value, ?string &$error1 = null, array $data = [], ?string &$error2 = null): bool
44 | {
45 | /** @var Passwords $checker */
46 | $checker = service('passwords');
47 |
48 | if (function_exists('auth') && auth()->user()) {
49 | $user = auth()->user();
50 | } else {
51 | /** @phpstan-ignore-next-line */
52 | $user = $data === [] ? $this->buildUserFromRequest() : $this->buildUserFromData($data);
53 | }
54 |
55 | $result = $checker->check($value, $user);
56 |
57 | if (! $result->isOK()) {
58 | if ($data === []) {
59 | $error1 = $result->reason();
60 | } else {
61 | $error2 = $result->reason();
62 | }
63 | }
64 |
65 | return $result->isOK();
66 | }
67 |
68 | /**
69 | * Returns true if $str is $val or fewer bytes in length.
70 | */
71 | public function max_byte(?string $str, string $val): bool
72 | {
73 | return is_numeric($val) && $val >= strlen($str ?? '');
74 | }
75 |
76 | /**
77 | * Builds a new user instance from the global request.
78 | *
79 | * @deprecated This will be removed soon.
80 | *
81 | * @see https://github.com/codeigniter4/shield/pull/747#discussion_r1198778666
82 | */
83 | protected function buildUserFromRequest(): User
84 | {
85 | $fields = $this->prepareValidFields();
86 |
87 | /** @var IncomingRequest $request */
88 | $request = service('request');
89 |
90 | $data = $request->getPost($fields);
91 |
92 | return new User($data);
93 | }
94 |
95 | /**
96 | * Builds a new user instance from assigned data..
97 | *
98 | * @param array $data Assigned data
99 | */
100 | protected function buildUserFromData(array $data = []): User
101 | {
102 | $fields = $this->prepareValidFields();
103 |
104 | $data = array_intersect_key($data, array_fill_keys($fields, null));
105 |
106 | return new User($data);
107 | }
108 |
109 | /**
110 | * Prepare valid user fields
111 | */
112 | protected function prepareValidFields(): array
113 | {
114 | $config = config('Auth');
115 | $fields = array_merge($config->validFields, $config->personalFields, ['email', 'password']);
116 |
117 | return array_unique($fields);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/ValidatorInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authentication\Passwords;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 | use CodeIgniter\Shield\Result;
18 |
19 | /**
20 | * Interface ValidatorInterface
21 | *
22 | * Forms the
23 | */
24 | interface ValidatorInterface
25 | {
26 | /**
27 | * Checks the password and returns true/false
28 | * if it passes muster. Must return either true/false.
29 | * True means the password passes this test and
30 | * the password will be passed to any remaining validators.
31 | * False will immediately stop validation process
32 | */
33 | public function check(string $password, ?User $user = null): Result;
34 |
35 | /**
36 | * Returns the error string that should be displayed to the user.
37 | */
38 | public function error(): ?string;
39 |
40 | /**
41 | * Returns a suggestion that may be displayed to the user
42 | * to help them choose a better password. The method is
43 | * required, but a suggestion is optional. May return
44 | * null instead.
45 | */
46 | public function suggestion(): ?string;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Authentication/Passwords/_dictionary.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeigniter4/shield/81b69bf9985f71f3a64d9274980acc85d0d0ceb7/src/Authentication/Passwords/_dictionary.txt
--------------------------------------------------------------------------------
/src/Authorization/AuthorizationException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authorization;
15 |
16 | use CodeIgniter\Shield\Exceptions\RuntimeException;
17 |
18 | class AuthorizationException extends RuntimeException
19 | {
20 | protected $code = 401;
21 |
22 | public static function forUnknownGroup(string $group): self
23 | {
24 | return new self(lang('Auth.unknownGroup', [$group]));
25 | }
26 |
27 | public static function forUnknownPermission(string $permission): self
28 | {
29 | return new self(lang('Auth.unknownPermission', [$permission]));
30 | }
31 |
32 | public static function forUnauthorized(): self
33 | {
34 | return new self(lang('Auth.notEnoughPrivilege'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Authorization/Groups.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Authorization;
15 |
16 | use CodeIgniter\Shield\Entities\Group;
17 | use CodeIgniter\Shield\Exceptions\RuntimeException;
18 |
19 | /**
20 | * Provides utility feature for working with
21 | * groups, adding permissions, etc.
22 | */
23 | class Groups
24 | {
25 | /**
26 | * Grabs a group info from settings.
27 | */
28 | public function info(string $group): ?Group
29 | {
30 | $info = setting('AuthGroups.groups')[strtolower($group)] ?? null;
31 |
32 | if (empty($info)) {
33 | return null;
34 | }
35 |
36 | $info['alias'] = $group;
37 |
38 | return new Group($info);
39 | }
40 |
41 | /**
42 | * Saves or creates the group.
43 | */
44 | public function save(Group $group): void
45 | {
46 | if (empty($group->title)) {
47 | throw new RuntimeException(lang('Auth.missingTitle'));
48 | }
49 |
50 | $groups = setting('AuthGroups.groups');
51 |
52 | $alias = $group->alias;
53 |
54 | if (empty($alias)) {
55 | $alias = strtolower(url_title($group->title));
56 | }
57 |
58 | $groups[$alias] = [
59 | 'title' => $group->title,
60 | 'description' => $group->description,
61 | ];
62 |
63 | // Save it
64 | setting('AuthGroups.groups', $groups);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Collectors/Auth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Collectors;
15 |
16 | use CodeIgniter\Debug\Toolbar\Collectors\BaseCollector;
17 | use CodeIgniter\Shield\Auth as ShieldAuth;
18 |
19 | /**
20 | * Debug Toolbar Collector for Auth
21 | */
22 | class Auth extends BaseCollector
23 | {
24 | /**
25 | * Whether this collector has data that can
26 | * be displayed in the Timeline.
27 | *
28 | * @var bool
29 | */
30 | protected $hasTimeline = false;
31 |
32 | /**
33 | * Whether this collector needs to display
34 | * content in a tab or not.
35 | *
36 | * @var bool
37 | */
38 | protected $hasTabContent = true;
39 |
40 | /**
41 | * Whether this collector has data that
42 | * should be shown in the Vars tab.
43 | *
44 | * @var bool
45 | */
46 | protected $hasVarData = false;
47 |
48 | /**
49 | * The 'title' of this Collector.
50 | * Used to name things in the toolbar HTML.
51 | *
52 | * @var string
53 | */
54 | protected $title = 'Auth';
55 |
56 | private readonly ShieldAuth $auth;
57 |
58 | public function __construct()
59 | {
60 | $this->auth = service('auth');
61 | }
62 |
63 | /**
64 | * Returns any information that should be shown next to the title.
65 | */
66 | public function getTitleDetails(): string
67 | {
68 | return ShieldAuth::SHIELD_VERSION . ' | ' . $this->auth->getAuthenticator()::class;
69 | }
70 |
71 | /**
72 | * Returns the data of this collector to be formatted in the toolbar
73 | */
74 | public function display(): string
75 | {
76 | if ($this->auth->loggedIn()) {
77 | $user = $this->auth->user();
78 | $groups = implode(', ', $user->getGroups());
79 | $permissions = implode(', ', $user->getPermissions());
80 |
81 | return <<Current User
83 |
84 |
85 |
User ID
#{$user->id}
86 |
Username
{$user->username}
87 |
Email
{$user->email}
88 |
Groups
{$groups}
89 |
Permissions
{$permissions}
90 |
91 |
92 | HTML;
93 | }
94 |
95 | return '
Not logged in.
';
96 | }
97 |
98 | /**
99 | * Gets the "badge" value for the button.
100 | *
101 | * @return int|string|null ID of the current User, or null when not logged in
102 | */
103 | public function getBadgeValue()
104 | {
105 | return $this->auth->loggedIn() ? $this->auth->id() : null;
106 | }
107 |
108 | /**
109 | * Display the icon.
110 | *
111 | * Icon from https://icons8.com - 1em package
112 | */
113 | public function icon(): string
114 | {
115 | return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADLSURBVEhL5ZRLCsIwGAa7UkE9gd5HUfEoekxxJx7AhXoCca/fhESkJiQxBHwMDG3S/9EmJc0n0JMruZVXK/fMdWQRY7mXt4A7OZJvwZu74hRayIEc2nv3jGtXZrOWrnifiRY0OkhiWK5sWGeS52bkZymJ2ZhRJmwmySxLCL6CmIsZZUIixkiNezCRR+kSUyWH3Cgn6SuQIk2iuOBckvN+t8FMnq1TJloUN3jefN9mhvJeCAVWb8CyUDj0vxc3iPFHDaofFdUPu2+iae7nYJMCY/1bpAAAAABJRU5ErkJggg==';
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Commands/BaseCommand.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands;
15 |
16 | use CodeIgniter\CLI\BaseCommand as FrameworkBaseCommand;
17 | use CodeIgniter\CLI\Commands;
18 | use CodeIgniter\Shield\Commands\Utils\InputOutput;
19 | use Psr\Log\LoggerInterface;
20 |
21 | abstract class BaseCommand extends FrameworkBaseCommand
22 | {
23 | protected static ?InputOutput $io = null;
24 |
25 | /**
26 | * The group the command is lumped under
27 | * when listing commands.
28 | *
29 | * @var string
30 | */
31 | protected $group = 'Shield';
32 |
33 | public function __construct(LoggerInterface $logger, Commands $commands)
34 | {
35 | parent::__construct($logger, $commands);
36 |
37 | $this->ensureInputOutput();
38 | }
39 |
40 | /**
41 | * Asks the user for input.
42 | *
43 | * @param string $field Output "field" question
44 | * @param array|string $options String to a default value, array to a list of options (the first option will be the default value)
45 | * @param array|string $validation Validation rules
46 | *
47 | * @return string The user input
48 | */
49 | protected function prompt(string $field, $options = null, $validation = null): string
50 | {
51 | return self::$io->prompt($field, $options, $validation);
52 | }
53 |
54 | /**
55 | * Outputs a string to the cli on its own line.
56 | */
57 | protected function write(
58 | string $text = '',
59 | ?string $foreground = null,
60 | ?string $background = null,
61 | ): void {
62 | self::$io->write($text, $foreground, $background);
63 | }
64 |
65 | /**
66 | * Outputs an error to the CLI using STDERR instead of STDOUT
67 | */
68 | protected function error(
69 | string $text,
70 | string $foreground = 'light_red',
71 | ?string $background = null,
72 | ): void {
73 | self::$io->error($text, $foreground, $background);
74 | }
75 |
76 | protected function ensureInputOutput(): void
77 | {
78 | if (self::$io === null) {
79 | self::$io = new InputOutput();
80 | }
81 | }
82 |
83 | /**
84 | * @internal Testing purpose only
85 | */
86 | public static function setInputOutput(InputOutput $io): void
87 | {
88 | self::$io = $io;
89 | }
90 |
91 | /**
92 | * @internal Testing purpose only
93 | */
94 | public static function resetInputOutput(): void
95 | {
96 | self::$io = null;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Commands/Exceptions/BadInputException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands\Exceptions;
15 |
16 | use CodeIgniter\Shield\Exceptions\RuntimeException;
17 |
18 | class BadInputException extends RuntimeException
19 | {
20 | }
21 |
--------------------------------------------------------------------------------
/src/Commands/Exceptions/CancelException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands\Exceptions;
15 |
16 | use CodeIgniter\Shield\Exceptions\RuntimeException;
17 |
18 | class CancelException extends RuntimeException
19 | {
20 | }
21 |
--------------------------------------------------------------------------------
/src/Commands/Generators/UserModelGenerator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands\Generators;
15 |
16 | use CodeIgniter\CLI\BaseCommand;
17 | use CodeIgniter\CLI\CLI;
18 | use CodeIgniter\CLI\GeneratorTrait;
19 |
20 | /**
21 | * Generates a custom user model file.
22 | */
23 | class UserModelGenerator extends BaseCommand
24 | {
25 | use GeneratorTrait;
26 |
27 | /**
28 | * @var string
29 | */
30 | protected $group = 'Shield';
31 |
32 | /**
33 | * @var string
34 | */
35 | protected $name = 'shield:model';
36 |
37 | /**
38 | * @var string
39 | */
40 | protected $description = 'Generate a new UserModel file.';
41 |
42 | /**
43 | * @var string
44 | */
45 | protected $usage = 'shield:model [] [options]';
46 |
47 | /**
48 | * @var array
49 | */
50 | protected $arguments = [
51 | 'name' => 'The model class name. If not provided, this will default to `UserModel`.',
52 | ];
53 |
54 | /**
55 | * @var array
56 | */
57 | protected $options = [
58 | '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
59 | '--suffix' => 'Append the component title to the class name (e.g. User => UserModel).',
60 | '--force' => 'Force overwrite existing file.',
61 | ];
62 |
63 | /**
64 | * Actually execute the command.
65 | */
66 | public function run(array $params): int
67 | {
68 | $this->component = 'Model';
69 | $this->directory = 'Models';
70 | $this->template = 'usermodel.tpl.php';
71 |
72 | $this->classNameLang = 'CLI.generator.className.model';
73 | $this->setHasClassName(false);
74 |
75 | $class = $params[0] ?? CLI::getSegment(2) ?? 'UserModel';
76 |
77 | if (! $this->verifyChosenModelClassName($class, $params)) {
78 | CLI::error('Cannot use `ShieldUserModel` as class name as this conflicts with the parent class.', 'light_gray', 'red');
79 |
80 | return 1;
81 | }
82 |
83 | $params[0] = $class;
84 |
85 | $this->generateClass($params);
86 |
87 | return 0;
88 | }
89 |
90 | /**
91 | * The chosen class name should not conflict with the alias of the parent class.
92 | */
93 | private function verifyChosenModelClassName(string $class, array $params): bool
94 | {
95 | helper('inflector');
96 |
97 | if (array_key_exists('suffix', $params) && ! strripos($class, 'Model')) {
98 | $class .= 'Model';
99 | }
100 |
101 | return strtolower(pascalize($class)) !== 'shieldusermodel';
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Commands/Generators/Views/usermodel.tpl.php:
--------------------------------------------------------------------------------
1 | <@php
2 |
3 | declare(strict_types=1);
4 |
5 | namespace {namespace};
6 |
7 | use CodeIgniter\Shield\Models\UserModel as ShieldUserModel;
8 |
9 | class {class} extends ShieldUserModel
10 | {
11 | protected function initialize(): void
12 | {
13 | parent::initialize();
14 |
15 | $this->allowedFields = [
16 | ...$this->allowedFields,
17 |
18 | // 'first_name',
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Commands/Setup/ContentReplacer.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands\Setup;
15 |
16 | class ContentReplacer
17 | {
18 | /**
19 | * @param array $replaces [search => replace]
20 | */
21 | public function replace(string $content, array $replaces): string
22 | {
23 | return strtr($content, $replaces);
24 | }
25 |
26 | /**
27 | * @param string $text Text to add.
28 | * @param string $pattern Regexp search pattern.
29 | * @param string $replace Regexp replacement including text to add.
30 | *
31 | * @return bool|string true: already updated, false: regexp error.
32 | */
33 | public function add(string $content, string $text, string $pattern, string $replace)
34 | {
35 | $return = preg_match('/' . preg_quote($text, '/') . '/u', $content);
36 |
37 | if ($return === 1) {
38 | // It has already been updated.
39 |
40 | return true;
41 | }
42 |
43 | if ($return === false) {
44 | // Regexp error.
45 |
46 | return false;
47 | }
48 |
49 | return preg_replace($pattern, $replace, $content);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Commands/Utils/InputOutput.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Commands\Utils;
15 |
16 | use CodeIgniter\CLI\CLI;
17 |
18 | class InputOutput
19 | {
20 | /**
21 | * Asks the user for input.
22 | *
23 | * @param string $field Output "field" question
24 | * @param array|string $options String to a default value, array to a list of options (the first option will be the default value)
25 | * @param array|string $validation Validation rules
26 | *
27 | * @return string The user input
28 | */
29 | public function prompt(string $field, $options = null, $validation = null): string
30 | {
31 | return CLI::prompt($field, $options, $validation);
32 | }
33 |
34 | /**
35 | * Outputs a string to the cli on its own line.
36 | */
37 | public function write(
38 | string $text = '',
39 | ?string $foreground = null,
40 | ?string $background = null,
41 | ): void {
42 | CLI::write($text, $foreground, $background);
43 | }
44 |
45 | /**
46 | * Outputs an error to the CLI using STDERR instead of STDOUT
47 | */
48 | public function error(string $text, string $foreground = 'light_red', ?string $background = null): void
49 | {
50 | CLI::error($text, $foreground, $background);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Config/AuthJWT.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Config;
15 |
16 | use CodeIgniter\Config\BaseConfig;
17 |
18 | /**
19 | * JWT Authenticator Configuration
20 | */
21 | class AuthJWT extends BaseConfig
22 | {
23 | /**
24 | * --------------------------------------------------------------------
25 | * Name of Authenticator Header
26 | * --------------------------------------------------------------------
27 | * The name of Header that the Authorization token should be found.
28 | * According to the specs, this should be `Authorization`, but rare
29 | * circumstances might need a different header.
30 | */
31 | public string $authenticatorHeader = 'Authorization';
32 |
33 | /**
34 | * --------------------------------------------------------------------
35 | * The Default Payload Items
36 | * --------------------------------------------------------------------
37 | * All JWTs will have these claims in the payload.
38 | *
39 | * @var array
40 | */
41 | public array $defaultClaims = [
42 | 'iss' => '',
43 | ];
44 |
45 | /**
46 | * --------------------------------------------------------------------
47 | * The Keys
48 | * --------------------------------------------------------------------
49 | * The key of the array is the key group name.
50 | * The first key of the group is used for signing.
51 | *
52 | * @var array>>
53 | * @phpstan-var array>>
54 | */
55 | public array $keys = [
56 | 'default' => [
57 | // Symmetric Key
58 | [
59 | 'kid' => '', // Key ID. Optional if you have only one key.
60 | 'alg' => 'HS256', // algorithm.
61 | // Set secret random string. Needs at least 256 bits for HS256 algorithm.
62 | // E.g., $ php -r 'echo base64_encode(random_bytes(32));'
63 | 'secret' => '',
64 | ],
65 | // Asymmetric Key
66 | // [
67 | // 'kid' => '', // Key ID. Optional if you have only one key.
68 | // 'alg' => 'RS256', // algorithm.
69 | // 'public' => '', // Public Key
70 | // 'private' => '', // Private Key
71 | // 'passphrase' => '' // Passphrase
72 | // ],
73 | ],
74 | ];
75 |
76 | /**
77 | * --------------------------------------------------------------------
78 | * Time To Live (in seconds)
79 | * --------------------------------------------------------------------
80 | * Specifies the amount of time, in seconds, that a token is valid.
81 | */
82 | public int $timeToLive = HOUR;
83 |
84 | /**
85 | * --------------------------------------------------------------------
86 | * Record Login Attempts
87 | * --------------------------------------------------------------------
88 | * Whether login attempts are recorded in the database.
89 | *
90 | * Valid values are:
91 | * - Auth::RECORD_LOGIN_ATTEMPT_NONE
92 | * - Auth::RECORD_LOGIN_ATTEMPT_FAILURE
93 | * - Auth::RECORD_LOGIN_ATTEMPT_ALL
94 | */
95 | public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
96 | }
97 |
--------------------------------------------------------------------------------
/src/Config/AuthRoutes.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Config;
15 |
16 | use CodeIgniter\Config\BaseConfig;
17 |
18 | class AuthRoutes extends BaseConfig
19 | {
20 | public array $routes = [
21 | 'register' => [
22 | [
23 | 'get',
24 | 'register',
25 | 'RegisterController::registerView',
26 | 'register', // Route name
27 | ],
28 | [
29 | 'post',
30 | 'register',
31 | 'RegisterController::registerAction',
32 | ],
33 | ],
34 | 'login' => [
35 | [
36 | 'get',
37 | 'login',
38 | 'LoginController::loginView',
39 | 'login', // Route name
40 | ],
41 | [
42 | 'post',
43 | 'login',
44 | 'LoginController::loginAction',
45 | ],
46 | ],
47 | 'magic-link' => [
48 | [
49 | 'get',
50 | 'login/magic-link',
51 | 'MagicLinkController::loginView',
52 | 'magic-link', // Route name
53 | ],
54 | [
55 | 'post',
56 | 'login/magic-link',
57 | 'MagicLinkController::loginAction',
58 | ],
59 | [
60 | 'get',
61 | 'login/verify-magic-link',
62 | 'MagicLinkController::verify',
63 | 'verify-magic-link', // Route name
64 | ],
65 | ],
66 | 'logout' => [
67 | [
68 | 'get',
69 | 'logout',
70 | 'LoginController::logoutAction',
71 | 'logout', // Route name
72 | ],
73 | ],
74 | 'auth-actions' => [
75 | [
76 | 'get',
77 | 'auth/a/show',
78 | 'ActionController::show',
79 | 'auth-action-show', // Route name
80 | ],
81 | [
82 | 'post',
83 | 'auth/a/handle',
84 | 'ActionController::handle',
85 | 'auth-action-handle', // Route name
86 | ],
87 | [
88 | 'post',
89 | 'auth/a/verify',
90 | 'ActionController::verify',
91 | 'auth-action-verify', // Route name
92 | ],
93 | ],
94 | ];
95 | }
96 |
--------------------------------------------------------------------------------
/src/Config/BaseAuthToken.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Config;
15 |
16 | use CodeIgniter\Config\BaseConfig;
17 |
18 | class BaseAuthToken extends BaseConfig
19 | {
20 | /**
21 | * List of HMAC Encryption Keys
22 | *
23 | * @var array|string
24 | */
25 | public $hmacEncryptionKeys;
26 |
27 | /**
28 | * AuthToken Config Constructor
29 | */
30 | public function __construct()
31 | {
32 | parent::__construct();
33 |
34 | if (is_string($this->hmacEncryptionKeys)) {
35 | $array = json_decode($this->hmacEncryptionKeys, true);
36 | if (is_array($array)) {
37 | $this->hmacEncryptionKeys = $array;
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * Override parent initEnvValue() to allow for direct setting to array properties values from ENV
44 | *
45 | * In order to set array properties via ENV vars we need to set the property to a string value first.
46 | *
47 | * @param mixed $property
48 | */
49 | protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix): void
50 | {
51 | // if attempting to set property from ENV, first set to empty string
52 | if ($name === 'hmacEncryptionKeys' && $this->getEnvValue($name, $prefix, $shortPrefix) !== null) {
53 | $property = '';
54 | }
55 |
56 | parent::initEnvValue($property, $name, $prefix, $shortPrefix);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Config/Registrar.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Config;
15 |
16 | use CodeIgniter\Shield\Authentication\Passwords\ValidationRules as PasswordRules;
17 | use CodeIgniter\Shield\Collectors\Auth;
18 | use CodeIgniter\Shield\Filters\AuthRates;
19 | use CodeIgniter\Shield\Filters\ChainAuth;
20 | use CodeIgniter\Shield\Filters\ForcePasswordResetFilter;
21 | use CodeIgniter\Shield\Filters\GroupFilter;
22 | use CodeIgniter\Shield\Filters\HmacAuth;
23 | use CodeIgniter\Shield\Filters\JWTAuth;
24 | use CodeIgniter\Shield\Filters\PermissionFilter;
25 | use CodeIgniter\Shield\Filters\SessionAuth;
26 | use CodeIgniter\Shield\Filters\TokenAuth;
27 |
28 | class Registrar
29 | {
30 | /**
31 | * Registers the Shield filters.
32 | */
33 | public static function Filters(): array
34 | {
35 | return [
36 | 'aliases' => [
37 | 'session' => SessionAuth::class,
38 | 'tokens' => TokenAuth::class,
39 | 'hmac' => HmacAuth::class,
40 | 'chain' => ChainAuth::class,
41 | 'auth-rates' => AuthRates::class,
42 | 'group' => GroupFilter::class,
43 | 'permission' => PermissionFilter::class,
44 | 'force-reset' => ForcePasswordResetFilter::class,
45 | 'jwt' => JWTAuth::class,
46 | ],
47 | ];
48 | }
49 |
50 | public static function Validation(): array
51 | {
52 | return [
53 | 'ruleSets' => [
54 | PasswordRules::class,
55 | ],
56 | ];
57 | }
58 |
59 | public static function Toolbar(): array
60 | {
61 | return [
62 | 'collectors' => [
63 | Auth::class,
64 | ],
65 | ];
66 | }
67 |
68 | public static function Generators(): array
69 | {
70 | return [
71 | 'views' => [
72 | 'shield:model' => 'CodeIgniter\Shield\Commands\Generators\Views\usermodel.tpl.php',
73 | ],
74 | ];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Config/Services.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Config;
15 |
16 | use CodeIgniter\Config\BaseService;
17 | use CodeIgniter\Shield\Auth;
18 | use CodeIgniter\Shield\Authentication\JWTManager;
19 | use CodeIgniter\Shield\Authentication\Passwords;
20 | use CodeIgniter\Shield\Config\Auth as AuthConfig;
21 |
22 | class Services extends BaseService
23 | {
24 | /**
25 | * The base auth class
26 | */
27 | public static function auth(bool $getShared = true): Auth
28 | {
29 | if ($getShared) {
30 | return self::getSharedInstance('auth');
31 | }
32 |
33 | /** @var AuthConfig $config */
34 | $config = config('Auth');
35 |
36 | return new Auth($config);
37 | }
38 |
39 | /**
40 | * Password utilities.
41 | */
42 | public static function passwords(bool $getShared = true): Passwords
43 | {
44 | if ($getShared) {
45 | return self::getSharedInstance('passwords');
46 | }
47 |
48 | return new Passwords(config('Auth'));
49 | }
50 |
51 | /**
52 | * JWT Manager.
53 | */
54 | public static function jwtmanager(bool $getShared = true): JWTManager
55 | {
56 | if ($getShared) {
57 | return self::getSharedInstance('jwtmanager');
58 | }
59 |
60 | return new JWTManager();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Controllers/ActionController.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Controllers;
15 |
16 | use App\Controllers\BaseController;
17 | use CodeIgniter\Exceptions\PageNotFoundException;
18 | use CodeIgniter\HTTP\Response;
19 | use CodeIgniter\Shield\Authentication\Actions\ActionInterface;
20 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
21 |
22 | /**
23 | * Class ActionController
24 | *
25 | * A generic controller to handle Authentication Actions.
26 | */
27 | class ActionController extends BaseController
28 | {
29 | protected ?ActionInterface $action = null;
30 |
31 | /**
32 | * Perform an initial check if we have a valid action or not.
33 | *
34 | * @param list $params
35 | *
36 | * @return Response|string
37 | */
38 | public function _remap(string $method, ...$params)
39 | {
40 | /** @var Session $authenticator */
41 | $authenticator = auth('session')->getAuthenticator();
42 |
43 | // Grab our action instance if one has been set.
44 | $this->action = $authenticator->getAction();
45 |
46 | if (! $this->action instanceof ActionInterface) {
47 | throw new PageNotFoundException();
48 | }
49 |
50 | return $this->{$method}(...$params);
51 | }
52 |
53 | /**
54 | * Shows the initial screen to the user to start the flow.
55 | * This might be asking for the user's email to reset a password,
56 | * or asking for a cell-number for a 2FA.
57 | *
58 | * @return Response|string
59 | */
60 | public function show()
61 | {
62 | return $this->action->show();
63 | }
64 |
65 | /**
66 | * Processes the form that was displayed in the previous form.
67 | *
68 | * @return Response|string
69 | */
70 | public function handle()
71 | {
72 | return $this->action->handle($this->request);
73 | }
74 |
75 | /**
76 | * This handles the response after the user takes action
77 | * in response to the show/handle flow. This might be
78 | * from clicking the 'confirm my email' action or
79 | * following entering a code sent in an SMS.
80 | *
81 | * @return Response|string
82 | */
83 | public function verify()
84 | {
85 | return $this->action->verify($this->request);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Controllers/LoginController.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Controllers;
15 |
16 | use App\Controllers\BaseController;
17 | use CodeIgniter\HTTP\RedirectResponse;
18 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
19 | use CodeIgniter\Shield\Traits\Viewable;
20 | use CodeIgniter\Shield\Validation\ValidationRules;
21 |
22 | class LoginController extends BaseController
23 | {
24 | use Viewable;
25 |
26 | /**
27 | * Displays the form the login to the site.
28 | *
29 | * @return RedirectResponse|string
30 | */
31 | public function loginView()
32 | {
33 | if (auth()->loggedIn()) {
34 | return redirect()->to(config('Auth')->loginRedirect());
35 | }
36 |
37 | /** @var Session $authenticator */
38 | $authenticator = auth('session')->getAuthenticator();
39 |
40 | // If an action has been defined, start it up.
41 | if ($authenticator->hasAction()) {
42 | return redirect()->route('auth-action-show');
43 | }
44 |
45 | return $this->view(setting('Auth.views')['login']);
46 | }
47 |
48 | /**
49 | * Attempts to log the user in.
50 | */
51 | public function loginAction(): RedirectResponse
52 | {
53 | // Validate here first, since some things,
54 | // like the password, can only be validated properly here.
55 | $rules = $this->getValidationRules();
56 |
57 | if (! $this->validateData($this->request->getPost(), $rules, [], config('Auth')->DBGroup)) {
58 | return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
59 | }
60 |
61 | /** @var array $credentials */
62 | $credentials = $this->request->getPost(setting('Auth.validFields')) ?? [];
63 | $credentials = array_filter($credentials);
64 | $credentials['password'] = $this->request->getPost('password');
65 | $remember = (bool) $this->request->getPost('remember');
66 |
67 | /** @var Session $authenticator */
68 | $authenticator = auth('session')->getAuthenticator();
69 |
70 | // Attempt to login
71 | $result = $authenticator->remember($remember)->attempt($credentials);
72 | if (! $result->isOK()) {
73 | return redirect()->route('login')->withInput()->with('error', $result->reason());
74 | }
75 |
76 | // If an action has been defined for login, start it up.
77 | if ($authenticator->hasAction()) {
78 | return redirect()->route('auth-action-show')->withCookies();
79 | }
80 |
81 | return redirect()->to(config('Auth')->loginRedirect())->withCookies();
82 | }
83 |
84 | /**
85 | * Returns the rules that should be used for validation.
86 | *
87 | * @return array|string>>
88 | */
89 | protected function getValidationRules(): array
90 | {
91 | $rules = new ValidationRules();
92 |
93 | return $rules->getLoginRules();
94 | }
95 |
96 | /**
97 | * Logs the current user out.
98 | */
99 | public function logoutAction(): RedirectResponse
100 | {
101 | // Capture logout redirect URL before auth logout,
102 | // otherwise you cannot check the user in `logoutRedirect()`.
103 | $url = config('Auth')->logoutRedirect();
104 |
105 | auth()->logout();
106 |
107 | return redirect()->to($url)->with('message', lang('Auth.successLogout'));
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Entities/AccessToken.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Entities;
15 |
16 | use CodeIgniter\Entity\Entity;
17 | use CodeIgniter\I18n\Time;
18 |
19 | /**
20 | * Class AccessToken
21 | *
22 | * Represents a single Personal Access Token, used
23 | * for authenticating users for an API.
24 | *
25 | * @property string|Time|null $expires
26 | * @property string|Time|null $last_used_at
27 | */
28 | class AccessToken extends Entity
29 | {
30 | private ?User $user = null;
31 |
32 | /**
33 | * @var array
34 | */
35 | protected $casts = [
36 | 'id' => '?integer',
37 | 'last_used_at' => 'datetime',
38 | 'extra' => 'array',
39 | 'expires' => 'datetime',
40 | ];
41 |
42 | /**
43 | * @var array
44 | */
45 | protected $datamap = [
46 | 'scopes' => 'extra',
47 | ];
48 |
49 | /**
50 | * Returns the user associated with this token.
51 | */
52 | public function user(): ?User
53 | {
54 | if ($this->user === null) {
55 | $users = auth()->getProvider();
56 | $this->user = $users->findById($this->user_id);
57 | }
58 |
59 | return $this->user;
60 | }
61 |
62 | /**
63 | * Determines whether this token grants
64 | * permission to the $scope
65 | */
66 | public function can(string $scope): bool
67 | {
68 | if ($this->extra === []) {
69 | return false;
70 | }
71 |
72 | // Wildcard present
73 | if (in_array('*', $this->extra, true)) {
74 | return true;
75 | }
76 |
77 | // Check stored scopes
78 | return in_array($scope, $this->extra, true);
79 | }
80 |
81 | /**
82 | * Determines whether this token does NOT
83 | * grant permission to $scope.
84 | */
85 | public function cant(string $scope): bool
86 | {
87 | if ($this->extra === []) {
88 | return true;
89 | }
90 |
91 | // Wildcard present
92 | if (in_array('*', $this->extra, true)) {
93 | return false;
94 | }
95 |
96 | // Check stored scopes
97 | return ! in_array($scope, $this->extra, true);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Entities/Group.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Entities;
15 |
16 | use CodeIgniter\Entity\Entity;
17 |
18 | /**
19 | * Represents a single User Group
20 | * and provides utility functions.
21 | */
22 | class Group extends Entity
23 | {
24 | protected ?array $permissions = null;
25 |
26 | /**
27 | * Returns the permissions for this group.
28 | */
29 | public function permissions(): array
30 | {
31 | $this->populatePermissions();
32 |
33 | return $this->permissions;
34 | }
35 |
36 | /**
37 | * Overrides and saves all permissions in the class
38 | * with the permissions array that is passed in.
39 | */
40 | public function setPermissions(array $permissions): void
41 | {
42 | $this->permissions = $permissions;
43 |
44 | $matrix = setting('AuthGroups.matrix');
45 |
46 | $matrix[$this->alias] = $permissions;
47 |
48 | setting('AuthGroups.matrix', $matrix);
49 | }
50 |
51 | /**
52 | * Adds a single permission to this group and saves it.
53 | */
54 | public function addPermission(string $permission): void
55 | {
56 | $this->populatePermissions();
57 |
58 | array_unshift($this->permissions, $permission);
59 |
60 | $this->setPermissions($this->permissions);
61 | }
62 |
63 | /**
64 | * Removes a single permission from this group and saves it.
65 | */
66 | public function removePermission(string $permission): void
67 | {
68 | $this->populatePermissions();
69 |
70 | unset($this->permissions[array_search($permission, $this->permissions, true)]);
71 |
72 | $this->setPermissions($this->permissions);
73 | }
74 |
75 | /**
76 | * Determines if the group has the given permission
77 | */
78 | public function can(string $permission): bool
79 | {
80 | $this->populatePermissions();
81 |
82 | // Check exact match
83 | if ($this->permissions !== null && $this->permissions !== [] && in_array($permission, $this->permissions, true)) {
84 | return true;
85 | }
86 |
87 | // Check wildcard match
88 | $check = substr($permission, 0, strpos($permission, '.')) . '.*';
89 |
90 | return $this->permissions !== null && $this->permissions !== [] && in_array($check, $this->permissions, true);
91 | }
92 |
93 | /**
94 | * Loads our permissions for this group.
95 | */
96 | private function populatePermissions(): void
97 | {
98 | if ($this->permissions !== null) {
99 | return;
100 | }
101 |
102 | $this->permissions = setting('AuthGroups.matrix')[$this->alias] ?? [];
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Entities/Login.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Entities;
15 |
16 | use CodeIgniter\Entity\Entity;
17 |
18 | class Login extends Entity
19 | {
20 | /**
21 | * @var array
22 | */
23 | protected $casts = [
24 | 'date' => 'datetime',
25 | 'success' => 'int-bool',
26 | ];
27 | }
28 |
--------------------------------------------------------------------------------
/src/Entities/UserIdentity.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Entities;
15 |
16 | use CodeIgniter\Entity\Entity;
17 | use CodeIgniter\I18n\Time;
18 | use CodeIgniter\Shield\Authentication\Passwords;
19 |
20 | /**
21 | * Class UserIdentity
22 | *
23 | * Represents a single set of user identity credentials.
24 | * For the base Shield system, this would be one of the following:
25 | * - password
26 | * - reset hash
27 | * - access token
28 | *
29 | * This can also be used to store credentials for social logins,
30 | * OAUTH or JWT tokens, etc. A user can have multiple of each,
31 | * though a Authenticator may want to enforce only one exists for that
32 | * user, like a password.
33 | *
34 | * @property string|Time|null $last_used_at
35 | * @property string|null $secret
36 | * @property string|null $secret2
37 | */
38 | class UserIdentity extends Entity
39 | {
40 | /**
41 | * @var array
42 | */
43 | protected $casts = [
44 | 'id' => '?integer',
45 | 'force_reset' => 'int-bool',
46 | ];
47 |
48 | /**
49 | * @var list
50 | */
51 | protected $dates = [
52 | 'expires',
53 | 'last_used_at',
54 | ];
55 |
56 | /**
57 | * Uses password-strength hashing to hash
58 | * a given value for the 'secret'.
59 | */
60 | public function hashSecret(string $value): UserIdentity
61 | {
62 | /** @var Passwords $passwords */
63 | $passwords = service('passwords');
64 |
65 | $this->attributes['secret'] = $passwords->hash($value);
66 |
67 | return $this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Exceptions/BaseException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | use Throwable;
17 |
18 | /**
19 | * Base Exception Interface for Shield
20 | */
21 | interface BaseException extends Throwable
22 | {
23 | }
24 |
--------------------------------------------------------------------------------
/src/Exceptions/GroupException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | use CodeIgniter\Shield\Authorization\AuthorizationException;
17 |
18 | class GroupException extends AuthorizationException
19 | {
20 | }
21 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class InvalidArgumentException extends LogicException implements BaseException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exceptions/LogicException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class LogicException extends \LogicException implements BaseException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exceptions/PermissionException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | use CodeIgniter\Shield\Authorization\AuthorizationException;
17 |
18 | class PermissionException extends AuthorizationException
19 | {
20 | }
21 |
--------------------------------------------------------------------------------
/src/Exceptions/RuntimeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class RuntimeException extends \RuntimeException implements BaseException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exceptions/SecurityException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class SecurityException extends RuntimeException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exceptions/UserNotFoundException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class UserNotFoundException extends RuntimeException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exceptions/ValidationException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Exceptions;
15 |
16 | class ValidationException extends RuntimeException
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/src/Filters/AbstractAuthFilter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\RedirectResponse;
18 | use CodeIgniter\HTTP\RequestInterface;
19 | use CodeIgniter\HTTP\ResponseInterface;
20 |
21 | /**
22 | * Group Authorization Filter.
23 | */
24 | abstract class AbstractAuthFilter implements FilterInterface
25 | {
26 | /**
27 | * Ensures the user is logged in and a member of one or
28 | * more groups as specified in the filter.
29 | *
30 | * @param array|null $arguments
31 | *
32 | * @return RedirectResponse|void
33 | */
34 | public function before(RequestInterface $request, $arguments = null)
35 | {
36 | if (empty($arguments)) {
37 | return;
38 | }
39 |
40 | if (! auth()->loggedIn()) {
41 | // Set the entrance url to redirect a user after successful login
42 | if (uri_string() !== route_to('login')) {
43 | $session = session();
44 | $session->setTempdata('beforeLoginUrl', current_url(), 300);
45 | }
46 |
47 | return redirect()->route('login');
48 | }
49 |
50 | if ($this->isAuthorized($arguments)) {
51 | return;
52 | }
53 |
54 | return $this->redirectToDeniedUrl();
55 | }
56 |
57 | /**
58 | * We don't have anything to do here.
59 | *
60 | * @param array|null $arguments
61 | */
62 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
63 | {
64 | // Nothing required
65 | }
66 |
67 | /**
68 | * Ensures the user is logged in and has one or more
69 | * of the permissions as specified in the filter.
70 | */
71 | abstract protected function isAuthorized(array $arguments): bool;
72 |
73 | /**
74 | * Returns redirect response when the user does not have access authorizations.
75 | */
76 | abstract protected function redirectToDeniedUrl(): RedirectResponse;
77 | }
78 |
--------------------------------------------------------------------------------
/src/Filters/AuthRates.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RedirectResponse;
19 | use CodeIgniter\HTTP\RequestInterface;
20 | use CodeIgniter\HTTP\ResponseInterface;
21 |
22 | /**
23 | * Auth Rate-Limiting Filter.
24 | *
25 | * Provides rated limiting intended for Auth routes.
26 | */
27 | class AuthRates implements FilterInterface
28 | {
29 | /**
30 | * Intened for use on auth form pages to restrict the number
31 | * of attempts that can be generated. Restricts it to 10 attempts
32 | * per minute, which is what auth0 uses.
33 | *
34 | * @see https://auth0.com/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy/database-connections-rate-limits
35 | *
36 | * @param array|null $arguments
37 | *
38 | * @return RedirectResponse|void
39 | */
40 | public function before(RequestInterface $request, $arguments = null)
41 | {
42 | if (! $request instanceof IncomingRequest) {
43 | return;
44 | }
45 |
46 | $throttler = service('throttler');
47 |
48 | // Restrict an IP address to no more than 10 requests
49 | // per minute on any auth-form pages (login, register, forgot, etc).
50 | if ($throttler->check(md5($request->getIPAddress()), 10, MINUTE, 1) === false) {
51 | return service('response')->setStatusCode(
52 | 429,
53 | lang('Auth.throttled', [$throttler->getTokenTime()]), // message
54 | );
55 | }
56 | }
57 |
58 | /**
59 | * We don't have anything to do here.
60 | *
61 | * @param array|null $arguments
62 | */
63 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
64 | {
65 | // Nothing required
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Filters/ChainAuth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RedirectResponse;
19 | use CodeIgniter\HTTP\RequestInterface;
20 | use CodeIgniter\HTTP\ResponseInterface;
21 |
22 | /**
23 | * Chain Authentication Filter.
24 | *
25 | * Checks all authentication systems specified within
26 | * `Config\Auth->authenticationChain`
27 | */
28 | class ChainAuth implements FilterInterface
29 | {
30 | /**
31 | * Checks authenticators in sequence to see if the user is logged in through
32 | * either of authenticators.
33 | *
34 | * @param array|null $arguments
35 | *
36 | * @return RedirectResponse|void
37 | */
38 | public function before(RequestInterface $request, $arguments = null)
39 | {
40 | if (! $request instanceof IncomingRequest) {
41 | return;
42 | }
43 |
44 | helper('settings');
45 |
46 | $chain = config('Auth')->authenticationChain;
47 |
48 | foreach ($chain as $alias) {
49 | $auth = auth($alias);
50 |
51 | if ($auth->loggedIn()) {
52 | // Make sure Auth uses this Authenticator
53 | auth()->setAuthenticator($alias);
54 |
55 | $authenticator = $auth->getAuthenticator();
56 |
57 | if (setting('Auth.recordActiveDate')) {
58 | $authenticator->recordActiveDate();
59 | }
60 |
61 | return;
62 | }
63 | }
64 |
65 | return redirect()->route('login');
66 | }
67 |
68 | /**
69 | * We don't have anything to do here.
70 | *
71 | * @param array|null $arguments
72 | */
73 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
74 | {
75 | // Nothing required
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Filters/ForcePasswordResetFilter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RedirectResponse;
19 | use CodeIgniter\HTTP\RequestInterface;
20 | use CodeIgniter\HTTP\ResponseInterface;
21 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
22 |
23 | /**
24 | * Force Password Reset Filter.
25 | */
26 | class ForcePasswordResetFilter implements FilterInterface
27 | {
28 | /**
29 | * Checks if a logged in user should reset their
30 | * password, and then redirect to the appropriate
31 | * page.
32 | *
33 | * @param array|null $arguments
34 | *
35 | * @return RedirectResponse|void
36 | */
37 | public function before(RequestInterface $request, $arguments = null)
38 | {
39 | if (! $request instanceof IncomingRequest) {
40 | return;
41 | }
42 |
43 | /** @var Session $authenticator */
44 | $authenticator = auth('session')->getAuthenticator();
45 |
46 | if ($authenticator->loggedIn() && $authenticator->getUser()->requiresPasswordReset()) {
47 | return redirect()->to(config('Auth')->forcePasswordResetRedirect());
48 | }
49 | }
50 |
51 | /**
52 | * We don't have anything to do here.
53 | *
54 | * @param array|null $arguments
55 | */
56 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
57 | {
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Filters/GroupFilter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\HTTP\RedirectResponse;
17 |
18 | /**
19 | * Group Authorization Filter.
20 | */
21 | class GroupFilter extends AbstractAuthFilter
22 | {
23 | /**
24 | * Ensures the user is logged in and a member of one or
25 | * more groups as specified in the filter.
26 | */
27 | protected function isAuthorized(array $arguments): bool
28 | {
29 | return auth()->user()->inGroup(...$arguments);
30 | }
31 |
32 | /**
33 | * If the user does not belong to the group, redirect to the configured URL with an error message.
34 | */
35 | protected function redirectToDeniedUrl(): RedirectResponse
36 | {
37 | return redirect()->to(config('Auth')->groupDeniedRedirect())
38 | ->with('error', lang('Auth.notEnoughPrivilege'));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Filters/HmacAuth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\RequestInterface;
18 | use CodeIgniter\HTTP\Response;
19 | use CodeIgniter\HTTP\ResponseInterface;
20 |
21 | /**
22 | * HMAC Token Authentication Filter.
23 | *
24 | * Personal HMAC Token authentication for web applications / API.
25 | */
26 | class HmacAuth implements FilterInterface
27 | {
28 | /**
29 | * {@inheritDoc}
30 | */
31 | public function before(RequestInterface $request, $arguments = null)
32 | {
33 | $authenticator = auth('hmac')->getAuthenticator();
34 |
35 | $requestParams = [
36 | 'token' => $request->getHeaderLine(setting('AuthToken.authenticatorHeader')['hmac'] ?? 'Authorization'),
37 | 'body' => $request->getBody() ?? '',
38 | ];
39 |
40 | $result = $authenticator->attempt($requestParams);
41 |
42 | if (! $result->isOK() || ($arguments !== null && $arguments !== [] && $result->extraInfo()->hmacTokenCant($arguments[0]))) {
43 | return service('response')
44 | ->setStatusCode(Response::HTTP_UNAUTHORIZED)
45 | ->setJSON(['message' => lang('Auth.badToken')]);
46 | }
47 |
48 | if (setting('Auth.recordActiveDate')) {
49 | $authenticator->recordActiveDate();
50 | }
51 |
52 | // Block inactive users when Email Activation is enabled
53 | $user = $authenticator->getUser();
54 | if ($user !== null && ! $user->isActivated()) {
55 | $authenticator->logout();
56 |
57 | return service('response')
58 | ->setStatusCode(Response::HTTP_FORBIDDEN)
59 | ->setJSON(['message' => lang('Auth.activationBlocked')]);
60 | }
61 |
62 | return $request;
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
69 | {
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Filters/JWTAuth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RequestInterface;
19 | use CodeIgniter\HTTP\ResponseInterface;
20 | use CodeIgniter\Shield\Authentication\Authenticators\JWT;
21 | use Config\Services;
22 |
23 | /**
24 | * JWT Authentication Filter.
25 | *
26 | * JSON Web Token authentication for web applications.
27 | */
28 | class JWTAuth implements FilterInterface
29 | {
30 | /**
31 | * Gets the JWT from the Request header, and checks it.
32 | *
33 | * @param array|null $arguments
34 | *
35 | * @return ResponseInterface|void
36 | */
37 | public function before(RequestInterface $request, $arguments = null)
38 | {
39 | if (! $request instanceof IncomingRequest) {
40 | return;
41 | }
42 |
43 | /** @var JWT $authenticator */
44 | $authenticator = auth('jwt')->getAuthenticator();
45 |
46 | $token = $authenticator->getTokenFromRequest($request);
47 |
48 | $result = $authenticator->attempt(['token' => $token]);
49 |
50 | if (! $result->isOK()) {
51 | return Services::response()
52 | ->setJSON([
53 | 'error' => $result->reason(),
54 | ])
55 | ->setStatusCode(ResponseInterface::HTTP_UNAUTHORIZED);
56 | }
57 |
58 | if (setting('Auth.recordActiveDate')) {
59 | $authenticator->recordActiveDate();
60 | }
61 | }
62 |
63 | /**
64 | * We don't have anything to do here.
65 | *
66 | * @param array|null $arguments
67 | */
68 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
69 | {
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Filters/PermissionFilter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\HTTP\RedirectResponse;
17 |
18 | /**
19 | * Permission Authorization Filter.
20 | */
21 | class PermissionFilter extends AbstractAuthFilter
22 | {
23 | /**
24 | * Ensures the user is logged in and has one or more
25 | * of the permissions as specified in the filter.
26 | */
27 | protected function isAuthorized(array $arguments): bool
28 | {
29 | foreach ($arguments as $permission) {
30 | if (auth()->user()->can($permission)) {
31 | return true;
32 | }
33 | }
34 |
35 | return false;
36 | }
37 |
38 | /**
39 | * If the user does not have the permission, redirect to the configured URL with an error message.
40 | */
41 | protected function redirectToDeniedUrl(): RedirectResponse
42 | {
43 | return redirect()->to(config('Auth')->permissionDeniedRedirect())
44 | ->with('error', lang('Auth.notEnoughPrivilege'));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Filters/SessionAuth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RedirectResponse;
19 | use CodeIgniter\HTTP\RequestInterface;
20 | use CodeIgniter\HTTP\Response;
21 | use CodeIgniter\HTTP\ResponseInterface;
22 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
23 |
24 | /**
25 | * Session Authentication Filter.
26 | *
27 | * Email/Password-based authentication for web applications.
28 | */
29 | class SessionAuth implements FilterInterface
30 | {
31 | /**
32 | * Do whatever processing this filter needs to do.
33 | * By default it should not return anything during
34 | * normal execution. However, when an abnormal state
35 | * is found, it should return an instance of
36 | * CodeIgniter\HTTP\Response. If it does, script
37 | * execution will end and that Response will be
38 | * sent back to the client, allowing for error pages,
39 | * redirects, etc.
40 | *
41 | * @param array|null $arguments
42 | *
43 | * @return RedirectResponse|void
44 | */
45 | public function before(RequestInterface $request, $arguments = null)
46 | {
47 | if (! $request instanceof IncomingRequest) {
48 | return;
49 | }
50 |
51 | /** @var Session $authenticator */
52 | $authenticator = auth('session')->getAuthenticator();
53 |
54 | if ($authenticator->loggedIn()) {
55 | if (setting('Auth.recordActiveDate')) {
56 | $authenticator->recordActiveDate();
57 | }
58 |
59 | // Block inactive users when Email Activation is enabled
60 | $user = $authenticator->getUser();
61 |
62 | if ($user->isBanned()) {
63 | $error = $user->getBanMessage() ?? lang('Auth.logOutBannedUser');
64 | $authenticator->logout();
65 |
66 | return redirect()->to(config('Auth')->logoutRedirect())
67 | ->with('error', $error);
68 | }
69 |
70 | if ($user !== null && ! $user->isActivated()) {
71 | // If an action has been defined for register, start it up.
72 | $hasAction = $authenticator->startUpAction('register', $user);
73 | if ($hasAction) {
74 | return redirect()->route('auth-action-show')
75 | ->with('error', lang('Auth.activationBlocked'));
76 | }
77 | }
78 |
79 | return;
80 | }
81 |
82 | if ($authenticator->isPending()) {
83 | return redirect()->route('auth-action-show')
84 | ->with('error', $authenticator->getPendingMessage());
85 | }
86 |
87 | if (uri_string() !== route_to('login')) {
88 | $session = session();
89 | $session->setTempdata('beforeLoginUrl', current_url(), 300);
90 | }
91 |
92 | return redirect()->route('login');
93 | }
94 |
95 | /**
96 | * We don't have anything to do here.
97 | *
98 | * @param array|null $arguments
99 | */
100 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
101 | {
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Filters/TokenAuth.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Filters;
15 |
16 | use CodeIgniter\Filters\FilterInterface;
17 | use CodeIgniter\HTTP\IncomingRequest;
18 | use CodeIgniter\HTTP\RedirectResponse;
19 | use CodeIgniter\HTTP\RequestInterface;
20 | use CodeIgniter\HTTP\Response;
21 | use CodeIgniter\HTTP\ResponseInterface;
22 | use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens;
23 |
24 | /**
25 | * Access Token Authentication Filter.
26 | *
27 | * Personal Access Token authentication for web applications.
28 | */
29 | class TokenAuth implements FilterInterface
30 | {
31 | /**
32 | * Do whatever processing this filter needs to do.
33 | * By default, it should not return anything during
34 | * normal execution. However, when an abnormal state
35 | * is found, it should return an instance of
36 | * CodeIgniter\HTTP\Response. If it does, script
37 | * execution will end and that Response will be
38 | * sent back to the client, allowing for error pages,
39 | * redirects, etc.
40 | *
41 | * @param array|null $arguments
42 | *
43 | * @return RedirectResponse|void
44 | */
45 | public function before(RequestInterface $request, $arguments = null)
46 | {
47 | if (! $request instanceof IncomingRequest) {
48 | return;
49 | }
50 |
51 | /** @var AccessTokens $authenticator */
52 | $authenticator = auth('tokens')->getAuthenticator();
53 |
54 | $result = $authenticator->attempt([
55 | 'token' => $request->getHeaderLine(setting('AuthToken.authenticatorHeader')['tokens'] ?? 'Authorization'),
56 | ]);
57 |
58 | if (! $result->isOK() || (! empty($arguments) && $result->extraInfo()->tokenCant($arguments[0]))) {
59 | return service('response')
60 | ->setStatusCode(Response::HTTP_UNAUTHORIZED)
61 | ->setJSON(['message' => lang('Auth.badToken')]);
62 | }
63 |
64 | if (setting('Auth.recordActiveDate')) {
65 | $authenticator->recordActiveDate();
66 | }
67 |
68 | // Block inactive users when Email Activation is enabled
69 | $user = $authenticator->getUser();
70 | if ($user !== null && ! $user->isActivated()) {
71 | $authenticator->logout();
72 |
73 | return service('response')
74 | ->setStatusCode(Response::HTTP_FORBIDDEN)
75 | ->setJSON(['message' => lang('Auth.activationBlocked')]);
76 | }
77 | }
78 |
79 | /**
80 | * We don't have anything to do here.
81 | *
82 | * @param array|null $arguments
83 | */
84 | public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
85 | {
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Helpers/auth_helper.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | use CodeIgniter\Shield\Auth;
15 |
16 | if (! function_exists('auth')) {
17 | /**
18 | * Provides convenient access to the main Auth class
19 | * for CodeIgniter Shield.
20 | *
21 | * @param string|null $alias Authenticator alias
22 | */
23 | function auth(?string $alias = null): Auth
24 | {
25 | /** @var Auth $auth */
26 | $auth = service('auth');
27 |
28 | return $auth->setAuthenticator($alias);
29 | }
30 | }
31 |
32 | if (! function_exists('user_id')) {
33 | /**
34 | * Returns the ID for the current logged-in user.
35 | *
36 | * @return int|string|null
37 | */
38 | function user_id()
39 | {
40 | /** @var Auth $auth */
41 | $auth = service('auth');
42 |
43 | return $auth->id();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Helpers/email_helper.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | use CodeIgniter\Email\Email;
15 |
16 | if (! function_exists('emailer')) {
17 | /**
18 | * Provides convenient access to the CodeIgniter Email class.
19 | *
20 | * @param array $overrides Email preferences to override.
21 | *
22 | * @internal
23 | */
24 | function emailer(array $overrides = []): Email
25 | {
26 | $config = [
27 | 'userAgent' => setting('Email.userAgent'),
28 | 'protocol' => setting('Email.protocol'),
29 | 'mailPath' => setting('Email.mailPath'),
30 | 'SMTPHost' => setting('Email.SMTPHost'),
31 | 'SMTPUser' => setting('Email.SMTPUser'),
32 | 'SMTPPass' => setting('Email.SMTPPass'),
33 | 'SMTPPort' => setting('Email.SMTPPort'),
34 | 'SMTPTimeout' => setting('Email.SMTPTimeout'),
35 | 'SMTPKeepAlive' => setting('Email.SMTPKeepAlive'),
36 | 'SMTPCrypto' => setting('Email.SMTPCrypto'),
37 | 'wordWrap' => setting('Email.wordWrap'),
38 | 'wrapChars' => setting('Email.wrapChars'),
39 | 'mailType' => setting('Email.mailType'),
40 | 'charset' => setting('Email.charset'),
41 | 'validate' => setting('Email.validate'),
42 | 'priority' => setting('Email.priority'),
43 | 'CRLF' => setting('Email.CRLF'),
44 | 'newline' => setting('Email.newline'),
45 | 'BCCBatchMode' => setting('Email.BCCBatchMode'),
46 | 'BCCBatchSize' => setting('Email.BCCBatchSize'),
47 | 'DSN' => setting('Email.DSN'),
48 | ];
49 |
50 | if ($overrides !== []) {
51 | $config = array_merge($config, $overrides);
52 | }
53 |
54 | /** @var Email $email */
55 | $email = service('email');
56 |
57 | return $email->initialize($config);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Models/BaseModel.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\Model;
17 | use CodeIgniter\Shield\Config\Auth;
18 |
19 | abstract class BaseModel extends Model
20 | {
21 | use CheckQueryReturnTrait;
22 |
23 | /**
24 | * Auth Table names
25 | */
26 | protected array $tables;
27 |
28 | protected Auth $authConfig;
29 |
30 | public function __construct()
31 | {
32 | $this->authConfig = config('Auth');
33 |
34 | if ($this->authConfig->DBGroup !== null) {
35 | $this->DBGroup = $this->authConfig->DBGroup;
36 | }
37 |
38 | parent::__construct();
39 | }
40 |
41 | protected function initialize(): void
42 | {
43 | $this->tables = $this->authConfig->tables;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Models/CheckQueryReturnTrait.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\Shield\Exceptions\ValidationException;
17 | use ReflectionObject;
18 | use ReflectionProperty;
19 |
20 | trait CheckQueryReturnTrait
21 | {
22 | protected ?bool $currentDBDebug = null;
23 |
24 | /**
25 | * @param bool|int|string $return insert() returns insert ID.
26 | */
27 | protected function checkQueryReturn($return): void
28 | {
29 | $this->restoreDBDebug();
30 |
31 | $this->checkValidationError();
32 |
33 | if ($return === false) {
34 | $error = $this->db->error();
35 | $message = 'Query error: ' . $error['code'] . ', '
36 | . $error['message'] . ', query: ' . $this->db->getLastQuery();
37 |
38 | throw new DatabaseException($message, (int) $error['code']);
39 | }
40 | }
41 |
42 | protected function checkValidationError(): void
43 | {
44 | if ($this->validation === null) {
45 | return;
46 | }
47 |
48 | $validationErrors = $this->validation->getErrors();
49 |
50 | if ($validationErrors !== []) {
51 | $message = 'Validation error:';
52 |
53 | foreach ($validationErrors as $field => $error) {
54 | $message .= ' [' . $field . '] ' . $error;
55 | }
56 |
57 | throw new ValidationException($message);
58 | }
59 | }
60 |
61 | protected function disableDBDebug(): void
62 | {
63 | if (! $this->db->DBDebug) {
64 | // `DBDebug` is false. Do nothing.
65 | return;
66 | }
67 |
68 | $this->currentDBDebug = $this->db->DBDebug;
69 |
70 | $propertyDBDebug = $this->getPropertyDBDebug();
71 | $propertyDBDebug->setValue($this->db, false);
72 | }
73 |
74 | protected function restoreDBDebug(): void
75 | {
76 | if ($this->currentDBDebug === null) {
77 | // `DBDebug` has not been changed. Do nothing.
78 | return;
79 | }
80 |
81 | $propertyDBDebug = $this->getPropertyDBDebug();
82 | $propertyDBDebug->setValue($this->db, $this->currentDBDebug);
83 |
84 | $this->currentDBDebug = null;
85 | }
86 |
87 | protected function getPropertyDBDebug(): ReflectionProperty
88 | {
89 | $refClass = new ReflectionObject($this->db);
90 | $refProperty = $refClass->getProperty('DBDebug');
91 | $refProperty->setAccessible(true);
92 |
93 | return $refProperty;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Models/DatabaseException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\Shield\Exceptions\RuntimeException;
17 |
18 | class DatabaseException extends RuntimeException
19 | {
20 | }
21 |
--------------------------------------------------------------------------------
/src/Models/GroupModel.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 |
18 | class GroupModel extends BaseModel
19 | {
20 | protected $primaryKey = 'id';
21 | protected $returnType = 'array';
22 | protected $useSoftDeletes = false;
23 | protected $allowedFields = [
24 | 'user_id',
25 | 'group',
26 | 'created_at',
27 | ];
28 | protected $useTimestamps = false;
29 | protected $validationRules = [];
30 | protected $validationMessages = [];
31 | protected $skipValidation = false;
32 |
33 | protected function initialize(): void
34 | {
35 | parent::initialize();
36 |
37 | $this->table = $this->tables['groups_users'];
38 | }
39 |
40 | public function getForUser(User $user): array
41 | {
42 | $rows = $this->builder()
43 | ->select('group')
44 | ->where('user_id', $user->id)
45 | ->get()
46 | ->getResultArray();
47 |
48 | return array_column($rows, 'group');
49 | }
50 |
51 | /**
52 | * @param int|string $userId
53 | */
54 | public function deleteAll($userId): void
55 | {
56 | $return = $this->builder()
57 | ->where('user_id', $userId)
58 | ->delete();
59 |
60 | $this->checkQueryReturn($return);
61 | }
62 |
63 | /**
64 | * @param int|string $userId
65 | */
66 | public function deleteNotIn($userId, mixed $cache): void
67 | {
68 | $return = $this->builder()
69 | ->where('user_id', $userId)
70 | ->whereNotIn('group', $cache)
71 | ->delete();
72 |
73 | $this->checkQueryReturn($return);
74 | }
75 |
76 | /**
77 | * @param non-empty-string $group Group name
78 | */
79 | public function isValidGroup(string $group): bool
80 | {
81 | $allowedGroups = array_keys(setting('AuthGroups.groups'));
82 |
83 | return in_array($group, $allowedGroups, true);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Models/PermissionModel.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 |
18 | class PermissionModel extends BaseModel
19 | {
20 | protected $primaryKey = 'id';
21 | protected $returnType = 'array';
22 | protected $useSoftDeletes = false;
23 | protected $allowedFields = [
24 | 'user_id',
25 | 'permission',
26 | 'created_at',
27 | ];
28 | protected $useTimestamps = false;
29 | protected $validationRules = [];
30 | protected $validationMessages = [];
31 | protected $skipValidation = false;
32 |
33 | protected function initialize(): void
34 | {
35 | parent::initialize();
36 |
37 | $this->table = $this->tables['permissions_users'];
38 | }
39 |
40 | public function getForUser(User $user): array
41 | {
42 | $rows = $this->builder()
43 | ->select('permission')
44 | ->where('user_id', $user->id)
45 | ->get()
46 | ->getResultArray();
47 |
48 | return array_column($rows, 'permission');
49 | }
50 |
51 | /**
52 | * @param int|string $userId
53 | */
54 | public function deleteAll($userId): void
55 | {
56 | $return = $this->builder()
57 | ->where('user_id', $userId)
58 | ->delete();
59 |
60 | $this->checkQueryReturn($return);
61 | }
62 |
63 | /**
64 | * @param int|string $userId
65 | */
66 | public function deleteNotIn($userId, mixed $cache): void
67 | {
68 | $return = $this->builder()
69 | ->where('user_id', $userId)
70 | ->whereNotIn('permission', $cache)
71 | ->delete();
72 |
73 | $this->checkQueryReturn($return);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Models/RememberModel.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\I18n\Time;
17 | use CodeIgniter\Shield\Entities\User;
18 | use Faker\Generator;
19 | use stdClass;
20 |
21 | class RememberModel extends BaseModel
22 | {
23 | protected $primaryKey = 'id';
24 | protected $returnType = 'object';
25 | protected $useSoftDeletes = false;
26 | protected $allowedFields = [
27 | 'selector',
28 | 'hashedValidator',
29 | 'user_id',
30 | 'expires',
31 | ];
32 | protected $useTimestamps = true;
33 |
34 | protected function initialize(): void
35 | {
36 | parent::initialize();
37 |
38 | $this->table = $this->tables['remember_tokens'];
39 | }
40 |
41 | public function fake(Generator &$faker): stdClass
42 | {
43 | return (object) [
44 | 'user_id' => 1,
45 | 'selector' => 'selector',
46 | 'hashedValidator' => 'validator',
47 | 'expires' => Time::parse('+1 day'),
48 | ];
49 | }
50 |
51 | /**
52 | * Stores a remember-me token for the user.
53 | *
54 | * @TODO `string $expires` → `Time $expires`
55 | */
56 | public function rememberUser(User $user, string $selector, string $hashedValidator, string $expires): void
57 | {
58 | $return = $this->insert([
59 | 'user_id' => $user->id,
60 | 'selector' => $selector,
61 | 'hashedValidator' => $hashedValidator,
62 | 'expires' => Time::parse($expires),
63 | ]);
64 |
65 | $this->checkQueryReturn($return);
66 | }
67 |
68 | /**
69 | * Returns the remember-me token info for a given selector.
70 | */
71 | public function getRememberToken(string $selector): ?stdClass
72 | {
73 | return $this->where('selector', $selector)->get()->getRow(); // @phpstan-ignore-line
74 | }
75 |
76 | /**
77 | * Updates the validator for a given selector.
78 | */
79 | public function updateRememberValidator(stdClass $token): void
80 | {
81 | $return = $this->save($token);
82 |
83 | $this->checkQueryReturn($return);
84 | }
85 |
86 | /**
87 | * Removes all persistent login tokens (remember-me) for a single user
88 | * across all devices they may have logged in with.
89 | */
90 | public function purgeRememberTokens(User $user): void
91 | {
92 | $return = $this->where(['user_id' => $user->id])->delete();
93 |
94 | $this->checkQueryReturn($return);
95 | }
96 |
97 | /**
98 | * Purges the 'auth_remember_tokens' table of any records that are past
99 | * their expiration date already.
100 | */
101 | public function purgeOldRememberTokens(): void
102 | {
103 | $return = $this->where('expires <=', date('Y-m-d H:i:s'))
104 | ->delete();
105 |
106 | $this->checkQueryReturn($return);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Models/TokenLoginModel.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Models;
15 |
16 | use CodeIgniter\I18n\Time;
17 | use CodeIgniter\Shield\Entities\Login;
18 | use Faker\Generator;
19 |
20 | class TokenLoginModel extends LoginModel
21 | {
22 | protected function initialize(): void
23 | {
24 | parent::initialize();
25 |
26 | $this->table = $this->tables['token_logins'];
27 | }
28 |
29 | /**
30 | * Generate a fake login for testing
31 | */
32 | public function fake(Generator &$faker): Login
33 | {
34 | return new Login([
35 | 'ip_address' => $faker->ipv4(),
36 | 'identifier' => 'token: ' . random_string('crypto', 64),
37 | 'user_id' => fake(UserModel::class)->id,
38 | 'date' => Time::parse('-1 day'),
39 | 'success' => true,
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Result.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield;
15 |
16 | use CodeIgniter\Shield\Entities\User;
17 |
18 | class Result
19 | {
20 | protected bool $success = false;
21 |
22 | /**
23 | * Provides a simple explanation of
24 | * the error that happened.
25 | * Typically, a single sentence.
26 | */
27 | protected ?string $reason = null;
28 |
29 | /**
30 | * Extra information.
31 | *
32 | * @var string|User|null `User` when successful. Suggestion strings when fails.
33 | */
34 | protected $extraInfo;
35 |
36 | /**
37 | * @phpstan-param array{success: bool, reason?: string|null, extraInfo?: string|User} $details
38 | * @psalm-param array{success: bool, reason?: string|null, extraInfo?: string|User} $details
39 | */
40 | public function __construct(array $details)
41 | {
42 | foreach ($details as $key => $value) {
43 | assert(property_exists($this, $key), 'Property "' . $key . '" does not exist.');
44 |
45 | $this->{$key} = $value;
46 | }
47 | }
48 |
49 | /**
50 | * Was the result a success?
51 | */
52 | public function isOK(): bool
53 | {
54 | return $this->success;
55 | }
56 |
57 | public function reason(): ?string
58 | {
59 | return $this->reason;
60 | }
61 |
62 | /**
63 | * @return string|User|null `User` when successful. Suggestion strings when fails.
64 | */
65 | public function extraInfo()
66 | {
67 | return $this->extraInfo;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Test/AuthenticationTesting.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Test;
15 |
16 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
17 | use CodeIgniter\Shield\Entities\User;
18 |
19 | /**
20 | * Trait AuthenticationTesting
21 | *
22 | * Helper methods for testing using Shield.
23 | */
24 | trait AuthenticationTesting
25 | {
26 | /**
27 | * Logs the user for testing purposes.
28 | *
29 | * @param bool $pending Whether pending login state or not.
30 | *
31 | * @return $this
32 | */
33 | public function actingAs(User $user, bool $pending = false): self
34 | {
35 | /** @var Session $authenticator */
36 | $authenticator = auth('session')->getAuthenticator();
37 |
38 | if ($pending) {
39 | $authenticator->startLogin($user);
40 | } else {
41 | $authenticator->login($user);
42 | }
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Test/MockInputOutput.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Test;
15 |
16 | use CodeIgniter\CLI\CLI;
17 | use CodeIgniter\Shield\Commands\Utils\InputOutput;
18 | use CodeIgniter\Shield\Exceptions\LogicException;
19 | use CodeIgniter\Test\Filters\CITestStreamFilter;
20 | use CodeIgniter\Test\PhpStreamWrapper;
21 |
22 | final class MockInputOutput extends InputOutput
23 | {
24 | private array $inputs = [];
25 | private array $outputs = [];
26 |
27 | /**
28 | * Sets user inputs.
29 | */
30 | public function setInputs(array $inputs): void
31 | {
32 | $this->inputs = $inputs;
33 | }
34 |
35 | /**
36 | * Takes the last output from the output array.
37 | */
38 | public function getLastOutput(): string
39 | {
40 | return array_pop($this->outputs);
41 | }
42 |
43 | /**
44 | * Takes the first output from the output array.
45 | */
46 | public function getFirstOutput(): string
47 | {
48 | return array_shift($this->outputs);
49 | }
50 |
51 | /**
52 | * Returns all outputs.
53 | */
54 | public function getOutputs(): string
55 | {
56 | return implode('', $this->outputs);
57 | }
58 |
59 | public function prompt(string $field, $options = null, $validation = null): string
60 | {
61 | $input = array_shift($this->inputs);
62 |
63 | CITestStreamFilter::registration();
64 | CITestStreamFilter::addOutputFilter();
65 | CITestStreamFilter::addErrorFilter();
66 |
67 | PhpStreamWrapper::register();
68 | PhpStreamWrapper::setContent($input);
69 |
70 | $userInput = CLI::prompt($field, $options, $validation);
71 |
72 | PhpStreamWrapper::restore();
73 |
74 | CITestStreamFilter::removeOutputFilter();
75 | CITestStreamFilter::removeErrorFilter();
76 |
77 | if ($input !== $userInput) {
78 | throw new LogicException($input . '!==' . $userInput);
79 | }
80 |
81 | return $input;
82 | }
83 |
84 | public function write(
85 | string $text = '',
86 | ?string $foreground = null,
87 | ?string $background = null,
88 | ): void {
89 | CITestStreamFilter::registration();
90 | CITestStreamFilter::addOutputFilter();
91 |
92 | CLI::write($text, $foreground, $background);
93 | $this->outputs[] = CITestStreamFilter::$buffer;
94 |
95 | CITestStreamFilter::removeOutputFilter();
96 | }
97 |
98 | public function error(string $text, string $foreground = 'light_red', ?string $background = null): void
99 | {
100 | CITestStreamFilter::registration();
101 | CITestStreamFilter::addErrorFilter();
102 |
103 | CLI::error($text, $foreground, $background);
104 | $this->outputs[] = CITestStreamFilter::$buffer;
105 |
106 | CITestStreamFilter::removeErrorFilter();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Traits/Activatable.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Traits;
15 |
16 | trait Activatable
17 | {
18 | /**
19 | * Returns true if the user has been activated
20 | * and activation is required after registration.
21 | */
22 | public function isActivated(): bool
23 | {
24 | // If activation is not required, then we're always active.
25 | return ! $this->shouldActivate() || $this->active;
26 | }
27 |
28 | /**
29 | * Returns true if the user has not been activated.
30 | */
31 | public function isNotActivated(): bool
32 | {
33 | return ! $this->isActivated();
34 | }
35 |
36 | /**
37 | * Activates the user.
38 | */
39 | public function activate(): void
40 | {
41 | $users = auth()->getProvider();
42 |
43 | $users->update($this->id, ['active' => 1]);
44 | }
45 |
46 | /**
47 | * Deactivates the user.
48 | */
49 | public function deactivate(): void
50 | {
51 | $users = auth()->getProvider();
52 |
53 | $users->update($this->id, ['active' => 0]);
54 | }
55 |
56 | /**
57 | * Does the Auth actions require activation?
58 | * Check for the generic 'Activator' class name to allow
59 | * for custom implementations, provided they follow the naming convention.
60 | */
61 | private function shouldActivate(): bool
62 | {
63 | return str_contains(setting('Auth.actions')['register'] ?? '', 'Activator');
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Traits/Bannable.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Traits;
15 |
16 | trait Bannable
17 | {
18 | /**
19 | * Is the user banned?
20 | */
21 | public function isBanned(): bool
22 | {
23 | return $this->status && $this->status === 'banned';
24 | }
25 |
26 | /**
27 | * Ban the user from logging in.
28 | *
29 | * @return $this
30 | */
31 | public function ban(?string $message = null): self
32 | {
33 | $this->status = 'banned';
34 | $this->status_message = $message;
35 |
36 | $users = auth()->getProvider();
37 |
38 | $users->save($this);
39 |
40 | return $this;
41 | }
42 |
43 | /**
44 | * Unban the user and allow them to login
45 | *
46 | * @return $this
47 | */
48 | public function unBan(): self
49 | {
50 | $this->status = null;
51 | $this->status_message = null;
52 |
53 | $users = auth()->getProvider();
54 |
55 | $users->save($this);
56 |
57 | return $this;
58 | }
59 |
60 | /**
61 | * Returns the ban message.
62 | */
63 | public function getBanMessage(): ?string
64 | {
65 | return $this->status_message;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Traits/Resettable.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Traits;
15 |
16 | use CodeIgniter\Shield\Authentication\Authenticators\Session;
17 | use CodeIgniter\Shield\Models\UserIdentityModel;
18 |
19 | /**
20 | * Reusable methods to help the
21 | * enforcing of password resets
22 | */
23 | trait Resettable
24 | {
25 | /**
26 | * Returns true|false based on the value of the
27 | * force reset column of the user's identity.
28 | */
29 | public function requiresPasswordReset(): bool
30 | {
31 | $identityModel = model(UserIdentityModel::class);
32 | $identity = $identityModel->getIdentityByType($this, Session::ID_TYPE_EMAIL_PASSWORD);
33 |
34 | return $identity->force_reset;
35 | }
36 |
37 | /**
38 | * Force password reset
39 | */
40 | public function forcePasswordReset(): void
41 | {
42 | // Do nothing if user already requires reset
43 | if ($this->requiresPasswordReset()) {
44 | return;
45 | }
46 |
47 | $this->setForceReset(true);
48 | }
49 |
50 | /**
51 | * Undo Force password reset
52 | */
53 | public function undoForcePasswordReset(): void
54 | {
55 | // If user doesn't require password reset, do nothing
56 | if ($this->requiresPasswordReset() === false) {
57 | return;
58 | }
59 |
60 | $this->setForceReset(false);
61 | }
62 |
63 | /**
64 | * Set force_reset
65 | */
66 | private function setForceReset(bool $value): void
67 | {
68 | $value = (int) $value;
69 |
70 | $identityModel = model(UserIdentityModel::class);
71 | $identityModel->set('force_reset', $value);
72 | $identityModel->where(['user_id' => $this->id, 'type' => Session::ID_TYPE_EMAIL_PASSWORD]);
73 | $identityModel->update();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Traits/Viewable.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Traits;
15 |
16 | trait Viewable
17 | {
18 | /**
19 | * Provides a way for third-party systems to simply override
20 | * the way the view gets converted to HTML to integrate with their
21 | * own templating systems.
22 | */
23 | protected function view(string $view, array $data = [], array $options = []): string
24 | {
25 | return view($view, $data, $options);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Validation/ValidationRules.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view
11 | * the LICENSE file that was distributed with this source code.
12 | */
13 |
14 | namespace CodeIgniter\Shield\Validation;
15 |
16 | use CodeIgniter\Shield\Authentication\Passwords;
17 | use CodeIgniter\Shield\Config\Auth;
18 |
19 | class ValidationRules
20 | {
21 | protected Auth $config;
22 |
23 | /**
24 | * Auth Table names
25 | */
26 | protected array $tables;
27 |
28 | public function __construct()
29 | {
30 | /** @var Auth $authConfig */
31 | $authConfig = config('Auth');
32 |
33 | $this->config = $authConfig;
34 | $this->tables = $this->config->tables;
35 | }
36 |
37 | public function getRegistrationRules(): array
38 | {
39 | $setting = setting('Validation.registration');
40 | if ($setting !== null) {
41 | return $setting;
42 | }
43 |
44 | $usernameRules = $this->config->usernameValidationRules;
45 | $usernameRules['rules'][] = sprintf(
46 | 'is_unique[%s.username]',
47 | $this->tables['users'],
48 | );
49 |
50 | $emailRules = $this->config->emailValidationRules;
51 | $emailRules['rules'][] = sprintf(
52 | 'is_unique[%s.secret]',
53 | $this->tables['identities'],
54 | );
55 |
56 | $passwordRules = $this->getPasswordRules();
57 | $passwordRules['rules'][] = 'strong_password[]';
58 |
59 | return [
60 | 'username' => $usernameRules,
61 | 'email' => $emailRules,
62 | 'password' => $passwordRules,
63 | 'password_confirm' => $this->getPasswordConfirmRules(),
64 | ];
65 | }
66 |
67 | public function getLoginRules(): array
68 | {
69 | return setting('Validation.login') ?? [
70 | // 'username' => $this->config->usernameValidationRules,
71 | 'email' => $this->config->emailValidationRules,
72 | 'password' => $this->getPasswordRules(),
73 | ];
74 | }
75 |
76 | public function getPasswordRules(): array
77 | {
78 | return [
79 | 'label' => 'Auth.password',
80 | 'rules' => ['required', Passwords::getMaxLengthRule()],
81 | 'errors' => [
82 | 'max_byte' => 'Auth.errorPasswordTooLongBytes',
83 | ],
84 | ];
85 | }
86 |
87 | public function getPasswordConfirmRules(): array
88 | {
89 | return [
90 | 'label' => 'Auth.passwordConfirm',
91 | 'rules' => 'required|matches[password]',
92 | ];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Views/Email/email_2fa_email.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | = lang('Auth.email2FASubject') ?>
9 |
10 |
11 |
12 |