using System.Text; using System.Text.Json; using Fido2NetLib; using Fido2NetLib.Objects; namespace Passwordless; public class CredentialManager { private readonly IFido2 _fido2; private readonly OptionsCache _optionsCache; private readonly JsonSerializerOptions _jsonOptions; public CredentialManager(IFido2 fido2, OptionsCache optionsCache) { _fido2 = fido2; _optionsCache = optionsCache; _jsonOptions = new JsonSerializerOptions() { WriteIndented = true, }; } public CredentialCreateOptions BuildCredentialOptions(string login) { var loginDisplay = Encoding.UTF8.GetString(Base64UrlConverter.Decode(login)); var loginName = NameTransform.ToFileName(loginDisplay); var user = new Fido2User { DisplayName = loginDisplay, Id = Base64UrlConverter.Decode(login), Name = loginName, }; var authenticatorSelection = new AuthenticatorSelection { UserVerification = UserVerificationRequirement.Discouraged, RequireResidentKey = false, }; var extensions = new AuthenticationExtensionsClientInputs { Extensions = true, UserVerificationMethod = false, }; var options = _fido2.RequestNewCredential(user, new List(), authenticatorSelection, AttestationConveyancePreference.None, extensions); _optionsCache.Set(options); return options; } public async Task RegisterCredential(string login, AuthenticatorAttestationRawResponse attestationResponse) { // 2. Create callback so that lib can verify credential id is unique to this user static Task Callback(IsCredentialIdUniqueToUserParams args, CancellationToken cancellationToken) { return Task.FromResult(!File.Exists($"./data/{args.User.Name}")); } var loginDisplay = Encoding.UTF8.GetString(Base64UrlConverter.Decode(login)); var loginName = NameTransform.ToFileName(loginDisplay); var options = _optionsCache.Get(loginName); var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, Callback); if (success.Status == "ok") { await using var fileStream = File.OpenWrite($"./data/{success.Result!.User.Name}.json"); await JsonSerializer.SerializeAsync(fileStream, success.Result, _jsonOptions); } return success; } }