From d1cf0353048f4ffaaba6d915a665f177a82d2dc2 Mon Sep 17 00:00:00 2001 From: Alon Kolyakov Date: Tue, 17 Mar 2026 23:42:48 +0200 Subject: [PATCH] Add Name parameter to UserPasskeyInfo constructor UserPasskeyInfo exposes a Name property but the constructor doesn't accept it, forcing callers to use an object initializer to set it. Every other property is covered by the constructor, so the omission feels unintentional. This adds a new constructor overload that accepts an optional 'string? name' parameter. The existing constructor is preserved (it delegates to the new one with name: null) so there's no binary or source breaking change. Changes: - New constructor overload on UserPasskeyInfo with trailing 'name' parameter - Existing constructor chains into the new one - PublicAPI.Unshipped.txt updated - Unit tests for both constructors Fixes #65774 --- .../src/PublicAPI.Unshipped.txt | 1 + .../Extensions.Core/src/UserPasskeyInfo.cs | 31 +++++++ .../test/Identity.Test/UserPasskeyInfoTest.cs | 88 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 src/Identity/test/Identity.Test/UserPasskeyInfoTest.cs diff --git a/src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt b/src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt index 8e7ea89bc368..3884283ff48e 100644 --- a/src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt +++ b/src/Identity/Extensions.Core/src/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Microsoft.AspNetCore.Identity.UserPasskeyInfo.Aaguid.get -> byte[]? Microsoft.AspNetCore.Identity.UserPasskeyInfo.Aaguid.set -> void +Microsoft.AspNetCore.Identity.UserPasskeyInfo.UserPasskeyInfo(byte[]! credentialId, byte[]! publicKey, System.DateTimeOffset createdAt, uint signCount, string![]? transports, bool isUserVerified, bool isBackupEligible, bool isBackedUp, byte[]! attestationObject, byte[]! clientDataJson, string? name) -> void diff --git a/src/Identity/Extensions.Core/src/UserPasskeyInfo.cs b/src/Identity/Extensions.Core/src/UserPasskeyInfo.cs index a8454f2dd04d..bd93c54f9043 100644 --- a/src/Identity/Extensions.Core/src/UserPasskeyInfo.cs +++ b/src/Identity/Extensions.Core/src/UserPasskeyInfo.cs @@ -34,6 +34,36 @@ public UserPasskeyInfo( bool isBackedUp, byte[] attestationObject, byte[] clientDataJson) + : this(credentialId, publicKey, createdAt, signCount, transports, isUserVerified, isBackupEligible, isBackedUp, attestationObject, clientDataJson, name: null) + { + } + + /// + /// Initializes a new instance of with a friendly name. + /// + /// The credential ID for the passkey. + /// The public key for the passkey. + /// The time when the passkey was created. + /// The signature counter for the passkey. + /// The transports supported by this passkey. + /// Indicates if the passkey has a verified user. + /// Indicates if the passkey is eligible for backup. + /// Indicates if the passkey is currently backed up. + /// The passkey's attestation object. + /// The passkey's client data JSON. + /// An optional friendly name for the passkey. + public UserPasskeyInfo( + byte[] credentialId, + byte[] publicKey, + DateTimeOffset createdAt, + uint signCount, + string[]? transports, + bool isUserVerified, + bool isBackupEligible, + bool isBackedUp, + byte[] attestationObject, + byte[] clientDataJson, + string? name) { CredentialId = credentialId; PublicKey = publicKey; @@ -45,6 +75,7 @@ public UserPasskeyInfo( IsBackedUp = isBackedUp; AttestationObject = attestationObject; ClientDataJson = clientDataJson; + Name = name; } /// diff --git a/src/Identity/test/Identity.Test/UserPasskeyInfoTest.cs b/src/Identity/test/Identity.Test/UserPasskeyInfoTest.cs new file mode 100644 index 000000000000..4d268671c23c --- /dev/null +++ b/src/Identity/test/Identity.Test/UserPasskeyInfoTest.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.Test; + +public class UserPasskeyInfoTest +{ + [Fact] + public void Ctor_WithName_SetsAllProperties() + { + // Arrange + var credentialId = new byte[] { 1, 2, 3 }; + var publicKey = new byte[] { 4, 5, 6 }; + var createdAt = DateTimeOffset.UtcNow; + var transports = new[] { "usb", "nfc" }; + var attestationObject = new byte[] { 7, 8, 9 }; + var clientDataJson = new byte[] { 10, 11 }; + var name = "My Security Key"; + + // Act + var passkey = new UserPasskeyInfo( + credentialId, + publicKey, + createdAt, + signCount: 42, + transports, + isUserVerified: true, + isBackupEligible: true, + isBackedUp: false, + attestationObject, + clientDataJson, + name); + + // Assert + Assert.Equal(credentialId, passkey.CredentialId); + Assert.Equal(publicKey, passkey.PublicKey); + Assert.Equal(createdAt, passkey.CreatedAt); + Assert.Equal(42u, passkey.SignCount); + Assert.Equal(transports, passkey.Transports); + Assert.True(passkey.IsUserVerified); + Assert.True(passkey.IsBackupEligible); + Assert.False(passkey.IsBackedUp); + Assert.Equal(attestationObject, passkey.AttestationObject); + Assert.Equal(clientDataJson, passkey.ClientDataJson); + Assert.Equal("My Security Key", passkey.Name); + } + + [Fact] + public void Ctor_WithNullName_LeavesNameNull() + { + var passkey = new UserPasskeyInfo( + new byte[] { 1 }, + new byte[] { 2 }, + DateTimeOffset.UtcNow, + signCount: 0, + transports: null, + isUserVerified: false, + isBackupEligible: false, + isBackedUp: false, + new byte[] { 3 }, + new byte[] { 4 }, + name: null); + + Assert.Null(passkey.Name); + } + + [Fact] + public void Ctor_WithoutName_LeavesNameNull() + { + // The original constructor should still work and leave Name unset. + var passkey = new UserPasskeyInfo( + new byte[] { 1 }, + new byte[] { 2 }, + DateTimeOffset.UtcNow, + signCount: 0, + transports: null, + isUserVerified: false, + isBackupEligible: false, + isBackedUp: false, + new byte[] { 3 }, + new byte[] { 4 }); + + Assert.Null(passkey.Name); + } +}