diff --git a/.gitattributes b/.gitattributes index b50d952..4b62b65 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,11 +2,12 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". +/.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore /phpunit.xml.dist export-ignore -/.scrutinizer.yml export-ignore +/phpunit.xml export-ignore /tests export-ignore /.editorconfig export-ignore -/.assets export-ignore +/.styleci.yml export-ignore +/composer.lock export-ignore \ No newline at end of file diff --git a/.assets/buymeacoffee.png b/.github/assets/buymeacoffee.png similarity index 100% rename from .assets/buymeacoffee.png rename to .github/assets/buymeacoffee.png diff --git a/.assets/ko-fi.png b/.github/assets/ko-fi.png similarity index 100% rename from .assets/ko-fi.png rename to .github/assets/ko-fi.png diff --git a/.assets/patreon.png b/.github/assets/patreon.png similarity index 100% rename from .assets/patreon.png rename to .github/assets/patreon.png diff --git a/.assets/paypal.png b/.github/assets/paypal.png similarity index 100% rename from .assets/paypal.png rename to .github/assets/paypal.png diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 420454f..6d6808d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,50 +1,127 @@ -name: Tests +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + +name: "Tests" on: push: + branches: + - "feat/improve-ci" # TODO: Remove once the PR is ready to be merged. pull_request: jobs: - test: - - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - php: [ 8.0, 8.1 ] - laravel: [ 9.* ] - dependency-version: [ prefer-stable, prefer-lowest ] - include: - - laravel: 9.* - testbench: 7.* - name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }} + byte_level: + name: "0️⃣ Byte-level" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + - name: "Check file permissions" + run: | + test "$(find . -type f -not -path './.git/*' -executable)" == "" + - name: "Find non-printable ASCII characters" + run: | + ! LC_ALL=C.UTF-8 find ./src -type f -name "*.php" -print0 | xargs -0 -- grep -PHn "[^ -~]" + syntax_errors: + name: "1️⃣ Syntax errors" + runs-on: "ubuntu-latest" steps: - - name: Checkout - uses: actions/checkout@v2 + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + tools: "parallel-lint" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: "Check source code for syntax errors" + run: "composer exec -- parallel-lint src/" + + unit_tests: + name: "2️⃣ Unit and Feature tests" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + strategy: + matrix: + php-version: + - "8.1" + laravel-constrain: + - "9.*" + dependencies: + - "lowest" + - "highest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" with: - php-version: ${{ matrix.php }} + php-version: "${{ matrix.php-version }}" extensions: mbstring, intl coverage: xdebug - - name: Cache dependencies - uses: actions/cache@v2 + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" with: - path: ~/.composer/cache/files - key: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - restore-keys: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer- + dependency-versions: "${{ matrix.dependencies }}" - - name: Install dependencies - run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-progress --no-update - composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress - - name: Run Tests - run: composer run-script test + - name: "Execute unit tests" + run: "composer run-script test" + + - name: "Upload coverage to Codecov" + uses: "codecov/codecov-action@v2" + + static_analysis: + name: "3️⃣ Static Analysis" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + tools: "phpstan" - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + - name: "Checkout code" + uses: "actions/checkout@v3" + - name: "Validate Composer configuration" + run: "composer validate --strict" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + + - name: "Execute static analysis" + run: "composer exec -- phpstan analyze -l 5 src/" + + exported_files: + name: "4️⃣ Exported files" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Check exported files" + run: | + EXPECTED="LICENSE.md,README.md,composer.json" + CURRENT="$(git archive HEAD | tar --list --exclude="src" --exclude="src/*" --exclude=".stubs" --exclude=".stubs/*" --exclude="lang" --exclude="lang/*" --exclude="config" --exclude="config/*" --exclude="database" --exclude="database/*" --exclude="resources" --exclude="resources/*" | paste -s -d ",")" + echo "CURRENT =${CURRENT}" + echo "EXPECTED=${EXPECTED}" + test "${CURRENT}" == "${EXPECTED}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0054f22..c612ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -build +/build +/vendor +/.idea +.php-cs-fixer.cache +.phpunit.result.cache composer.lock -docs -vendor -coverage -.idea -/.phpunit.result.cache -/phpunit.xml.dist.bak +phpunit.xml.bak \ No newline at end of file diff --git a/README.md b/README.md index 68d51c0..896c687 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This package enables TOTP authentication using 6 digits codes. No need for exter ## Keep this package free -[![](.assets/patreon.png)](https://patreon.com/packagesforlaravel)[![](.assets/ko-fi.png)](https://ko-fi.com/DarkGhostHunter)[![](.assets/buymeacoffee.png)](https://www.buymeacoffee.com/darkghosthunter)[![](.assets/paypal.png)](https://www.paypal.com/paypalme/darkghosthunter) +[![](.github/assets/patreon.png)](https://patreon.com/packagesforlaravel)[![](.github/assets/ko-fi.png)](https://ko-fi.com/DarkGhostHunter)[![](.github/assets/buymeacoffee.png)](https://www.buymeacoffee.com/darkghosthunter)[![](.github/assets/paypal.png)](https://www.paypal.com/paypalme/darkghosthunter) Your support allows me to keep this package free, up-to-date and maintainable. Alternatively, you can **[spread the word!](http://twitter.com/share?text=I%20am%20using%20this%20cool%20PHP%20package&url=https://github.com%2FLaragear%2FTwoFactor&hashtags=PHP,Laravel)** diff --git a/src/Models/Concerns/HandlesCodes.php b/src/Models/Concerns/HandlesCodes.php index 3a84226..5759dae 100644 --- a/src/Models/Concerns/HandlesCodes.php +++ b/src/Models/Concerns/HandlesCodes.php @@ -213,9 +213,10 @@ protected function codeHasBeenUsed(string $code): bool */ protected function setCodeAsUsed(string $code, DateTimeInterface|int|string $at = 'now'): void { + $timestamp = Carbon::createFromTimestamp($this->getTimestampFromPeriod($at, $this->window + 1)); + // We will safely set the cache key for the whole lifetime plus window just to be safe. - $this->cache->set($this->cacheKey($code), true, - Carbon::createFromTimestamp($this->getTimestampFromPeriod($at, $this->window + 1)) - ); + // @phpstan-ignore-next-line + $this->cache->set($this->cacheKey($code), true, $timestamp); } } diff --git a/src/Models/Concerns/HandlesRecoveryCodes.php b/src/Models/Concerns/HandlesRecoveryCodes.php index 0dda7a4..1cd59b4 100644 --- a/src/Models/Concerns/HandlesRecoveryCodes.php +++ b/src/Models/Concerns/HandlesRecoveryCodes.php @@ -13,9 +13,9 @@ trait HandlesRecoveryCodes /** * The custom generator to make recovery codes. * - * @var (callable(int, int, int): \Illuminate\Support\Collection) + * @var (callable(int, int, int): \Illuminate\Support\Collection)|null */ - protected static $generator; + protected static $generator = null; /** * Returns if there are Recovery Codes available. diff --git a/src/Models/Concerns/SerializesSharedSecret.php b/src/Models/Concerns/SerializesSharedSecret.php index 60f1b20..93ca8bc 100644 --- a/src/Models/Concerns/SerializesSharedSecret.php +++ b/src/Models/Concerns/SerializesSharedSecret.php @@ -31,7 +31,7 @@ public function toUri(): string 'secret' => $this->shared_secret, 'algorithm' => strtoupper($this->attributes['algorithm']), 'digits' => $this->attributes['digits'], - ], null, '&', PHP_QUERY_RFC3986); + ], '', '&', PHP_QUERY_RFC3986); return 'otpauth://totp/'.rawurlencode($issuer).'%3A'.$this->attributes['label']."?$query"; } diff --git a/src/Models/TwoFactorAuthentication.php b/src/Models/TwoFactorAuthentication.php index 3f87abb..779b27a 100644 --- a/src/Models/TwoFactorAuthentication.php +++ b/src/Models/TwoFactorAuthentication.php @@ -34,7 +34,7 @@ * @property null|\Illuminate\Support\Carbon|\DateTime $updated_at * @property null|\Illuminate\Support\Carbon|\DateTime $created_at * - * @method static \Database\Factories\Laragear\TwoFactor\TwoFactorAuthenticationFactory factory() + * @method static \Database\Factories\Laragear\TwoFactor\TwoFactorAuthenticationFactory factory($count = null, $state = []) */ class TwoFactorAuthentication extends Model implements TwoFactorTotp { @@ -141,9 +141,7 @@ public static function generateRandomSecret(): string } /** - * Create a new factory instance for the model. - * - * @return \Illuminate\Database\Eloquent\Factories\Factory + * @inheritDoc */ protected static function newFactory(): Factory { diff --git a/src/Rules/Totp.php b/src/Rules/Totp.php index 784c389..51ebfb1 100644 --- a/src/Rules/Totp.php +++ b/src/Rules/Totp.php @@ -15,7 +15,7 @@ class Totp /** * Create a new "totp code" rule instance. * - * @param \Illuminate\Contracts\Auth\Authenticatable|\Laragear\TwoFactor\Contracts\TwoFactorAuthenticatable|null $user + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user */ public function __construct(protected ?Authenticatable $user = null) { diff --git a/src/TwoFactorLoginHelper.php b/src/TwoFactorLoginHelper.php index f93e6cd..c1d0c3a 100644 --- a/src/TwoFactorLoginHelper.php +++ b/src/TwoFactorLoginHelper.php @@ -9,7 +9,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use InvalidArgumentException; -use JetBrains\PhpStorm\ArrayShape; use Laragear\TwoFactor\Exceptions\InvalidCodeException; use function response; use function view; @@ -144,6 +143,10 @@ public function attempt(array $credentials = [], $remember = false): bool $this->throwConfirmView($this->input, $this->request->has($this->input) ? $e->errors() : []); } + + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd } /** @@ -165,11 +168,10 @@ protected function getSessionGuard(): SessionGuard /** * Retrieve the flashed credentials in the session, and merges with the new on top. * - * @param array $credentials + * @param array{credentials:array, remember:bool} $credentials * @param mixed $remember * @return array */ - #[ArrayShape(['credentials' => 'array', 'remember' => 'bool'])] protected function getFlashedData(array $credentials, mixed $remember): array { $original = $this->session->get("$this->sessionKey.credentials", []); @@ -195,6 +197,7 @@ protected function flashData(array $credentials, bool $remember): void $credentials[$key] = Crypt::encryptString($value); } + // @phpstan-ignore-next-line $this->session->flash($this->sessionKey, ['credentials' => $credentials, 'remember' => $remember]); } @@ -207,6 +210,7 @@ protected function flashData(array $credentials, bool $remember): void */ protected function throwConfirmView(string $input, array $errors): void { + // @phpstan-ignore-next-line response(view($this->view, ['input' => $input])->withErrors($errors))->throwResponse(); } } diff --git a/src/TwoFactorServiceProvider.php b/src/TwoFactorServiceProvider.php index 5b653bc..2f513a9 100644 --- a/src/TwoFactorServiceProvider.php +++ b/src/TwoFactorServiceProvider.php @@ -73,7 +73,9 @@ protected function publishFiles(): void $this->publishesMigrations(static::DB); $this->publishes([static::CONFIG => $this->app->configPath('two-factor.php')], 'config'); + // @phpstan-ignore-next-line $this->publishes([static::VIEWS => $this->app->viewPath('vendor/two-factor')], 'views'); + // @phpstan-ignore-next-line $this->publishes([static::LANG => $this->app->langPath('vendor/two-factor')], 'translations'); } }