diff --git a/.gitattributes b/.gitattributes index f358bea..4b62b65 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,10 +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 /phpunit.xml.dist export-ignore +/phpunit.xml export-ignore /tests export-ignore /.editorconfig export-ignore -/.github 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..868d7b5 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,50 +1,125 @@ -name: Tests +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + +name: "Tests" on: push: 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 - uses: actions/checkout@v2 + - name: "Checkout code" + uses: "actions/checkout@v3" - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - 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: "Set up PHP" + uses: "shivammathur/setup-php@v2" with: - php-version: ${{ matrix.php }} + 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: "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-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 + - 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: "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: | - 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: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - + EXPECTED="LICENSE.md,README.md,composer.json" + CURRENT="$(git archive HEAD | tar --list --exclude="src" --exclude="src/*" --exclude=".stubs" --exclude=".stubs/*" --exclude="stubs" --exclude="stubs/*" --exclude="lang" --exclude="lang/*" --exclude="config" --exclude="config/*" --exclude="database" --exclude="database/*" --exclude="resources" --exclude="resources/*" --exclude="routes" --exclude="routes/*" | paste -s -d ",")" + echo "CURRENT =${CURRENT}" + echo "EXPECTED=${EXPECTED}" + test "${CURRENT}" == "${EXPECTED}" diff --git a/.gitignore b/.gitignore index f6ff282..c612ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -.idea -build -composer.lock -docs -vendor -coverage +/build +/vendor +/.idea +.php-cs-fixer.cache .phpunit.result.cache -.vscode +composer.lock +phpunit.xml.bak \ No newline at end of file diff --git a/README.md b/README.md index ecc862a..f2eb251 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ public function login(AssertedRequest $request) ## Keep this package free -[![Patreon](.assets/patreon.png)](https://patreon.com/packagesforlaravel)[![Ko-fi](.assets/ko-fi.png)](https://ko-fi.com/DarkGhostHunter)[![Buymeacoffee](.assets/buymeacoffee.png)](https://www.buymeacoffee.com/darkghosthunter)[![PayPal](.assets/paypal.png)](https://www.paypal.com/paypalme/darkghosthunter) +[![Patreon](.github/assets/patreon.png)](https://patreon.com/packagesforlaravel)[![Ko-fi](.github/assets/ko-fi.png)](https://ko-fi.com/DarkGhostHunter)[![Buymeacoffee](.github/assets/buymeacoffee.png)](https://www.buymeacoffee.com/darkghosthunter)[![PayPal](.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%2FWebAuthn&hashtags=PHP,Laravel)** diff --git a/src/Assertion/Creator/Pipes/CreateAssertionChallenge.php b/src/Assertion/Creator/Pipes/CreateAssertionChallenge.php index 0a4860b..c9d78b1 100644 --- a/src/Assertion/Creator/Pipes/CreateAssertionChallenge.php +++ b/src/Assertion/Creator/Pipes/CreateAssertionChallenge.php @@ -33,6 +33,7 @@ class CreateAssertionChallenge $options = []; if ($assertion->acceptedCredentials?->isNotEmpty()) { + // @phpstan-ignore-next-line $options['credentials'] = $assertion->acceptedCredentials->map->getKey()->toArray(); } diff --git a/src/Assertion/Creator/Pipes/MayRetrieveCredentialsIdForUser.php b/src/Assertion/Creator/Pipes/MayRetrieveCredentialsIdForUser.php index 424cc0e..d5527e9 100644 --- a/src/Assertion/Creator/Pipes/MayRetrieveCredentialsIdForUser.php +++ b/src/Assertion/Creator/Pipes/MayRetrieveCredentialsIdForUser.php @@ -36,10 +36,11 @@ class MayRetrieveCredentialsIdForUser * Adapt all credentials into an `allowCredentials` digestible array. * * @param \Illuminate\Database\Eloquent\Collection $credentials - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection}> */ protected function parseCredentials(EloquentCollection $credentials): Collection { + // @phpstan-ignore-next-line return $credentials->map(static function (WebAuthnCredential $credential): array { return array_filter([ 'id' => $credential->getKey(), diff --git a/src/Assertion/Validator/Pipes/CheckCredentialIsForUser.php b/src/Assertion/Validator/Pipes/CheckCredentialIsForUser.php index 44561b5..cd56e32 100644 --- a/src/Assertion/Validator/Pipes/CheckCredentialIsForUser.php +++ b/src/Assertion/Validator/Pipes/CheckCredentialIsForUser.php @@ -56,6 +56,7 @@ class CheckCredentialIsForUser */ protected function validateUser(AssertionValidation $validation): void { + // @phpstan-ignore-next-line if ($validation->credential->authenticatable()->isNot($validation->user)) { throw AssertionException::make('User is not owner of the stored credential.'); } diff --git a/src/Attestation/AuthenticatorData.php b/src/Attestation/AuthenticatorData.php index dcf7ca6..ccea326 100644 --- a/src/Attestation/AuthenticatorData.php +++ b/src/Attestation/AuthenticatorData.php @@ -18,7 +18,7 @@ use function unpack; /** * MIT License * - * Copyright © 2021 Lukas Buchs + * Copyright (c) 2021 Lukas Buchs * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -76,10 +76,10 @@ class AuthenticatorData * @param string $relyingPartyIdHash * @param object $flags * @param int $counter - * @param object{aaguid: int|bool, credentialId: string, credentialPublicKey: string} $attestedCredentialData + * @param object $attestedCredentialData * @param array $extensionData */ - public function __construct( + final public function __construct( public string $relyingPartyIdHash, public object $flags, public int $counter, @@ -366,13 +366,21 @@ class AuthenticatorData protected static function readFlags(string $binFlag): object { $flags = (object) [ + // @phpstan-ignore-next-line 'bit_0' => (bool) ($binFlag & 1), + // @phpstan-ignore-next-line 'bit_1' => (bool) ($binFlag & 2), + // @phpstan-ignore-next-line 'bit_2' => (bool) ($binFlag & 4), + // @phpstan-ignore-next-line 'bit_3' => (bool) ($binFlag & 8), + // @phpstan-ignore-next-line 'bit_4' => (bool) ($binFlag & 16), + // @phpstan-ignore-next-line 'bit_5' => (bool) ($binFlag & 32), + // @phpstan-ignore-next-line 'bit_6' => (bool) ($binFlag & 64), + // @phpstan-ignore-next-line 'bit_7' => (bool) ($binFlag & 128), 'userPresent' => false, 'userVerified' => false, diff --git a/src/Attestation/Creator/Pipes/AddUserDescriptor.php b/src/Attestation/Creator/Pipes/AddUserDescriptor.php index e3f0852..d1ba4d3 100644 --- a/src/Attestation/Creator/Pipes/AddUserDescriptor.php +++ b/src/Attestation/Creator/Pipes/AddUserDescriptor.php @@ -23,6 +23,7 @@ class AddUserDescriptor $config = $attestable->user->webAuthnData(); // Create a new User UUID if it doesn't existe already in the credentials. + // @phpstan-ignore-next-line $config['id'] = $attestable->user->webAuthnCredentials()->value('user_id') ?: Str::uuid()->getHex()->toString(); diff --git a/src/Attestation/Creator/Pipes/MayPreventDuplicateCredentials.php b/src/Attestation/Creator/Pipes/MayPreventDuplicateCredentials.php index 71ee893..4f90068 100644 --- a/src/Attestation/Creator/Pipes/MayPreventDuplicateCredentials.php +++ b/src/Attestation/Creator/Pipes/MayPreventDuplicateCredentials.php @@ -39,6 +39,7 @@ class MayPreventDuplicateCredentials return $user ->webAuthnCredentials() ->get(['id', 'transports']) + // @phpstan-ignore-next-line ->map(static function (WebAuthnCredential $credential): array { return array_filter([ 'id'=> $credential->getKey(), diff --git a/src/Attestation/Formats/Format.php b/src/Attestation/Formats/Format.php index 8379398..2c64fd7 100644 --- a/src/Attestation/Formats/Format.php +++ b/src/Attestation/Formats/Format.php @@ -8,7 +8,7 @@ use Laragear\WebAuthn\Attestation\AuthenticatorData; /** * MIT License * - * Copyright © 2021 Lukas Buchs + * Copyright (c) 2021 Lukas Buchs * * 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/src/Auth/WebAuthnUserProvider.php b/src/Auth/WebAuthnUserProvider.php index dd4e7c0..9a2b68d 100644 --- a/src/Auth/WebAuthnUserProvider.php +++ b/src/Auth/WebAuthnUserProvider.php @@ -50,6 +50,7 @@ class WebAuthnUserProvider extends EloquentUserProvider /** @noinspection PhpIncompatibleReturnTypeInspection */ return $this->newModelQuery() ->whereHas('webAuthnCredentials', static function (Builder $query) use ($credentials): void { + // @phpstan-ignore-next-line $query->whereKey($credentials['id'])->whereEnabled(); }) ->first(); diff --git a/src/ByteBuffer.php b/src/ByteBuffer.php index abd948e..90d8109 100644 --- a/src/ByteBuffer.php +++ b/src/ByteBuffer.php @@ -49,8 +49,8 @@ use function unpack; * --- * MIT License * - * Copyright © 2021 Lukas Buchs - * Copyright © 2018 Thomas Bleeker (CBOR & ByteBuffer part) + * Copyright (c) 2021 Lukas Buchs + * Copyright (c) 2018 Thomas Bleeker (CBOR & ByteBuffer part) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -86,7 +86,7 @@ class ByteBuffer implements JsonSerializable, Jsonable, Stringable * @param string $binaryData * @param int $dataLength */ - public function __construct(protected string $binaryData, protected int $dataLength = 0) + final public function __construct(protected string $binaryData, protected int $dataLength = 0) { $this->dataLength = strlen($binaryData); } @@ -194,7 +194,7 @@ class ByteBuffer implements JsonSerializable, Jsonable, Stringable * Returns the value of a single unsigned 16-bit integer. * * @param int $offset - * @return mixed + * @return int */ public function getUint16Val(int $offset = 0): int { @@ -209,7 +209,7 @@ class ByteBuffer implements JsonSerializable, Jsonable, Stringable * Returns the value of a single unsigned 32-bit integer. * * @param int $offset - * @return mixed + * @return int */ public function getUint32Val(int $offset = 0): int { @@ -404,7 +404,7 @@ class ByteBuffer implements JsonSerializable, Jsonable, Stringable throw new InvalidArgumentException('ByteBuffer: Invalid base64 url string'); } - return new ByteBuffer($bin); + return new static($bin); } /** @@ -415,11 +415,14 @@ class ByteBuffer implements JsonSerializable, Jsonable, Stringable */ public static function fromBase64(string $base64): static { - if (false === $bin = base64_decode($base64)) { + /** @var string|false $bin */ + $bin = base64_decode($base64); + + if (false === $bin) { throw new InvalidArgumentException('ByteBuffer: Invalid base64 string'); } - return new ByteBuffer($bin); + return new static($bin); } /** diff --git a/src/CborDecoder.php b/src/CborDecoder.php index 972a8f4..e570259 100644 --- a/src/CborDecoder.php +++ b/src/CborDecoder.php @@ -34,8 +34,8 @@ use function sprintf; * --- * MIT License * - * Copyright © 2021 Lukas Buchs - * Copyright © 2018 Thomas Bleeker (CBOR & ByteBuffer part) + * Copyright (c) 2021 Lukas Buchs + * Copyright (c) 2018 Thomas Bleeker (CBOR & ByteBuffer part) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -248,7 +248,7 @@ class CborDecoder * @param \Laragear\WebAuthn\ByteBuffer $buf * @param $offset * @return \Laragear\WebAuthn\ByteBuffer|array|bool|float|int|string|null - * @throws \Laragear\WebAuthn\Exceptions\DataException + * @throws \Laragear\WebAuthn\Exceptions\DataException|\InvalidArgumentException */ protected static function parseItemData( int $type, diff --git a/src/Challenge.php b/src/Challenge.php index 92e899c..ebdfd49 100644 --- a/src/Challenge.php +++ b/src/Challenge.php @@ -17,7 +17,7 @@ class Challenge * @param bool $verify * @param array $properties */ - public function __construct( + final public function __construct( public ByteBuffer $data, public int $timeout, public bool $verify = true, diff --git a/src/Contracts/WebAuthnAuthenticatable.php b/src/Contracts/WebAuthnAuthenticatable.php index ec61d43..d76edcc 100644 --- a/src/Contracts/WebAuthnAuthenticatable.php +++ b/src/Contracts/WebAuthnAuthenticatable.php @@ -41,7 +41,8 @@ interface WebAuthnAuthenticatable /** * Returns a queryable relationship for its WebAuthn Credentials. * - * @return \Illuminate\Database\Eloquent\Relations\MorphMany&\Laragear\WebAuthn\Models\WebAuthnCredential + * @phpstan-ignore-next-line + * @return \Illuminate\Database\Eloquent\Relations\MorphMany|\Laragear\WebAuthn\Models\WebAuthnCredential */ public function webAuthnCredentials(): MorphMany; } diff --git a/src/Exceptions/AssertionException.php b/src/Exceptions/AssertionException.php index afbb5f2..8536b12 100644 --- a/src/Exceptions/AssertionException.php +++ b/src/Exceptions/AssertionException.php @@ -11,7 +11,7 @@ class AssertionException extends ValidationException implements WebAuthnExceptio * Create a new Assertion Exception with the error message. * * @param string $message - * @return \Laragear\WebAuthn\Exceptions\AssertionException + * @return static */ public static function make(string $message): static { diff --git a/src/Exceptions/AttestationException.php b/src/Exceptions/AttestationException.php index 3853532..14e7abc 100644 --- a/src/Exceptions/AttestationException.php +++ b/src/Exceptions/AttestationException.php @@ -11,7 +11,7 @@ class AttestationException extends ValidationException implements WebAuthnExcept * Create a new Attestation Exception with the error message. * * @param string $message - * @return \Laragear\WebAuthn\Exceptions\AttestationException + * @return static */ public static function make(string $message): static { diff --git a/src/Http/Requests/AssertedRequest.php b/src/Http/Requests/AssertedRequest.php index 59c88ba..452521f 100644 --- a/src/Http/Requests/AssertedRequest.php +++ b/src/Http/Requests/AssertedRequest.php @@ -48,15 +48,18 @@ class AssertedRequest extends FormRequest * Logs in the user for this assertion request. * * @param string|null $guard - * @return \Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable&\Illuminate\Contracts\Auth\Authenticatable|null + * @phpstan-ignore-next-line + * @return \Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable|\Illuminate\Contracts\Auth\Authenticatable|null */ public function login(string $guard = null, bool $remember = null, bool $destroySession = false): ?WebAuthnAuthenticatable { + /** @var \Illuminate\Contracts\Auth\StatefulGuard $auth */ $auth = Auth::guard($guard); if ($auth->attempt($this->validated(), $remember ?? $this->hasRemember())) { $this->session()->regenerate($destroySession); + // @phpstan-ignore-next-line return $auth->user(); } diff --git a/src/Http/Requests/AssertionRequest.php b/src/Http/Requests/AssertionRequest.php index 81c5256..c6c750d 100644 --- a/src/Http/Requests/AssertionRequest.php +++ b/src/Http/Requests/AssertionRequest.php @@ -133,7 +133,9 @@ class AssertionRequest extends FormRequest // retrieve by its ID, otherwise we will fall back to credentials. Once done, we // will check it uses WebAuthn if is not null, otherwise we'll fail miserably. $user = is_string($credentials) || is_int($credentials) + // @phpstan-ignore-next-line ? Auth::guard($this->guard)->getProvider()->retrieveById($credentials) + // @phpstan-ignore-next-line : Auth::guard($this->guard)->getProvider()->retrieveByCredentials($credentials); if ($user && ! $user instanceof WebAuthnAuthenticatable) { diff --git a/src/Models/WebAuthnCredential.php b/src/Models/WebAuthnCredential.php index 48c8f98..701a92d 100644 --- a/src/Models/WebAuthnCredential.php +++ b/src/Models/WebAuthnCredential.php @@ -9,7 +9,7 @@ use Laragear\WebAuthn\Events\CredentialDisabled; use Laragear\WebAuthn\Events\CredentialEnabled; /** - * @mixin \Illuminate\Database\Eloquent\Builder<\Laragear\WebAuthn\Models\WebAuthnCredential> + * @mixin \Illuminate\Database\Eloquent\Builder * * @method static \Illuminate\Database\Eloquent\Builder|static query() * @method \Illuminate\Database\Eloquent\Builder|static newQuery() @@ -22,10 +22,10 @@ use Laragear\WebAuthn\Events\CredentialEnabled; * @method \Laragear\WebAuthn\Models\WebAuthnCredential firstOr($columns = ['*'], \Closure $callback = null) * @method \Laragear\WebAuthn\Models\WebAuthnCredential firstWhere($column, $operator = null, $value = null, $boolean = 'and') * @method \Laragear\WebAuthn\Models\WebAuthnCredential updateOrCreate(array $attributes, array $values = []) - * @method static|null first($columns = ['*']) + * @method ?static first($columns = ['*']) * @method static static findOrFail($id, $columns = ['*']) * @method static static findOrNew($id, $columns = ['*']) - * @method static static|null find($id, $columns = ['*']) + * @method static ?null find($id, $columns = ['*']) * * @property-read string $id * @@ -98,7 +98,8 @@ class WebAuthnCredential extends Model protected $visible = ['id', 'origin', 'alias', 'aaguid', 'attestation_format', 'disabled_at', 'is_enabled']; /** - * @return \Illuminate\Database\Eloquent\Relations\MorphTo&\Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable + * @phpstan-ignore-next-line + * @return \Illuminate\Database\Eloquent\Relations\MorphTo|\Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable */ public function authenticatable(): MorphTo { @@ -113,6 +114,7 @@ class WebAuthnCredential extends Model */ protected function scopeWhereEnabled(Builder $query): Builder { + // @phpstan-ignore-next-line return $query->whereNull('disabled_at'); } /** @@ -123,6 +125,7 @@ class WebAuthnCredential extends Model */ protected function scopeWhereDisabled(Builder $query): Builder { + // @phpstan-ignore-next-line return $query->whereNotNull('disabled_at'); } diff --git a/src/WebAuthnServiceProvider.php b/src/WebAuthnServiceProvider.php index 4fc7984..7ca9ed4 100644 --- a/src/WebAuthnServiceProvider.php +++ b/src/WebAuthnServiceProvider.php @@ -45,6 +45,7 @@ class WebAuthnServiceProvider extends ServiceProvider if ($this->app->runningInConsole()) { $this->publishesMigrations(static::MIGRATIONS); $this->publishes([static::ROUTES => $this->app->basePath('routes/webauthn.php')], 'routes'); + // @phpstan-ignore-next-line $this->publishes([static::CONTROLLERS => $this->app->path('Http/Controllers/WebAuthn')], 'controllers'); $this->publishes([static::JS => $this->app->resourcePath('js/vendor/webauthn')], 'js'); }