diff --git a/package.json b/package.json index 8b68c280..67915b5c 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,17 @@ "check-format": "yarn format --no-write --check" }, "devDependencies": { + "@simplewebauthn/types": "^12.0.0", "@babel/core": "^7.15.8", "@babel/preset-env": "^7.15.8", "@babel/preset-react": "^7.15.8", "@babel/preset-typescript": "^7.15.8", - "@rollup/plugin-commonjs": "^26.0.0", - "@rollup/plugin-node-resolve": "^15.0.0", - "@rollup/plugin-typescript": "^11.0.0", + "@rollup/plugin-commonjs": "^28.0", + "@rollup/plugin-node-resolve": "^16.0", + "@rollup/plugin-typescript": "^11.1", "@symfony/stimulus-testing": "^2.0.1", - "@typescript-eslint/eslint-plugin": "^7.14.0", - "@typescript-eslint/parser": "^7.14.0", + "@typescript-eslint/eslint-plugin": "^8.20", + "@typescript-eslint/parser": "^8.20", "babel-jest": "^29.0", "clean-css-cli": "^5.6.2", "eslint": "^9.6.0", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6c289546..4cf321db 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2011,7 +2011,7 @@ parameters: path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php - - message: '#^Parameter \#1 \$certificates of static method Webauthn\\TrustPath\\CertificateTrustPath\:\:create\(\) expects array\, array given\.$#' + message: '#^Parameter \#1 \$certificates of static method Webauthn\\TrustPath\\CertificateTrustPath\:\:create\(\) expects array\, array\ given\.$#' identifier: argument.type count: 1 path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php diff --git a/rollup.config.js b/rollup.config.js index a3eb8043..741dbf9a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -43,16 +43,14 @@ const wildcardExternalsPlugin = (peerDependencies) => ({ * * This could probably be configured in the TypeScript plugin. */ -const moveTypescriptDeclarationsPlugin = (packagePath) => ({ - name: 'move-ts-declarations', - writeBundle: async () => { - const files = glob.sync(path.join(packagePath, 'dist', '**', 'assets', 'src', '**/*.d.ts')); +const moveTypescriptDeclarationsPlugin = () => ({ + name: 'move-to-dist', + writeBundle: () =>{ + console.log(packageRoot); + const files = glob.sync(path.join(packageRoot, 'dist', '**', 'assets', 'src', '**/*.d.ts')); files.forEach((file) => { - // a bit odd, but remove first 8 directories, which will leave - // only the relative path to the file const relativePath = file.split('/').slice(8).join('/'); - - const targetFile = path.join(packagePath, 'dist', relativePath); + const targetFile = path.join(packageRoot, 'dist', relativePath); if (!fs.existsSync(path.dirname(targetFile))) { fs.mkdirSync(path.dirname(targetFile), { recursive: true }); } @@ -83,7 +81,6 @@ module.exports = { filterRoot: packageRoot, include: ['src/**/*.ts'], compilerOptions: { - outDir: 'dist', declaration: true, emitDeclarationOnly: true, } diff --git a/src/stimulus/assets/dist/controller.js b/src/stimulus/assets/dist/controller.js index aa8c80f5..063013e6 100644 --- a/src/stimulus/assets/dist/controller.js +++ b/src/stimulus/assets/dist/controller.js @@ -9,10 +9,10 @@ class default_1 extends Controller { const options = { requestResultUrl: this.requestResultUrlValue, requestOptionsUrl: this.requestOptionsUrlValue, - requestSuccessRedirectUri: (_a = this.requestSuccessRedirectUriValue) !== null && _a !== void 0 ? _a : null, + requestSuccessRedirectUri: (_a = this.requestSuccessRedirectUriValue) !== null && _a !== undefined ? _a : null, creationResultUrl: this.creationResultUrlValue, creationOptionsUrl: this.creationOptionsUrlValue, - creationSuccessRedirectUri: (_b = this.creationSuccessRedirectUriValue) !== null && _b !== void 0 ? _b : null, + creationSuccessRedirectUri: (_b = this.creationSuccessRedirectUriValue) !== null && _b !== undefined ? _b : null, }; this._dispatchEvent('webauthn:connect', { options }); const supportAutofill = await browserSupportsWebAuthnAutofill(); @@ -39,7 +39,7 @@ class default_1 extends Controller { } async _processSignin(optionsResponseJson, useBrowserAutofill) { try { - const authenticatorResponse = await startAuthentication(optionsResponseJson, useBrowserAutofill); + const authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill }); this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse }); const assertionResponse = await this._getAssertionResponse(authenticatorResponse); if (assertionResponse !== false && this.requestSuccessRedirectUriValue) { @@ -62,7 +62,7 @@ class default_1 extends Controller { if (!optionsResponseJson) { return; } - const authenticatorResponse = await startRegistration(optionsResponseJson); + const authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson }); this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse }); const attestationResponseJSON = await this._getAttestationResponse(authenticatorResponse); if (attestationResponseJSON !== false && this.creationSuccessRedirectUriValue) { diff --git a/src/stimulus/assets/package.json b/src/stimulus/assets/package.json index 09e032bf..0d86ba0e 100644 --- a/src/stimulus/assets/package.json +++ b/src/stimulus/assets/package.json @@ -17,15 +17,15 @@ }, "importmap": { "@hotwired/stimulus": "^3.0.0", - "@simplewebauthn/browser": "^10.0.0" + "@simplewebauthn/browser": "^13.0.0" } }, "peerDependencies": { "@hotwired/stimulus": "^3.0.0", - "@simplewebauthn/browser": "^10.0.0" + "@simplewebauthn/browser": "^13.0.0" }, "devDependencies": { "@hotwired/stimulus": "^3.0.0", - "@simplewebauthn/browser": "^10.0.0" + "@simplewebauthn/browser": "^13.0.0" } } diff --git a/src/stimulus/assets/src/controller.ts b/src/stimulus/assets/src/controller.ts index a6c6fca7..19fbf80e 100644 --- a/src/stimulus/assets/src/controller.ts +++ b/src/stimulus/assets/src/controller.ts @@ -83,7 +83,7 @@ export default class extends Controller { private async _processSignin(optionsResponseJson: Object, useBrowserAutofill: boolean): Promise { try { // @ts-ignore - const authenticatorResponse = await startAuthentication(optionsResponseJson, useBrowserAutofill); + const authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill }); this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse }); const assertionResponse = await this._getAssertionResponse(authenticatorResponse); @@ -109,7 +109,7 @@ export default class extends Controller { } // @ts-ignore - const authenticatorResponse = await startRegistration(optionsResponseJson); + const authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson }); this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse }); const attestationResponseJSON = await this._getAttestationResponse(authenticatorResponse); diff --git a/src/symfony/src/Resources/config/doctrine-mapping/PublicKeyCredentialSource.orm.xml b/src/symfony/src/Resources/config/doctrine-mapping/PublicKeyCredentialSource.orm.xml index 42cb6677..90cb8e58 100644 --- a/src/symfony/src/Resources/config/doctrine-mapping/PublicKeyCredentialSource.orm.xml +++ b/src/symfony/src/Resources/config/doctrine-mapping/PublicKeyCredentialSource.orm.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd" > - + diff --git a/src/webauthn/src/Denormalizer/TrustPathDenormalizer.php b/src/webauthn/src/Denormalizer/TrustPathDenormalizer.php index fb7dd6bd..c79d5446 100644 --- a/src/webauthn/src/Denormalizer/TrustPathDenormalizer.php +++ b/src/webauthn/src/Denormalizer/TrustPathDenormalizer.php @@ -12,13 +12,14 @@ use Webauthn\TrustPath\TrustPath; use function array_key_exists; use function assert; +use function is_array; final class TrustPathDenormalizer implements DenormalizerInterface, NormalizerInterface { public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { return match (true) { - array_key_exists('x5c', $data) => CertificateTrustPath::create($data), + array_key_exists('x5c', $data) && is_array($data['x5c']) => CertificateTrustPath::create($data['x5c']), $data === [], isset($data['type']) && $data['type'] === EmptyTrustPath::class => EmptyTrustPath::create(), default => throw new InvalidTrustPathException('Unsupported trust path type'), }; diff --git a/src/webauthn/src/MetadataService/Service/FidoAllianceCompliantMetadataService.php b/src/webauthn/src/MetadataService/Service/FidoAllianceCompliantMetadataService.php index fc27ebe0..e6f36538 100644 --- a/src/webauthn/src/MetadataService/Service/FidoAllianceCompliantMetadataService.php +++ b/src/webauthn/src/MetadataService/Service/FidoAllianceCompliantMetadataService.php @@ -10,6 +10,7 @@ use Jose\Component\Signature\Algorithm\RS256; use Jose\Component\Signature\JWSVerifier; use Jose\Component\Signature\Serializer\CompactSerializer; +use LogicException; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\SerializerInterface; @@ -59,6 +60,11 @@ public function __construct( private readonly ?string $rootCertificateUri = null, ?SerializerInterface $serializer = null, ) { + if (! class_exists(CompactSerializer::class)) { + throw new LogicException( + 'The "web-token/jwt-library" package is required to use this service. Please run "composer require web-token/jwt-library".' + ); + } $this->serializer = $serializer ?? (new WebauthnSerializerFactory( AttestationStatementSupportManager::create() ))->create(); diff --git a/src/webauthn/src/PublicKeyCredentialEntity.php b/src/webauthn/src/PublicKeyCredentialEntity.php index de7e4f82..e2c54e1e 100644 --- a/src/webauthn/src/PublicKeyCredentialEntity.php +++ b/src/webauthn/src/PublicKeyCredentialEntity.php @@ -7,7 +7,7 @@ abstract class PublicKeyCredentialEntity { /** - * @@deprecated since 5.1.0 and will be removed in 6.0.0. This value is always null. + * @deprecated since 5.1.0 and will be removed in 6.0.0. This value is always null. */ public ?string $icon = null; diff --git a/tests/framework/ComposerJsonTest.php b/tests/framework/ComposerJsonTest.php index ea3e5087..5334dc2e 100644 --- a/tests/framework/ComposerJsonTest.php +++ b/tests/framework/ComposerJsonTest.php @@ -56,7 +56,7 @@ public function packageDependenciesEqualRootDependencies(): void 'Dependencies declared in root composer.json, which are not declared in any sub-package: %s', implode('', $unusedDependencies) ); - static::assertCount(0, $unusedDependencies, $message); + static::assertEmpty($unusedDependencies, $message); } #[Test] diff --git a/tests/library/Unit/SerializerTest.php b/tests/library/Unit/SerializerTest.php index f79bf02b..5d7fa4e1 100644 --- a/tests/library/Unit/SerializerTest.php +++ b/tests/library/Unit/SerializerTest.php @@ -4,6 +4,7 @@ namespace Webauthn\Tests\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; @@ -13,6 +14,9 @@ use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\Tests\AbstractTestCase; +use Webauthn\TrustPath\CertificateTrustPath; +use Webauthn\TrustPath\EmptyTrustPath; +use Webauthn\TrustPath\TrustPath; use const JSON_THROW_ON_ERROR; /** @@ -20,6 +24,55 @@ */ final class SerializerTest extends AbstractTestCase { + public static function provideTrustPath(): iterable + { + yield [ + CertificateTrustPath::create(['X509_KEY_1', 'X509_KEY_2', 'X509_KEY_3']), + '{"x5c":["X509_KEY_1","X509_KEY_2","X509_KEY_3"]}', + ]; + yield [EmptyTrustPath::create(), '[]']; + } + + #[Test] + #[DataProvider('provideTrustPath')] + public function theTrustPathCanBeSerialized(TrustPath $trustPath, string $expected): void + { + //When + $json = $this->getSerializer() + ->serialize( + $trustPath, + 'json', + [ + JsonEncode::OPTIONS => JSON_THROW_ON_ERROR, + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true, + ], + ); + + //Then + static::assertJsonStringEqualsJsonString($expected, $json); + } + + #[Test] + #[DataProvider('provideTrustPath')] + public function theTrustPathCanBeDeserialized(TrustPath $trustPath, string $expected): void + { + //When + $deserialized = $this->getSerializer() + ->deserialize( + $expected, + TrustPath::class, + 'json', + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true, + ], + ); + + //Then + static::assertEquals($trustPath, $deserialized); + } + #[Test] public function theCredentialCanBeDeserialized(): void { diff --git a/tests/symfony/functional/Attestation/AttestationTest.php b/tests/symfony/functional/Attestation/AttestationTest.php index c6a68892..6a0c1f92 100644 --- a/tests/symfony/functional/Attestation/AttestationTest.php +++ b/tests/symfony/functional/Attestation/AttestationTest.php @@ -247,7 +247,7 @@ public function aPublicKeyCredentialCreationOptionsCanBeCreatedFromProfile(): vo ); static::assertSame(32, strlen($options->challenge)); static::assertSame([], $options->excludeCredentials); - static::assertCount(0, $options->pubKeyCredParams); + static::assertEmpty($options->pubKeyCredParams); static::assertSame('none', $options->attestation); static::assertNull($options->timeout); }