From 128babcae8ddb149c2a2cb6bdb5409d0dbc94dc9 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:10:43 +0300 Subject: [PATCH 1/6] asd --- src/Service/Fido2Service.cs | 198 +++++++++--------- src/Service/Models/EFStoredCredential.cs | 109 +++++----- src/Service/Models/RegisterSession.cs | 2 +- src/Service/Models/RegisterToken.cs | 7 +- src/Service/Models/RegistrationCompleteDTO.cs | 4 +- src/Service/Models/StoredCredential.cs | 7 +- 6 files changed, 160 insertions(+), 167 deletions(-) diff --git a/src/Service/Fido2Service.cs b/src/Service/Fido2Service.cs index 6ea62733f..811030fec 100644 --- a/src/Service/Fido2Service.cs +++ b/src/Service/Fido2Service.cs @@ -16,53 +16,32 @@ namespace Passwordless.Service; -public class Fido2Service : IFido2Service +public class Fido2Service( + ITenantProvider tenantProvider, + ILogger log, + ITenantStorage storage, + ITokenService tokenService, + IEventLogger eventLogger, + IFeatureContextProvider featureContextProvider, + IMetadataService metadataService, + TimeProvider timeProvider, + IAuthenticationConfigurationService authenticationConfigurationService) + : IFido2Service { - private readonly ITenantStorage _storage; - private readonly ITenantProvider _tenantProvider; - private readonly ILogger _log; - private readonly ITokenService _tokenService; - private readonly IEventLogger _eventLogger; - private readonly IFeatureContextProvider _featureContextProvider; - private readonly IMetadataService _metadataService; - private readonly IAuthenticationConfigurationService _authenticationConfigurationService; - private readonly TimeProvider _timeProvider; - - public Fido2Service(ITenantProvider tenantProvider, - ILogger log, - ITenantStorage storage, - ITokenService tokenService, - IEventLogger eventLogger, - IFeatureContextProvider featureContextProvider, - IMetadataService metadataService, - TimeProvider timeProvider, - IAuthenticationConfigurationService authenticationConfigurationService) - { - _storage = storage; - _tenantProvider = tenantProvider; - _log = log; - _tokenService = tokenService; - _eventLogger = eventLogger; - _featureContextProvider = featureContextProvider; - _metadataService = metadataService; - _authenticationConfigurationService = authenticationConfigurationService; - _timeProvider = timeProvider; - } - public async Task CreateRegisterTokenAsync(RegisterToken tokenProps) { if (tokenProps.ExpiresAt == default) { - tokenProps.ExpiresAt = _timeProvider.GetUtcNow().UtcDateTime.AddSeconds(120); + tokenProps.ExpiresAt = timeProvider.GetUtcNow().UtcDateTime.AddSeconds(120); } - var features = await _featureContextProvider.UseContext(); + var features = await featureContextProvider.UseContext(); if (features.MaxUsers.HasValue) { - var credentials = await _storage.GetCredentialsByUserIdAsync(tokenProps.UserId); + var credentials = await storage.GetCredentialsByUserIdAsync(tokenProps.UserId); if (!credentials.Any()) { - var users = await _storage.GetUsersCount(); + var users = await storage.GetUsersCount(); if (users >= features.MaxUsers) { throw new ApiException("max_users_reached", "Maximum number of users reached", 400); @@ -75,35 +54,35 @@ public async Task CreateRegisterTokenAsync(RegisterToken tokenProps) TokenValidator.ValidateAttestation(tokenProps, features); // Check if aliases is available - if (tokenProps.Aliases != null) + if (tokenProps.Aliases is not null) { - var hashedAliases = tokenProps.Aliases.Select(alias => HashAlias(alias, _tenantProvider.Tenant)); + var hashedAliases = tokenProps.Aliases.Select(alias => HashAlias(alias, tenantProvider.Tenant)); // todo: check if alias exists and belongs to different user. - var isAvailable = await _storage.CheckIfAliasIsAvailable(hashedAliases, tokenProps.UserId); + var isAvailable = await storage.CheckIfAliasIsAvailable(hashedAliases, tokenProps.UserId); if (!isAvailable) { throw new ApiException("alias_conflict", "Alias is already in use by another userid", 409); } } - var token = await _tokenService.EncodeTokenAsync(tokenProps, "register_"); + var token = await tokenService.EncodeTokenAsync(tokenProps, "register_"); - _eventLogger.LogRegistrationTokenCreatedEvent(tokenProps.UserId); + eventLogger.LogRegistrationTokenCreatedEvent(tokenProps.UserId); return token; } public async Task> RegisterBeginAsync(FidoRegistrationBeginDTO request) { - var features = await _featureContextProvider.UseContext(); + var features = await featureContextProvider.UseContext(); - var token = await _tokenService.DecodeTokenAsync(request.Token, "register_"); - token.Validate(_timeProvider.GetUtcNow()); + var token = await tokenService.DecodeTokenAsync(request.Token, "register_"); + token.Validate(timeProvider.GetUtcNow()); var userId = token.UserId; - var fido2 = GetFido2Instance(request, _metadataService); + var fido2 = GetFido2Instance(request, metadataService); if (string.IsNullOrEmpty(userId)) { @@ -118,7 +97,7 @@ public async Task> RegisterBeginAsync(F }; // Get user existing keys by userid - var existingKeys = await _storage.GetCredentialsByUserIdAsync(userId); //DemoStorage.GetCredentialsByUser(user).Select(c => c.Descriptor).ToList(); + var existingKeys = await storage.GetCredentialsByUserIdAsync(userId); //DemoStorage.GetCredentialsByUser(user).Select(c => c.Descriptor).ToList(); var keyIds = existingKeys.Select(k => k.Descriptor).ToList(); @@ -154,20 +133,20 @@ public async Task> RegisterBeginAsync(F options.Hints = token.Hints; - var session = await _tokenService.EncodeTokenAsync( + var session = await tokenService.EncodeTokenAsync( new RegisterSession { Options = options, - Aliases = token.Aliases, + Aliases = token.Aliases ?? [], AliasHashing = token.AliasHashing }, "session_", true ); - _eventLogger.LogRegistrationBeganEvent(userId); + eventLogger.LogRegistrationBeganEvent(userId); - // return options to client + // Return options to client return new SessionResponse { Data = options, Session = session }; } catch (ArgumentException e) @@ -178,9 +157,9 @@ public async Task> RegisterBeginAsync(F public async Task RegisterCompleteAsync(RegistrationCompleteDTO request, string deviceInfo, string country) { - var session = await _tokenService.DecodeTokenAsync(request.Session, "session_", true); + var session = await tokenService.DecodeTokenAsync(request.Session, "session_", true); - var fido2 = GetFido2Instance(request, _metadataService); + var fido2 = GetFido2Instance(request, metadataService); MakeNewCredentialResult success; @@ -188,25 +167,29 @@ public async Task RegisterCompleteAsync(RegistrationCompleteDTO r { success = await fido2.MakeNewCredentialAsync(request.Response, session.Options, async (args, _) => { - bool exists = await _storage.ExistsAsync(args.CredentialId); + var exists = await storage.ExistsAsync(args.CredentialId); return !exists; }); } catch (Fido2VerificationException e) { - _log.LogWarning(e, "Unable to create new credential due to wrong configuration or wrong parameters."); + log.LogWarning(e, "Unable to create new credential due to wrong configuration or wrong parameters."); throw new ApiException("fido2_invalid_registration", e.Message, 400); } // Check whether we're allowed to register credentials for this authenticator - var features = await _featureContextProvider.UseContext(); + var features = await featureContextProvider.UseContext(); if (features.AllowAttestation) { - var configuredAuthenticators = await _storage.GetAuthenticatorsAsync(); + var configuredAuthenticators = await storage.GetAuthenticatorsAsync(); var blacklist = configuredAuthenticators.Where(x => !x.IsAllowed).ToImmutableList(); if (blacklist.Any() && blacklist.Any(x => x.AaGuid == success.Result!.AaGuid)) { - throw new ApiException("authenticator_not_allowed", "The authenticator is on the blocklist and is not allowed to be used for registration.", 400); + throw new ApiException( + "authenticator_not_allowed", + "The authenticator is on the blocklist and is not allowed to be used for registration.", + 400 + ); } var whitelist = configuredAuthenticators.Where(x => x.IsAllowed).ToImmutableList(); @@ -214,32 +197,41 @@ public async Task RegisterCompleteAsync(RegistrationCompleteDTO r { if (session.Options.Attestation == AttestationConveyancePreference.None) { - throw new ApiException("attestation_required", "Attestation 'none' was used for registration, but an allowlist was configured. Please use a supported attestation method.", 400); + throw new ApiException( + "attestation_required", + "Attestation 'none' was used for registration, but an allowlist was configured. Please use a supported attestation method.", + 400 + ); } - throw new ApiException("authenticator_not_allowed", "An allowlist was configured. The authenticator is not found on the allowlist and is not allowed to be used for registration.", 400); + + throw new ApiException( + "authenticator_not_allowed", + "An allowlist was configured. The authenticator is not found on the allowlist and is not allowed to be used for registration.", + 400 + ); } } - var userId = Encoding.UTF8.GetString(success.Result.User.Id); + var userId = Encoding.UTF8.GetString(success.Result!.User.Id); // Add aliases try { - if (session.Aliases != null && session.Aliases.Any()) + if (session.Aliases is not null && session.Aliases.Any()) { await SetAliasAsync(new AliasPayload(userId, session.Aliases, session.AliasHashing)); } } catch (Exception e) { - _log.LogError(e, "Error while saving alias during /register/complete"); + log.LogError(e, "Error while saving alias during /register/complete"); throw; } - var now = _timeProvider.GetUtcNow().UtcDateTime; + var now = timeProvider.GetUtcNow().UtcDateTime; var descriptor = new PublicKeyCredentialDescriptor(success.Result.Id); - await _storage.AddCredentialToUser(session.Options.User, new StoredCredential + await storage.AddCredentialToUser(session.Options.User, new StoredCredential { Descriptor = descriptor, PublicKey = success.Result.PublicKey, @@ -254,9 +246,10 @@ public async Task RegisterCompleteAsync(RegistrationCompleteDTO r RPID = request.RPID, Origin = request.Origin, Nickname = request.Nickname, + AuthenticatorDisplayName = request.AuthenticatorDisplayName, BackupState = success.Result.IsBackedUp, IsBackupEligible = success.Result.IsBackupEligible, - IsDiscoverable = request.Response.ClientExtensionResults?.CredProps?.Rk, + IsDiscoverable = request.Response.ClientExtensionResults?.CredProps?.Rk }); var tokenData = new VerifySignInToken @@ -265,19 +258,20 @@ public async Task RegisterCompleteAsync(RegistrationCompleteDTO r Success = true, Origin = request.Origin, RpId = session.Options.Rp.Id, - Timestamp = _timeProvider.GetUtcNow().UtcDateTime, + Timestamp = timeProvider.GetUtcNow().UtcDateTime, CredentialId = success.Result.Id, Device = deviceInfo, Country = country, Nickname = request.Nickname, - ExpiresAt = _timeProvider.GetUtcNow().UtcDateTime.AddSeconds(120), + AuthenticatorDisplayName = request.AuthenticatorDisplayName, + ExpiresAt = timeProvider.GetUtcNow().UtcDateTime.AddSeconds(120), TokenId = Guid.NewGuid(), Type = "passkey_register" }; - _eventLogger.LogRegistrationCompletedEvent(userId); + eventLogger.LogRegistrationCompletedEvent(userId); - var token = await _tokenService.EncodeTokenAsync(tokenData, "verify_"); + var token = await tokenService.EncodeTokenAsync(tokenData, "verify_"); return new TokenResponse(token); } @@ -288,8 +282,8 @@ public async Task CreateSigninTokenAsync(SigninTokenRequest request) { Success = true, UserId = request.UserId, - Timestamp = _timeProvider.GetUtcNow().UtcDateTime, - ExpiresAt = _timeProvider.GetUtcNow().UtcDateTime.Add(request.TimeToLive), + Timestamp = timeProvider.GetUtcNow().UtcDateTime, + ExpiresAt = timeProvider.GetUtcNow().UtcDateTime.Add(request.TimeToLive), TokenId = Guid.NewGuid(), Type = "generated_signin", RpId = request.RPID, @@ -297,7 +291,7 @@ public async Task CreateSigninTokenAsync(SigninTokenRequest request) Purpose = request.Purpose }; - return await _tokenService.EncodeTokenAsync(tokenProps, "verify_"); + return await tokenService.EncodeTokenAsync(tokenProps, "verify_"); } public async Task CreateMagicLinkTokenAsync(MagicLinkTokenRequest request) @@ -306,24 +300,24 @@ public async Task CreateMagicLinkTokenAsync(MagicLinkTokenRequest reques { Success = true, UserId = request.UserId, - Timestamp = _timeProvider.GetUtcNow().UtcDateTime, - ExpiresAt = _timeProvider.GetUtcNow().UtcDateTime.Add(request.TimeToLive), + Timestamp = timeProvider.GetUtcNow().UtcDateTime, + ExpiresAt = timeProvider.GetUtcNow().UtcDateTime.Add(request.TimeToLive), TokenId = Guid.NewGuid(), Type = "magic_link", RpId = request.RPID, Origin = request.Origin }; - return await _tokenService.EncodeTokenAsync(tokenProps, "verify_"); + return await tokenService.EncodeTokenAsync(tokenProps, "verify_"); } public async Task> SignInBeginAsync(SignInBeginDTO request) { - var fido2 = GetFido2Instance(request, _metadataService); + var fido2 = GetFido2Instance(request, metadataService); var existingCredentials = await GetExistingCredentialsAsync(request); - var signInConfiguration = await _authenticationConfigurationService.GetAuthenticationConfigurationOrDefaultAsync(request.Purpose); + var signInConfiguration = await authenticationConfigurationService.GetAuthenticationConfigurationOrDefaultAsync(request.Purpose); var options = fido2.GetAssertionOptions( existingCredentials.ToList(), @@ -338,7 +332,7 @@ public async Task> SignInBeginAsync(SignInBegi Purpose = signInConfiguration.Purpose }; - var session = await _tokenService.EncodeTokenAsync(sessionOptions, "session_", true); + var session = await tokenService.EncodeTokenAsync(sessionOptions, "session_", true); return new SessionResponse { Data = options, Session = session }; } @@ -347,32 +341,32 @@ private async Task> GetExistingCreden { if (!string.IsNullOrEmpty(request.UserId)) { - _log.LogInformation("event=signin/begin account={account} arg={arg}", _tenantProvider, "userid"); - return (await _storage.GetCredentialsByUserIdAsync(request.UserId)).Select(c => c.Descriptor); + log.LogInformation("event=signin/begin account={account} arg={arg}", tenantProvider, "userid"); + return (await storage.GetCredentialsByUserIdAsync(request.UserId)).Select(c => c.Descriptor); } if (!string.IsNullOrEmpty(request.Alias)) { - var hashedAlias = HashAlias(request.Alias, _tenantProvider.Tenant); + var hashedAlias = HashAlias(request.Alias, tenantProvider.Tenant); - var existingCredentials = await _storage.GetCredentialsByAliasAsync(hashedAlias); - _log.LogInformation("event=signin/begin account={account} arg={arg} foundCredentials={foundCredentials}", _tenantProvider, "alias", existingCredentials.Count); + var existingCredentials = await storage.GetCredentialsByAliasAsync(hashedAlias); + log.LogInformation("event=signin/begin account={account} arg={arg} foundCredentials={foundCredentials}", tenantProvider, "alias", existingCredentials.Count); return existingCredentials; } - _log.LogInformation("event=signin/begin account={account} arg={arg}", _tenantProvider, "empty"); + log.LogInformation("event=signin/begin account={account} arg={arg}", tenantProvider, "empty"); return Array.Empty(); } public async Task SignInCompleteAsync(SignInCompleteDTO request, string device, string country) { - var fido2 = GetFido2Instance(request, _metadataService); + var fido2 = GetFido2Instance(request, metadataService); - var authenticationSessionConfiguration = await _tokenService.DecodeTokenAsync(request.Session, "session_", true); + var authenticationSessionConfiguration = await tokenService.DecodeTokenAsync(request.Session, "session_", true); // Get registered credential from database - var credential = await _storage.GetCredential(request.Response.Id); + var credential = await storage.GetCredential(request.Response.Id); if (credential == null) { throw new UnknownCredentialException(Base64Url.Encode(request.Response.Id)); @@ -382,7 +376,7 @@ public async Task SignInCompleteAsync(SignInCompleteDTO request, IsUserHandleOwnerOfCredentialIdAsync callback = (args, _) => Task.FromResult(credential.UserHandle.SequenceEqual(args.UserHandle)); // Make the assertion - var storedCredentials = (await _storage.GetCredentialsByUserIdAsync(request.Session)).Select(c => c.PublicKey).ToList(); + var storedCredentials = (await storage.GetCredentialsByUserIdAsync(request.Session)).Select(c => c.PublicKey).ToList(); var res = await fido2.MakeAssertionAsync( request.Response, authenticationSessionConfiguration.Options, @@ -392,9 +386,9 @@ public async Task SignInCompleteAsync(SignInCompleteDTO request, callback); // Store the updated counter - await _storage.UpdateCredential(res.CredentialId, res.SignCount, country, device); + await storage.UpdateCredential(res.CredentialId, res.SignCount, country, device); - var config = await _authenticationConfigurationService.GetAuthenticationConfigurationOrDefaultAsync(authenticationSessionConfiguration.Purpose); + var config = await authenticationConfigurationService.GetAuthenticationConfigurationOrDefaultAsync(authenticationSessionConfiguration.Purpose); var userId = Encoding.UTF8.GetString(credential.UserHandle); @@ -404,21 +398,21 @@ public async Task SignInCompleteAsync(SignInCompleteDTO request, Success = true, Origin = request.Origin, RpId = request.RPID, - Timestamp = _timeProvider.GetUtcNow().UtcDateTime, + Timestamp = timeProvider.GetUtcNow().UtcDateTime, Device = device, Country = country, Nickname = credential.Nickname, CredentialId = credential.Descriptor.Id, - ExpiresAt = _timeProvider.GetUtcNow().UtcDateTime.Add(config.TimeToLive), + ExpiresAt = timeProvider.GetUtcNow().UtcDateTime.Add(config.TimeToLive), TokenId = Guid.NewGuid(), Type = "passkey_signin", Purpose = config.Purpose.Value }; - _eventLogger.LogUserSignInCompletedEvent(userId); - await _authenticationConfigurationService.UpdateLastUsedOnAsync(config); + eventLogger.LogUserSignInCompletedEvent(userId); + await authenticationConfigurationService.UpdateLastUsedOnAsync(config); - var token = await _tokenService.EncodeTokenAsync(tokenData, "verify_"); + var token = await tokenService.EncodeTokenAsync(tokenData, "verify_"); // return OK to client return new TokenResponse(token); @@ -426,18 +420,18 @@ public async Task SignInCompleteAsync(SignInCompleteDTO request, public async Task SignInVerifyAsync(SignInVerifyDTO payload) { - var token = await _tokenService.DecodeTokenAsync(payload.Token, "verify_"); + var token = await tokenService.DecodeTokenAsync(payload.Token, "verify_"); - token.Validate(_timeProvider.GetUtcNow()); + token.Validate(timeProvider.GetUtcNow()); - _eventLogger.LogUserSignInTokenVerifiedEvent(token.UserId); + eventLogger.LogUserSignInTokenVerifiedEvent(token.UserId); return token; } public Task> GetAliases(string userId) { - return _storage.GetAliasesByUserId(userId); + return storage.GetAliasesByUserId(userId); } public async Task SetAliasAsync(AliasPayload data) @@ -450,12 +444,12 @@ public async Task SetAliasAsync(AliasPayload data) { plaintext = alias; } - values.Add(HashAlias(alias, _tenantProvider.Tenant), plaintext); + values.Add(HashAlias(alias, tenantProvider.Tenant), plaintext); } try { - await _storage.StoreAlias(data.UserId, values); + await storage.StoreAlias(data.UserId, values); } catch (DbUpdateException ex) { @@ -471,7 +465,7 @@ private string HashAlias(string username, string tenant) var sw = Stopwatch.StartNew(); var hashedUsername = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(tenant + username))); sw.Stop(); - _log.LogInformation("SHA256 Hashing username took {duration}ms", sw.ElapsedMilliseconds); + log.LogInformation("SHA256 Hashing username took {duration}ms", sw.ElapsedMilliseconds); return hashedUsername; } diff --git a/src/Service/Models/EFStoredCredential.cs b/src/Service/Models/EFStoredCredential.cs index a510cdce1..fd9e6b56a 100644 --- a/src/Service/Models/EFStoredCredential.cs +++ b/src/Service/Models/EFStoredCredential.cs @@ -10,6 +10,17 @@ public class EFStoredCredential : PerTenant public AuthenticatorTransport[]? DescriptorTransports { get; set; } public required byte[] PublicKey { get; set; } public required byte[] UserHandle { get; set; } + + public string UserId + { + get => Encoding.UTF8.GetString(UserHandle); + // This setter is required by EF, but the value should already be + // set in UserHandle. Ideally, we'd remove this column and use a + // computed column or query filter instead, but it is what it is. + [Obsolete("This should only be used internally by EF.", true)] + private set { } + } + public required uint SignatureCounter { get; set; } public required string AttestationFmt { get; set; } public required DateTime CreatedAt { get; set; } @@ -20,68 +31,54 @@ public class EFStoredCredential : PerTenant public string? Country { get; set; } public string? Device { get; set; } public string? Nickname { get; set; } - public string UserId - { - get => Encoding.UTF8.GetString(UserHandle); - // This setter is required by EF, but the value should already be - // set in UserHandle. Ideally, we'd remove this column and use a - // computed column or query filter instead, but it is what it is. - [Obsolete("This should only be used internally by EF.", true)] - private set { } - } + public string? AuthenticatorDisplayName { get; set; } public bool? BackupState { get; set; } - public bool? IsBackupEligible { get; set; } - public bool? IsDiscoverable { get; set; } - internal StoredCredential ToStoredCredential() + internal StoredCredential ToStoredCredential() => new() { - return new StoredCredential - { - Descriptor = new PublicKeyCredentialDescriptor(DescriptorType.Value, DescriptorId, DescriptorTransports), - PublicKey = PublicKey, - UserHandle = UserHandle, - SignatureCounter = SignatureCounter, - AttestationFmt = AttestationFmt, - CreatedAt = CreatedAt, - AaGuid = AaGuid, - LastUsedAt = LastUsedAt, - RPID = RPID, - Origin = Origin, - Country = Country, - Device = Device, - Nickname = Nickname, - BackupState = BackupState, - IsBackupEligible = IsBackupEligible, - IsDiscoverable = IsDiscoverable - }; - } + Descriptor = new PublicKeyCredentialDescriptor(DescriptorType.Value, DescriptorId, DescriptorTransports), + PublicKey = PublicKey, + UserHandle = UserHandle, + SignatureCounter = SignatureCounter, + AttestationFmt = AttestationFmt, + CreatedAt = CreatedAt, + AaGuid = AaGuid, + LastUsedAt = LastUsedAt, + RPID = RPID, + Origin = Origin, + Country = Country, + Device = Device, + Nickname = Nickname, + AuthenticatorDisplayName = AuthenticatorDisplayName, + BackupState = BackupState, + IsBackupEligible = IsBackupEligible, + IsDiscoverable = IsDiscoverable + }; - internal static EFStoredCredential FromStoredCredential(StoredCredential s, string tenant) + internal static EFStoredCredential FromStoredCredential(StoredCredential credential, string tenant) => new() { - return new EFStoredCredential - { - Tenant = tenant, - PublicKey = s.PublicKey, - UserHandle = s.UserHandle, - SignatureCounter = s.SignatureCounter, - AttestationFmt = s.AttestationFmt, - CreatedAt = s.CreatedAt, - AaGuid = s.AaGuid, - LastUsedAt = s.LastUsedAt, - RPID = s.RPID, - Origin = s.Origin, - Country = s.Country, - Device = s.Device, - Nickname = s.Nickname, - DescriptorId = s.Descriptor.Id, - DescriptorTransports = s.Descriptor.Transports, - DescriptorType = s.Descriptor.Type, - BackupState = s.BackupState, - IsBackupEligible = s.IsBackupEligible, - IsDiscoverable = s.IsDiscoverable - }; - } + Tenant = tenant, + PublicKey = credential.PublicKey, + UserHandle = credential.UserHandle, + SignatureCounter = credential.SignatureCounter, + AttestationFmt = credential.AttestationFmt, + CreatedAt = credential.CreatedAt, + AaGuid = credential.AaGuid, + LastUsedAt = credential.LastUsedAt, + RPID = credential.RPID, + Origin = credential.Origin, + Country = credential.Country, + Device = credential.Device, + Nickname = credential.Nickname, + AuthenticatorDisplayName = credential.AuthenticatorDisplayName, + DescriptorId = credential.Descriptor.Id, + DescriptorTransports = credential.Descriptor.Transports, + DescriptorType = credential.Descriptor.Type, + BackupState = credential.BackupState, + IsBackupEligible = credential.IsBackupEligible, + IsDiscoverable = credential.IsDiscoverable + }; } \ No newline at end of file diff --git a/src/Service/Models/RegisterSession.cs b/src/Service/Models/RegisterSession.cs index 3f02a5240..3a672d718 100644 --- a/src/Service/Models/RegisterSession.cs +++ b/src/Service/Models/RegisterSession.cs @@ -5,6 +5,6 @@ namespace Passwordless.Service.Models; public class RegisterSession { public CredentialCreateOptions Options { get; set; } - public HashSet Aliases { get; set; } + public HashSet? Aliases { get; set; } public bool AliasHashing { get; set; } = true; } \ No newline at end of file diff --git a/src/Service/Models/RegisterToken.cs b/src/Service/Models/RegisterToken.cs index a527b7487..66cec0963 100644 --- a/src/Service/Models/RegisterToken.cs +++ b/src/Service/Models/RegisterToken.cs @@ -23,7 +23,7 @@ public class RegisterToken : Token public string Attestation { get; set; } = "None"; [MessagePack.Key(14)] - public string AuthenticatorType { get; set; } + public string? AuthenticatorType { get; set; } [MessagePack.Key(15)] public bool Discoverable { get; set; } = true; @@ -68,13 +68,16 @@ public class VerifySignInToken : Token public string Country { get; set; } [MessagePack.Key(17)] - public string Nickname { get; set; } + public string? Nickname { get; set; } [MessagePack.Key(18)] public byte[] CredentialId { get; set; } [MessagePack.Key(19)] public string Purpose { get; set; } + + [MessagePack.Key(20)] + public string? AuthenticatorDisplayName { get; set; } } [MessagePackObject] diff --git a/src/Service/Models/RegistrationCompleteDTO.cs b/src/Service/Models/RegistrationCompleteDTO.cs index 7c06baeaa..d4f187fab 100644 --- a/src/Service/Models/RegistrationCompleteDTO.cs +++ b/src/Service/Models/RegistrationCompleteDTO.cs @@ -10,5 +10,7 @@ public class RegistrationCompleteDTO : RequestBase public AuthenticatorAttestationRawResponse Response { get; set; } [MaxLength(64, ErrorMessage = "Nickname cannot be longer than 64 characters.")] - public string Nickname { get; set; } + public string? Nickname { get; set; } + + public string? AuthenticatorDisplayName { get; set; } } \ No newline at end of file diff --git a/src/Service/Models/StoredCredential.cs b/src/Service/Models/StoredCredential.cs index 2ac940934..d2368f3a9 100644 --- a/src/Service/Models/StoredCredential.cs +++ b/src/Service/Models/StoredCredential.cs @@ -8,6 +8,7 @@ public class StoredCredential public required PublicKeyCredentialDescriptor Descriptor { get; set; } public required byte[] PublicKey { get; set; } public required byte[] UserHandle { get; set; } + public string UserId => Encoding.UTF8.GetString(UserHandle); public required uint SignatureCounter { get; set; } public required string AttestationFmt { get; set; } public required DateTime CreatedAt { get; set; } @@ -18,12 +19,8 @@ public class StoredCredential public string? Country { get; set; } public string? Device { get; set; } public string? Nickname { get; set; } - - public string UserId => Encoding.UTF8.GetString(UserHandle); - + public string? AuthenticatorDisplayName { get; set; } public bool? BackupState { get; set; } - public bool? IsBackupEligible { get; set; } - public bool? IsDiscoverable { get; set; } } \ No newline at end of file From d105a624186dd58acc5ba542256ebae8acaa2610 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:19:09 +0300 Subject: [PATCH 2/6] Store, retrieve, show authenticator display name provided by the user agent --- .../Components/Shared/Credentials.razor | 3 ++- .../Components/Shared/Credentials.razor.cs | 15 ++++++--------- src/Service/Fido2Service.cs | 12 +++++++----- src/Service/Models/RegisterToken.cs | 2 +- src/Service/Models/RegistrationCompleteDTO.cs | 2 -- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/AdminConsole/Components/Shared/Credentials.razor b/src/AdminConsole/Components/Shared/Credentials.razor index 7526dd2d3..ab0d34bbd 100644 --- a/src/AdminConsole/Components/Shared/Credentials.razor +++ b/src/AdminConsole/Components/Shared/Credentials.razor @@ -23,7 +23,7 @@
- + @if (!HideDetails) {