diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index b8d7bcfba..ebaf4df1e 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -19,54 +19,5 @@ on: - '.github/workflows/deptrac.yml' jobs: - build: - name: Dependency Tracing - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - tools: phive - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Deptrac cache directory - run: mkdir -p build/ - - - name: Cache Deptrac results - uses: actions/cache@v3 - with: - path: build - key: ${{ runner.os }}-deptrac-${{ github.sha }} - restore-keys: ${{ runner.os }}-deptrac- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Trace dependencies - run: | - sudo phive --no-progress install --global --trust-gpg-keys B8F640134AB1782E,A98E898BB53EB748 qossmic/deptrac - deptrac analyze --cache-file=build/deptrac.cache + deptrac: + uses: codeigniter4/.github/.github/workflows/deptrac.yml@main diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 61269b119..e6e623ce8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,9 +9,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x - - run: pip install mkdocs-material + - run: pip3 install mkdocs-material + - run: pip3 install mkdocs-git-revision-date-localized-plugin + - run: pip3 install mkdocs-redirects - run: mkdocs gh-deploy --force diff --git a/.github/workflows/no-merge-commits.yml b/.github/workflows/no-merge-commits.yml new file mode 100644 index 000000000..11173fbb4 --- /dev/null +++ b/.github/workflows/no-merge-commits.yml @@ -0,0 +1,22 @@ +name: Detect Merge Commits + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + test: + name: Check for merge commits + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run test + uses: NexusPHP/no-merge-commits@v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/phpcpd.yml b/.github/workflows/phpcpd.yml index ecd45d1c6..2704b80fc 100644 --- a/.github/workflows/phpcpd.yml +++ b/.github/workflows/phpcpd.yml @@ -15,22 +15,8 @@ on: - '.github/workflows/phpcpd.yml' jobs: - build: - name: Code Copy-Paste Detection - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phpcpd - extensions: dom, mbstring - coverage: none - - - name: Detect duplicate code - run: phpcpd src/ tests/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php + phpcpd: + uses: codeigniter4/.github/.github/workflows/phpcpd.yml@main + with: + dirs: "src/ tests/" + options: "--exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php --exclude tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php" diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml index cbf5566a8..ee1221ac2 100644 --- a/.github/workflows/phpcsfixer.yml +++ b/.github/workflows/phpcsfixer.yml @@ -15,45 +15,5 @@ on: - '.github/workflows/phpcsfixer.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Coding Standards - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: json, tokenizer - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Check code for standards compliance - run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff + phpcsfixer: + uses: codeigniter4/.github/.github/workflows/phpcsfixer.yml@main diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 9e0f6339a..58e2add51 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -19,56 +19,5 @@ on: - '.github/workflows/phpstan.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Static Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: phpstan, phpunit - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create PHPStan cache directory - run: mkdir -p build/phpstan - - - name: Cache PHPStan results - uses: actions/cache@v3 - with: - path: build/phpstan - key: ${{ runner.os }}-phpstan-${{ github.sha }} - restore-keys: ${{ runner.os }}-phpstan- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Run static analysis - run: vendor/bin/phpstan analyze + phpstan: + uses: codeigniter4/.github/.github/workflows/phpstan.yml@main diff --git a/.github/workflows/phpunit-lang.yml b/.github/workflows/phpunit-lang.yml index f8f1db140..dbd7f5a2b 100644 --- a/.github/workflows/phpunit-lang.yml +++ b/.github/workflows/phpunit-lang.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/phpunit-lowest.yml b/.github/workflows/phpunit-lowest.yml new file mode 100644 index 000000000..242a89803 --- /dev/null +++ b/.github/workflows/phpunit-lowest.yml @@ -0,0 +1,23 @@ +name: PHPUnit Lowest + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit-lowest.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpunit*' + - '.github/workflows/phpunit-lowest.yml' + +jobs: + phpunit: + uses: codeigniter4/.github/.github/workflows/phpunit-lowest.yml@main diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f88c90578..9bcd5ec40 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -19,167 +19,5 @@ on: - '.github/workflows/phpunit.yml' jobs: - main: - name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - ${{ matrix.dependencies }} - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - matrix: - php-versions: ['7.4', '8.0', '8.1', '8.2'] - db-platforms: ['MySQLi', 'SQLite3'] - mysql-versions: ['5.7'] - dependencies: ['highest'] - include: - # MySQL 8.0 - - php-versions: '7.4' - db-platforms: MySQLi - mysql-versions: '8.0' - dependencies: 'highest' - # Lowest Dependency - - php-versions: '7.4' - db-platforms: MySQLi - mysql-versions: '5.7' - dependencies: 'lowest' - # Postgre - - php-versions: '7.4' - db-platforms: Postgre - mysql-versions: '5.7' - dependencies: 'highest' - # SQLSRV - - php-versions: '7.4' - db-platforms: SQLSRV - mysql-versions: '5.7' - dependencies: 'highest' - # OCI8 - - php-versions: '7.4' - db-platforms: OCI8 - mysql-versions: '5.7' - dependencies: 'highest' - - services: - mysql: - image: mysql:${{ matrix.mysql-versions }} - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: test - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - postgres: - image: postgres - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test - ports: - - 5432:5432 - options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 - - mssql: - image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 - env: - SA_PASSWORD: 1Secure*Password1 - ACCEPT_EULA: Y - MSSQL_PID: Developer - ports: - - 1433:1433 - options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 - - oracle: - image: quillbuilduser/oracle-18-xe - env: - ORACLE_ALLOW_REMOTE: true - ports: - - 1521:1521 - options: --health-cmd="/opt/oracle/product/18c/dbhomeXE/bin/sqlplus -s sys/Oracle18@oracledbxe/XE as sysdba <<< 'SELECT 1 FROM DUAL'" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Create database for MSSQL Server - if: matrix.db-platforms == 'SQLSRV' - run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" - - - name: Install Oracle InstantClient - if: matrix.db-platforms == 'OCI8' - run: | - sudo apt-get install wget libaio1 alien - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo dpkg -i oracle-instantclient18.5-basic_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-devel_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-sqlplus_18.5.0.0.0-4_amd64.deb - echo "LD_LIBRARY_PATH=/lib/oracle/18.5/client64/lib/" >> $GITHUB_ENV - echo "NLS_LANG=AMERICAN_AMERICA.UTF8" >> $GITHUB_ENV - echo "C_INCLUDE_PATH=/usr/include/oracle/18.5/client64" >> $GITHUB_ENV - echo 'NLS_DATE_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_TZ_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - - - name: Create database for Oracle Database - if: matrix.db-platforms == 'OCI8' - run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba - - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: composer, phive, phpunit - extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, sqlsrv, oci8, pgsql - coverage: xdebug - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install ${{ env.COMPOSER_UPDATE_FLAGS }} --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update ${{ env.COMPOSER_UPDATE_FLAGS }} --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - env: - COMPOSER_UPDATE_FLAGS: ${{ matrix.dependencies == 'lowest' && '--prefer-lowest' || '' }} - - - name: Test with PHPUnit - run: vendor/bin/phpunit --verbose --coverage-text --testsuite main - env: - DB: ${{ matrix.db-platforms }} - TERM: xterm-256color - TACHYCARDIA_MONITOR_GA: enabled - - - if: matrix.php-versions == '8.0' - name: Run Coveralls - continue-on-error: true - run: | - sudo phive --no-progress install --global --trust-gpg-keys E82B2FB314E9906E php-coveralls - php-coveralls --verbose --coverage_clover=build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - - coveralls: - needs: [main] - name: Coveralls Finished - runs-on: ubuntu-latest - steps: - - name: Upload Coveralls results - uses: coverallsapp/github-action@master - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + phpunit: + uses: codeigniter4/.github/.github/workflows/phpunit.yml@main diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 67be11c56..53c76e7f9 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -19,52 +19,5 @@ on: - '.github/workflows/psalm.yml' jobs: - build: - name: Psalm Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phpstan, phpunit - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Psalm cache directory - run: mkdir -p build/psalm - - - name: Cache Psalm results - uses: actions/cache@v3 - with: - path: build/psalm - key: ${{ runner.os }}-psalm-${{ github.sha }} - restore-keys: ${{ runner.os }}-psalm- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Run Psalm analysis - run: vendor/bin/psalm + psalm: + uses: codeigniter4/.github/.github/workflows/psalm.yml@main diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 0abf2719b..8c19b162d 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -19,48 +19,5 @@ on: - '.github/workflows/rector.yml' jobs: - build: - name: PHP ${{ matrix.php-versions }} Rector Analysis - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - fail-fast: false - matrix: - php-versions: ['7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: phpstan - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Analyze for refactoring - run: | - composer global require --dev rector/rector:^0.15.1 - rector process --dry-run --no-progress-bar + rector: + uses: codeigniter4/.github/.github/workflows/rector.yml@main diff --git a/.github/workflows/smart-commenting.yml b/.github/workflows/smart-commenting.yml new file mode 100644 index 000000000..a8d8690a7 --- /dev/null +++ b/.github/workflows/smart-commenting.yml @@ -0,0 +1,62 @@ +name: Smart Commenting +on: + pull_request: + types: + - labeled +jobs: + + add-comment-for-GPG-Signing: + if: github.event.label.name == 'GPG-Signing needed' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for GPG-sign + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + You must GPG-sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. See Developer's Certificate of Origin. + See [signing][1]. + + **Note that all your commits must be signed.** If you have an unsigned commit, you can sign the previous commits by referring to [gpg-signing-old-commits][2]. + + [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#signing + [2]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/workflow.md#gpg-signing-old-commits + + add-comment-for-tests: + if: github.event.label.name == 'tests needed' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for PHPUnit test + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + **Unit Testing:** + Unit testing is expected for all CodeIgniter components. We use PHPUnit, and run unit tests using GitHub Actions for each PR submitted or changed. + + **So please write a unit test or update the existing tests.** + + See [unit testing][1] for more info. + + [1]: https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#unit-testing + + add-comment-for-conflict: + if: github.event.label.name == 'stale' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add comment for resolving a merge conflict + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + We detected conflicts in your PR against the base branch :speak_no_evil: + You may want to sync :arrows_counterclockwise: your branch with upstream! + See [resolving a merge conflict using the Git][1] for more info. + + [1]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index baec23af2..1758dda62 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -17,42 +17,5 @@ on: - '.github/workflows/unused.yml' jobs: - build: - name: Unused Package Detection - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: composer, composer-unused - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Detect unused packages - run: composer-unused -vvv --output-format=github --ansi --no-interaction --no-progress + unused: + uses: codeigniter4/.github/.github/workflows/unused.yml@main diff --git a/LICENSE b/LICENSE index 8a8446d16..422da62e7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2021 Lonnie Ezell +Copyright (c) 2020-2022 Lonnie Ezell +Copyright (c) 2022-2023 CodeIgniter Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 73b4f360c..8df491a39 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ [](https://github.com/codeigniter4/shield/actions/workflows/phpunit.yml) [](https://github.com/codeigniter4/shield/actions/workflows/phpstan.yml) +[](https://github.com/codeigniter4/shield/actions/workflows/phpcsfixer.yml) +[](https://github.com/codeigniter4/shield/actions/workflows/rector.yml) +[](https://github.com/codeigniter4/shield/actions/workflows/psalm.yml) [](https://github.com/codeigniter4/shield/actions/workflows/deptrac.yml) [](https://coveralls.io/github/codeigniter4/shield?branch=develop) -Shield is an authentication and authorization framework for CodeIgniter 4. While it does provide a base set of tools +Shield is the official authentication and authorization framework for CodeIgniter 4. +While it does provide a base set of tools that are commonly used in websites, it is designed to be flexible and easily customizable. The primary goals for Shield are: @@ -15,38 +19,44 @@ The primary goals for Shield are: ## Authentication Methods -Shield provides two primary methods **Session-based** and **Personal Access Codes** -of authentication out of the box. +Shield provides two primary methods **Session-based** and **Access Token** +authentication out of the box. -It also provides **JSON Web Tokens** authentication. +It also provides **HMAC SHA256 Token** and **JSON Web Token** authentication. ### Session-based -This is your typical email/username/password system you see everywhere. It includes a secure "remember me" functionality. +This is your typical email/username/password system you see everywhere. It includes a secure "remember-me" functionality. This can be used for standard web applications, as well as for single page applications. Includes full controllers and basic views for all standard functionality, like registration, login, forgot password, etc. -### Personal Access Codes +### Access Token -These are much like the access codes that GitHub uses, where they are unique to a single user, and a single user +These are much like the access tokens that GitHub uses, where they are unique to a single user, and a single user can have more than one. This can be used for API authentication of third-party users, and even for allowing access for a mobile application that you build. -### JSON Web Tokens +### HMAC SHA256 Token + +This is a slightly more complicated improvement on Access Token authentication. +The main advantage with HMAC is the shared Secret Key +is not passed in the request, but is instead used to create a hash signature of the request body. + +### JSON Web Token JWT or JSON Web Token is a compact and self-contained way of securely transmitting information between parties as a JSON object. It is commonly used for authentication and authorization purposes in web applications. -## Some Important Features +## Important Features -* Session-based authentication (traditional email/password with remember me) +* Session-based authentication (traditional ID/Password with Remember-me) * Stateless authentication using Personal Access Tokens * Optional Email verification on account registration -* Optional Email-based Two Factor Authentication after login -* Magic Login Links when a user forgets their password -* Flexible groups-based access control (think roles, but more flexible) -* Users can be granted additional permissions +* Optional Email-based Two-Factor Authentication after login +* Magic Link Login when a user forgets their password +* Flexible Groups-based access control (think Roles, but more flexible) +* Users can be granted additional Permissions See the [An Official Auth Library](https://codeigniter.com/news/shield) for more Info. @@ -56,13 +66,14 @@ See the [An Official Auth Library](https://codeigniter.com/news/shield) for more Usage of Shield requires the following: -- A [CodeIgniter 4.2.7+](https://github.com/codeigniter4/CodeIgniter4/) based project +- A [CodeIgniter 4.3.5+](https://github.com/codeigniter4/CodeIgniter4/) based project - [Composer](https://getcomposer.org/) for package management - PHP 7.4.3+ ### Installation Installation is done through Composer. + ```console composer require codeigniter4/shield ``` @@ -73,14 +84,15 @@ See the docs diff --git a/UPGRADING.md b/UPGRADING.md index 1ffe19509..1a6bcd5cd 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,34 @@ # Upgrade Guide +## Version 1.0.0-beta.6 to 1.0.0-beta.7 + +### The minimum CodeIgniter version + +Shield requires CodeIgniter 4.3.5 or later. +Versions prior to 4.3.5 have known vulnerabilities. +See https://github.com/codeigniter4/CodeIgniter4/security/advisories + +### Mandatory Config Changes + +#### New Config\AuthToken + +A new Config file **AuthToken.php** has been introduced. Run `php spark shield:setup` +again to install it into **app/Config/**, or install it manually. + +Then change the default settings as necessary. When using Token authentication, +the default value has been changed from all accesses to be recorded in the +``token_logins`` table to only accesses that fail authentication to be recorded. + +#### Config\Auth + +The following items have been moved. They are no longer used and should be removed. + +- `$authenticatorHeader` and `$unusedTokenLifetime` are moved to `Config\AuthToken`. + +The following items have been added. Copy the properties in **src/Config/Auth.php**. + +- `$usernameValidationRules` and `$emailValidationRules` are added. + ## Version 1.0.0-beta.3 to 1.0.0-beta.4 ### Important Password Changes diff --git a/admin/RELEASE.md b/admin/RELEASE.md index 27f73b24a..69cb216b2 100644 --- a/admin/RELEASE.md +++ b/admin/RELEASE.md @@ -71,7 +71,7 @@ the changelog. * Watch for the "docs" action and verify that the user guide updated: * [docs](https://github.com/codeigniter4/shield/actions/workflows/docs.yml) * Fast-forward `develop` branch to catch the merge commit from `master` - (note: pushing to develop is restricted to administrators): + (note: pushing to `develop` is restricted to administrators): ```console git fetch origin git checkout develop @@ -79,6 +79,9 @@ the changelog. git merge origin/master git push origin HEAD # Only administrators can push to the protected branch. ``` + **At this point, `master` must be merged into `develop`.** Otherwise, the + GitHub-generated release note from `develop` for the next release will not be + generated correctly. * Publish any Security Advisories that were resolved from private forks (note: publishing is restricted to administrators) * Announce the release on the forums and Slack channel diff --git a/admin/how_to_build_docs.md b/admin/how_to_build_docs.md new file mode 100644 index 000000000..9162fd73b --- /dev/null +++ b/admin/how_to_build_docs.md @@ -0,0 +1,39 @@ +# How to Build Shield Documentation + +We use Material for MkDocs for our documentation. +See https://squidfunk.github.io/mkdocs-material/getting-started/. + +## Requirements + +- Python3 +- pip + +## Installation + +```console +pip3 install mkdocs +pip3 install mkdocs-material +pip3 install mkdocs-git-revision-date-localized-plugin +pip3 install mkdocs-redirects +``` + +## Build the Docs + +```consolse +mkdocs build +``` + +## See the Docs + +```consolse +mkdocs serve +``` + +## Deploy Manually + +The documentation is built and deployed automatically by GitHub Actions. But if +you need to deploy manually, run the following command: + +```console +mkdocs gh-deploy +``` diff --git a/admin/pre-commit b/admin/pre-commit index 296e140e2..617482977 100644 --- a/admin/pre-commit +++ b/admin/pre-commit @@ -19,21 +19,6 @@ if [ "$STAGED_PHP_FILES" != "" ]; then done fi -if [ "$FILES" != "" ]; then - echo "Running PHPStan..." - - if [ -d /proc/cygdrive ]; then - ./vendor/bin/phpstan analyse - else - php ./vendor/bin/phpstan analyse - fi - - if [ $? != 0 ]; then - echo "Fix the phpstan error(s) before commit." - exit 1 - fi -fi - if [ "$FILES" != "" ]; then echo "Running PHP CS Fixer..." diff --git a/composer.json b/composer.json index 42ee9f2d5..d7a4327bd 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,27 @@ } ], "homepage": "https://github.com/codeigniter4/shield", + "support": { + "issues": "https://github.com/codeigniter4/shield/issues", + "forum": "https://github.com/codeigniter4/shield/discussions", + "source": "https://github.com/codeigniter4/shield", + "docs": "https://codeigniter4.github.io/shield/", + "slack": "https://codeigniterchat.slack.com" + }, "require": { "php": "^7.4.3 || ^8.0", "codeigniter4/settings": "^2.1" }, "require-dev": { + "codeigniter/phpstan-codeigniter": "^1.3", "codeigniter4/devkit": "^1.0", - "codeigniter4/framework": "^4.2.7", + "codeigniter4/framework": "^4.3.5", + "firebase/php-jwt": "^6.4", "mikey179/vfsstream": "^1.6.7", "mockery/mockery": "^1.0", - "firebase/php-jwt": "^6.4" + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "rector/rector": "0.18.5" }, "provide": { "codeigniter4/authentication-implementation": "1.0" @@ -41,12 +52,11 @@ "psr-4": { "CodeIgniter\\Shield\\": "src" }, + "files": [ + "src/Helpers/auth_helper.php" + ], "exclude-from-classmap": [ "**/Database/Migrations/**" - ], - "files": [ - "src/Helpers/auth_helper.php", - "src/Helpers/email_helper.php" ] }, "autoload-dev": { @@ -58,7 +68,8 @@ "config": { "allow-plugins": { "phpstan/extension-installer": true - } + }, + "sort-packages": true }, "scripts": { "post-update-cmd": [ @@ -69,7 +80,6 @@ "psalm", "rector process --dry-run" ], - "sa": "@analyze", "ci": [ "Composer\\Config::disableProcessTimeout", "@cs", @@ -80,17 +90,11 @@ ], "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff", "cs-fix": "php-cs-fixer fix --ansi --verbose --diff", - "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php", + "deduplicate": "phpcpd app/ src/ --exclude src/Database/Migrations/2020-12-28-223112_create_auth_tables.php --exclude src/Authentication/Authenticators/HmacSha256.php", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", + "sa": "@analyze", "style": "@cs-fix", "test": "phpunit" - }, - "support": { - "forum": "https://github.com/codeigniter4/shield/discussions", - "slack": "https://codeigniterchat.slack.com", - "source": "https://github.com/codeigniter4/shield", - "issues": "https://github.com/codeigniter4/shield/issues", - "docs": "https://github.com/codeigniter4/shield/blob/develop/docs/index.md" } } diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index efcc54c8c..ad87cfb4e 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -1,7 +1,8 @@ # JWT Authentication -> **Note** -> Shield now supports only JWS (Singed JWT). JWE (Encrypted JWT) is not supported. +!!! note + + Shield now supports only JWS (Singed JWT). JWE (Encrypted JWT) is not supported. ## What is JWT? @@ -54,9 +55,9 @@ To use JWT Authentication, you need additional setup and configuration. You need to add the following constants: ```php - public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all - public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures - public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts + public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all + public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures + public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts ``` You need to add JWT Authenticator: @@ -65,20 +66,20 @@ To use JWT Authentication, you need additional setup and configuration. // ... - public array $authenticators = [ - 'tokens' => AccessTokens::class, - 'session' => Session::class, - 'jwt' => JWT::class, - ]; + public array $authenticators = [ + 'tokens' => AccessTokens::class, + 'session' => Session::class, + 'jwt' => JWT::class, + ]; ``` If you want to use JWT Authenticator in Authentication Chain, add `jwt`: ```php - public array $authenticationChain = [ - 'session', - 'tokens', - 'jwt' - ]; + public array $authenticationChain = [ + 'session', + 'tokens', + 'jwt' + ]; ``` ## Configuration @@ -87,17 +88,18 @@ Configure **app/Config/AuthJWT.php** for your needs. ### Set the Default Claims -> **Note** -> A payload contains the actual data being transmitted, such as user ID, role, -> or expiration time. Items in a payload is called *claims*. +!!! note + + A payload contains the actual data being transmitted, such as user ID, role, + or expiration time. Items in a payload is called *claims*. Set the default payload items to the property `$defaultClaims`. E.g.: ```php - public array $defaultClaims = [ - 'iss' => 'https://codeigniter.com/', - ]; +public array $defaultClaims = [ + 'iss' => 'https://codeigniter.com/', +]; ``` The default claims will be included in all tokens issued by Shield. @@ -107,7 +109,7 @@ The default claims will be included in all tokens issued by Shield. Set your secret key in the `$keys` property, or set it in your `.env` file. E.g.: -```dotenv +```text authjwt.keys.default.0.secret = 8XBFsF6HThIa7OV/bSynahEch+WbKrGcuiJVYPiwqPE= ``` @@ -121,8 +123,9 @@ with the following command: php -r 'echo base64_encode(random_bytes(32));' ``` -> **Note** -> The secret key is used for signing and validating tokens. +!!! note + + The secret key is used for signing and validating tokens. ## Issuing JWTs @@ -147,8 +150,7 @@ use CodeIgniter\API\ResponseTrait; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Shield\Authentication\Authenticators\Session; use CodeIgniter\Shield\Authentication\JWTManager; -use CodeIgniter\Shield\Authentication\Passwords; -use CodeIgniter\Shield\Config\AuthSession; +use CodeIgniter\Shield\Validation\ValidationRules; class LoginController extends BaseController { @@ -163,7 +165,7 @@ class LoginController extends BaseController $rules = $this->getValidationRules(); // Validate credentials - if (! $this->validateData($this->request->getJSON(true), $rules)) { + if (! $this->validateData($this->request->getJSON(true), $rules, [], config('Auth')->DBGroup)) { return $this->fail( ['errors' => $this->validator->getErrors()], $this->codes['unauthorized'] @@ -212,26 +214,16 @@ class LoginController extends BaseController */ protected function getValidationRules(): array { - return setting('Validation.login') ?? [ - 'email' => [ - 'label' => 'Auth.email', - 'rules' => config(AuthSession::class)->emailValidationRules, - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|' . Passwords::getMaxLenghtRule(), - 'errors' => [ - 'max_byte' => 'Auth.errorPasswordTooLongBytes', - ], - ], - ]; + $rules = new ValidationRules(); + + return $rules->getLoginRules(); } } ``` You could send a request with the existing user's credentials by curl like this: -```console +```curl curl --location 'http://localhost:8080/auth/jwt' \ --header 'Content-Type: application/json' \ --data-raw '{"email" : "admin@example.jp" , "password" : "passw0rd!"}' @@ -242,7 +234,7 @@ the `Authorization` header as a `Bearer` token. You could send a request with the `Authorization` header by curl like this: -```console +```curl curl --location --request GET 'http://localhost:8080/api/users' \ --header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTaGllbGQgVGVzdCBBcHAiLCJzdWIiOiIxIiwiaWF0IjoxNjgxODA1OTMwLCJleHAiOjE2ODE4MDk1MzB9.DGpOmRPOBe45whVtEOSt53qJTw_CpH0V8oMoI_gm2XI' ``` diff --git a/docs/assets/css/dark_mode.css b/docs/assets/css/dark_mode.css new file mode 100644 index 000000000..8bc72d6a8 --- /dev/null +++ b/docs/assets/css/dark_mode.css @@ -0,0 +1,74 @@ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #6a290d; + --md-primary-fg-color--light: #8d7474; + --md-primary-fg-color--dark: #6d554d; + + .hljs-title, + .hljs-title.class_, + .hljs-title.class_.inherited__, + .hljs-title.function_ { + color: #c9a69b; + } + + .hljs-meta .hljs-string, + .hljs-regexp, + .hljs-string { + color: #a3b4c7; + } + + .hljs-attr, + .hljs-attribute, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-operator, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-id, + .hljs-variable { + color: #c1b79f; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-meta .hljs-keyword, + .hljs-template-tag, + .hljs-template-variable, + .hljs-type, + .hljs-variable.language_ { + color: #c97100; + } + + .hljs-subst { + color: #ddba52 + } + + .md-typeset .note > .admonition-title, + .md-typeset .note > summary { + background-color: #0000001a; + } + + .md-typeset .admonition.note, + .md-typeset details.note { + border-color: #675647; + } + + .md-typeset .note > .admonition-title:before, + .md-typeset .note > summary:before { + background-color: #65686d; + -webkit-mask-image: var(--md-admonition-icon--note); + mask-image: var(--md-admonition-icon--note); + } + + .md-typeset .admonition.warning, + .md-typeset details.warning { + border-color: #776144; + } + + .md-typeset .warning > .admonition-title:before, + .md-typeset .warning > summary:before { + background-color: #d9913bc2; + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-image: var(--md-admonition-icon--warning); + } +} diff --git a/docs/assets/js/curl.min.js b/docs/assets/js/curl.min.js new file mode 100644 index 000000000..924af722a --- /dev/null +++ b/docs/assets/js/curl.min.js @@ -0,0 +1,14 @@ +/*! `curl` grammar compiled for Highlight.js 11.3.1 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={className:"string",begin:/"/, +end:/"/,contains:[e.BACKSLASH_ESCAPE,{className:"variable",begin:/\$\(/, +end:/\)/,contains:[e.BACKSLASH_ESCAPE]}],relevance:0},a={className:"number", +variants:[{begin:e.C_NUMBER_RE}],relevance:0};return{name:"curl", +aliases:["curl"],keywords:"curl",case_insensitive:!0,contains:[{ +className:"literal",begin:/(--request|-X)\s/,contains:[{className:"symbol", +begin:/(get|post|delete|options|head|put|patch|trace|connect)/,end:/\s/, +returnEnd:!0}],returnEnd:!0,relevance:10},{className:"literal",begin:/--/, +end:/[\s"]/,returnEnd:!0,relevance:0},{className:"literal",begin:/-\w/, +end:/[\s"]/,returnEnd:!0,relevance:0},n,{className:"string",begin:/\\"/, +relevance:0},{className:"string",begin:/'/,end:/'/,relevance:0 +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,{match:/(\/[a-z._-]+)+/}]}}})() +;hljs.registerLanguage("curl",e)})(); \ No newline at end of file diff --git a/docs/assets/hljs.js b/docs/assets/js/hljs.js similarity index 100% rename from docs/assets/hljs.js rename to docs/assets/js/hljs.js diff --git a/docs/authentication.md b/docs/authentication.md deleted file mode 100644 index 3b177d3dc..000000000 --- a/docs/authentication.md +++ /dev/null @@ -1,305 +0,0 @@ -# Authentication - -- [Authentication](#authentication) - - [Available Authenticators](#available-authenticators) - - [Auth Helper](#auth-helper) - - [Authenticator Responses](#authenticator-responses) - - [isOK()](#isok) - - [reason()](#reason) - - [extraInfo()](#extrainfo) - - [Session Authenticator](#session-authenticator) - - [attempt()](#attempt) - - [check()](#check) - - [loggedIn()](#loggedin) - - [logout()](#logout) - - [forget()](#forget) - - [Access Token Authenticator](#access-token-authenticator) - - [Access Token/API Authentication](#access-tokenapi-authentication) - - [Generating Access Tokens](#generating-access-tokens) - - [Revoking Access Tokens](#revoking-access-tokens) - - [Retrieving Access Tokens](#retrieving-access-tokens) - - [Access Token Lifetime](#access-token-lifetime) - - [Access Token Scopes](#access-token-scopes) - -Authentication is the process of determining that a visitor actually belongs to your website, -and identifying them. Shield provides a flexible and secure authentication system for your -web apps and APIs. - -## Available Authenticators - -Shield ships with 2 authenticators that will serve several typical situations within web app development: the -Session authenticator, which uses username/email/password to authenticate against and stores it in the session, -and the Access Tokens authenticator which uses private access tokens passed in the headers. - -The available authenticators are defined in `Config\Auth`: - -```php -public $authenticators = [ - // alias => classname - 'session' => Session::class, - 'tokens' => AccessTokens::class, -]; -``` - -The default authenticator is also defined in the configuration file, and uses the alias given above: - -```php -public $defaultAuthenticator = 'session'; -``` - -## Auth Helper - -The auth functionality is designed to be used with the `auth_helper` that comes with Shield. This -helper method provides the `auth()` function which returns a convenient interface to the most frequently -used functionality within the auth libraries. - -```php -// get the current user -auth()->user(); - -// get the current user's id -auth()->id(); -// or -user_id(); - -// get the User Provider (UserModel by default) -auth()->getProvider(); -``` - -> **Note** -> The `auth_helper` is autoloaded by Composer. If you want to *override* the functions, -> you need to define them in **app/Common.php**. - -## Authenticator Responses - -Many of the authenticator methods will return a `CodeIgniter\Shield\Result` class. This provides a consistent -way of checking the results and can have additional information returned along with it. The class -has the following methods: - -### isOK() - -Returns a boolean value stating whether the check was successful or not. - -### reason() - -Returns a message that can be displayed to the user when the check fails. - -### extraInfo() - -Can return a custom bit of information. These will be detailed in the method descriptions below. - - -## Session Authenticator - -The Session authenticator stores the user's authentication within the user's session, and on a secure cookie -on their device. This is the standard password-based login used in most web sites. It supports a -secure remember me feature, and more. This can also be used to handle authentication for -single page applications (SPAs). - -### attempt() - -When a user attempts to login with their email and password, you would call the `attempt()` method -on the auth class, passing in their credentials. - -```php -$credentials = [ - 'email' => $this->request->getPost('email'), - 'password' => $this->request->getPost('password') -]; - -$loginAttempt = auth()->attempt($credentials); - -if (! $loginAttempt->isOK()) { - return redirect()->back()->with('error', $loginAttempt->reason()); -} -``` - -Upon a successful `attempt()`, the user is logged in. The Response object returned will provide -the user that was logged in as `extraInfo()`. - -```php -$result = auth()->attempt($credentials); - -if ($result->isOK()) { - $user = $result->extraInfo(); -} -``` - -If the attempt fails a `failedLogin` event is triggered with the credentials array as -the only parameter. Whether or not they pass, a login attempt is recorded in the `auth_logins` table. - -If `allowRemembering` is `true` in the `Auth` config file, you can tell the Session authenticator -to set a secure remember-me cookie. - -```php -$loginAttempt = auth()->remember()->attempt($credentials); -``` - -### check() - -If you would like to check a user's credentials without logging them in, you can use the `check()` -method. - -```php -$credentials = [ - 'email' => $this->request->getPost('email'), - 'password' => $this->request->getPost('password') -]; - -$validCreds = auth()->check($credentials); - -if (! $validCreds->isOK()) { - return redirect()->back()->with('error', $validCreds->reason()); -} -``` - -The Result instance returned contains the valid user as `extraInfo()`. - -### loggedIn() - -You can determine if a user is currently logged in with the aptly titled method, `loggedIn()`. - -```php -if (auth()->loggedIn()) { - // Do something. -} -``` - -### logout() - -You can call the `logout()` method to log the user out of the current session. This will destroy and -regenerate the current session, purge any remember-me tokens current for this user, and trigger a -`logout` event. - -```php -auth()->logout(); -``` - -### forget() - -The `forget` method will purge all remember-me tokens for the current user, making it so they -will not be remembered on the next visit to the site. - - - -## Access Token Authenticator - -The Access Token authenticator supports the use of revoke-able API tokens without using OAuth. These are commonly -used to provide third-party developers access to your API. These tokens typically have a very long -expiration time, often years. - -These are also suitable for use with mobile applications. In this case, the user would register/sign-in -with their email/password. The application would create a new access token for them, with a recognizable -name, like John's iPhone 12, and return it to the mobile application, where it is stored and used -in all future requests. - -### Access Token/API Authentication - -Using access tokens requires that you either use/extend `CodeIgniter\Shield\Models\UserModel` or -use the `CodeIgniter\Shield\Authentication\Traits\HasAccessTokens` on your own user model. This trait -provides all of the custom methods needed to implement access tokens in your application. The necessary -database table, `auth_identities`, is created in Shield's only migration class, which must be run -before first using any of the features of Shield. - -### Generating Access Tokens - -Access tokens are created through the `generateAccessToken()` method on the user. This takes a name to -give to the token as the first argument. The name is used to display it to the user so they can -differentiate between multiple tokens. - -```php -$token = $user->generateAccessToken('Work Laptop'); -``` - -This creates the token using a cryptographically secure random string. The token -is hashed (sha256) before saving it to the database. The method returns an instance of -`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The only time a plain text -version of the token is available is in the `AccessToken` returned immediately after creation. - -**The plain text version should be displayed to the user immediately so they can copy it for -their use.** If a user loses it, they cannot see the raw version anymore, but they can generate -a new token to use. - -```php -$token = $user->generateAccessToken('Work Laptop'); - -// Only available immediately after creation. -echo $token->raw_token; -``` - -### Revoking Access Tokens - -Access tokens can be revoked through the `revokeAccessToken()` method. This takes the plain-text -access token as the only argument. Revoking simply deletes the record from the database. - -```php -$user->revokeAccessToken($token); -``` - -Typically, the plain text token is retrieved from the request's headers as part of the authentication -process. If you need to revoke the token for another user as an admin, and don't have access to the -token, you would need to get the user's access tokens and delete them manually. - -You can revoke all access tokens with the `revokeAllAccessTokens()` method. - -```php -$user->revokeAllAccessTokens(); -``` - -### Retrieving Access Tokens - -The following methods are available to help you retrieve a user's access tokens: - -```php -// Retrieve a single token by plain text token -$token = $user->getAccessToken($rawToken); - -// Retrieve a single token by it's database ID -$token = $user->getAccessTokenById($id); - -// Retrieve all access tokens as an array of AccessToken instances. -$tokens = $user->accessTokens(); -``` - -### Access Token Lifetime - -Tokens will expire after a specified amount of time has passed since they have been used. -By default, this is set to 1 year. You can change this value by setting the `accessTokenLifetime` -value in the `Auth` config file. This is in seconds so that you can use the -[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -that CodeIgniter provides. - -```php -public $unusedTokenLifetime = YEAR; -``` - -### Access Token Scopes - -Each token can be given one or more scopes they can be used within. These can be thought of as -permissions the token grants to the user. Scopes are provided when the token is generated and -cannot be modified afterword. - -```php -$token = $user->gererateAccessToken('Work Laptop', ['posts.manage', 'forums.manage']); -``` - -By default a user is granted a wildcard scope which provides access to all scopes. This is the -same as: - -```php -$token = $user->gererateAccessToken('Work Laptop', ['*']); -``` - -During authentication, the token the user used is stored on the user. Once authenticated, you -can use the `tokenCan()` and `tokenCant()` methods on the user to determine if they have access -to the specified scope. - -```php -if ($user->tokenCan('posts.manage')) { - // do something.... -} - -if ($user->tokenCant('forums.manage')) { - // do something.... -} -``` diff --git a/docs/customization.md b/docs/customization.md deleted file mode 100644 index 6e21b7564..000000000 --- a/docs/customization.md +++ /dev/null @@ -1,326 +0,0 @@ -# Customizing Shield - -- [Customizing Shield](#customizing-shield) - - [Custom Table Names](#custom-table-names) - - [Route Configuration](#route-configuration) - - [Custom Redirect URLs](#custom-redirect-urls) - - [Customize Login Redirect](#customize-login-redirect) - - [Customize Register Redirect](#customize-register-redirect) - - [Customize Logout Redirect](#customize-logout-redirect) - - [Extending the Controllers](#extending-the-controllers) - - [Integrating Custom View Libraries](#integrating-custom-view-libraries) - - [Custom Validation Rules](#custom-validation-rules) - - [Registration](#registration) - - [Login](#login) - - [Custom User Provider](#custom-user-provider) - - [Custom Login Identifier](#custom-login-identifier) - -## Custom Table Names - -If you want to change the default table names, you can change the table names -in **app/Config/Auth.php**. - -```php -public array $tables = [ - 'users' => 'users', - 'identities' => 'auth_identities', - 'logins' => 'auth_logins', - 'token_logins' => 'auth_token_logins', - 'remember_tokens' => 'auth_remember_tokens', - 'groups_users' => 'auth_groups_users', - 'permissions_users' => 'auth_permissions_users', -]; -``` - -Set the table names that you want in the array values. - -> **Note** You must change the table names before running database migrations. - -## Route Configuration - -If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: - -```php -service('auth')->routes($routes, ['except' => ['login', 'register']]); -``` - -Then add the routes to your customized controllers: - -```php -$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); -$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); -``` - -## Custom Redirect URLs - -### Customize Login Redirect - -You can customize where a user is redirected to on login with the `loginRedirect()` method of the **app/Config/Auth.php** config file. This is handy if you want to redirect based on user group or other criteria. - -```php -public function loginRedirect(): string -{ - $url = auth()->user()->inGroup('admin') - ? '/admin' - : setting('Auth.redirects')['login']; - - return $this->getUrl($url); -} -``` - -Oftentimes, you will want to have different redirects for different user groups. A simple example -might be that you want admins redirected to `/admin` while all other groups redirect to `/`. -The **app/Config/Auth.php** config file also includes methods that you can add additional logic to in order to -achieve this: - -```php -public function loginRedirect(): string -{ - if (auth()->user()->can('admin.access')) { - return '/admin'; - } - - $url = setting('Auth.redirects')['login']; - - return $this->getUrl($url); -} -``` - -### Customize Register Redirect - -You can customize where a user is redirected to after registration in the `registerRedirect()` method of the **app/Config/Auth.php** config file. - -```php -public function registerRedirect(): string -{ - $url = setting('Auth.redirects')['register']; - - return $this->getUrl($url); -} -``` - -### Customize Logout Redirect - -The logout redirect can also be overridden by the `logoutRedirect()` method of the **app/Config/Auth.php** config file. This will not be used as often as login and register, but you might find the need. For example, if you programatically logged a user out you might want to take them to a page that specifies why they were logged out. Otherwise, you might take them to the home page or even the login page. - -```php -public function logoutRedirect(): string -{ - $url = setting('Auth.redirects')['logout']; - - return $this->getUrl($url); -} -``` - -## Extending the Controllers - -Shield has the following controllers that can be extended to handle -various parts of the authentication process: - -- **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification. -- **LoginController** handles the login process. -- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules. -- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to - override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used. - -It is not recommended to copy the entire controller into **app/Controllers** and change its namespace. Instead, you should create a new controller that extends -the existing controller and then only override the methods needed. This allows the other methods to stay up to date with any security -updates that might happen in the controllers. - -```php -themedView($view, $data, $options); - } -} -``` - -## Custom Validation Rules - -### Registration - -Shield has the following rules for registration: - -```php -[ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', - 'is_unique[users.username]', - ], - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email', - 'is_unique[auth_identities.secret]', - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|strong_password', - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', - ], -]; -``` - -> **Note** If you customize the table names, the table names -> (`users` and `auth_identities`) in the above rules will be automatically -> changed. The rules are implemented in -> `RegisterController::getValidationRules()`. - -If you need a different set of rules for registration, you can specify them in your `Validation` configuration (**app/Config/Validation.php**) like: - -```php - //-------------------------------------------------------------------- - // Rules For Registration - //-------------------------------------------------------------------- - public $registration = [ - 'username' => [ - 'label' => 'Auth.username', - 'rules' => [ - 'required', - 'max_length[30]', - 'min_length[3]', - 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', - 'is_unique[users.username]', - ], - ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => [ - 'required', - 'max_length[254]', - 'valid_email', - 'is_unique[auth_identities.secret]', - ], - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required|strong_password', - ], - 'password_confirm' => [ - 'label' => 'Auth.passwordConfirm', - 'rules' => 'required|matches[password]', - ], - ]; -``` - -> **Note** If you customize the table names, set the correct table names in the -> rules. - -### Login - -Similar to the process for validation rules in the **Registration** section, you can add rules for the login form to **app/Config/Validation.php** and change the rules. - -```php - //-------------------------------------------------------------------- - // Rules For Login - //-------------------------------------------------------------------- - public $login = [ - // 'username' => [ - // 'label' => 'Auth.username', - // 'rules' => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]', - // ], - 'email' => [ - 'label' => 'Auth.email', - 'rules' => 'required|max_length[254]|valid_email', - ], - 'password' => [ - 'label' => 'Auth.password', - 'rules' => 'required', - ], - ]; -``` - -## Custom User Provider - -If you want to customize user attributes, you need to create your own -[User Provider](./concepts.md#user-providers) class. -The only requirement is that your new class MUST extend the provided `CodeIgniter\Shield\Models\UserModel`. - -Shield has a CLI command to quickly create a custom `UserModel` class by running the following -command in the terminal: - -```console -php spark shield:model UserModel -``` - -The class name is optional. If none is provided, the generated class name would be `UserModel`. - -After creating the class, set the `$userProvider` property in **app/Config/Auth.php** as follows: - -```php -public string $userProvider = \App\Models\UserModel::class; -``` - -## Custom Login Identifier - -If your application has a need to use something other than `email` or `username`, you may specify any valid column within the `users` table that you may have added. This allows you to easily use phone numbers, employee or school IDs, etc as the user identifier. You must implement the following steps to set this up: - -This only works with the Session authenticator. - -1. Create a [migration](http://codeigniter.com/user_guide/dbmgmt/migration.html) that adds a new column to the `users` table. -2. Edit `app/Config/Auth.php` so that the new column you just created is within the `$validFields` array. - - ```php - public array $validFields = [ - 'employee_id' - ]; - ``` - - If you have multiple login forms on your site that use different credentials, you must have all of the valid identifying fields in the array. - - ```php - public array $validFields = [ - 'email', - 'employee_id' - ]; - ``` - > **Warning** - > It is very important for security that if you add a new column for identifier you must write a new **Validation Rules** and then set it using the [custom-validation-rules](https://github.com/codeigniter4/shield/blob/develop/docs/customization.md#custom-validation-rules) description. - -3. Edit the login form to change the name of the default `email` input to the new field name. - - ```php - -
= lang('Auth.emailActivateBody') ?>
-