From 9d319eb0007e2704343210e1ddb4407a5fb95ab7 Mon Sep 17 00:00:00 2001 From: d2dyno <53011783+d2dyno1@users.noreply.github.com> Date: Sun, 31 May 2026 20:36:42 +0200 Subject: [PATCH 1/3] Begin working on App Platform --- .../SecureFolderFS.Core.Cryptography.csproj | 1 + .../Models/SecurityWrapper.cs | 5 +- .../Operational/AppPlatformCreationRoutine.cs | 91 ++++++++++++ .../Operational/AppPlatformUnlockRoutine.cs | 80 ++++++++++ .../Routines/Operational/VaultRoutines.cs | 11 ++ .../VaultAccess/VaultParser.cs | 6 +- .../AndroidVaultCredentialsService.cs | 2 +- .../IOSVaultCredentialsService.cs | 2 +- .../VaultManagerService.cs | 32 ++++ .../AppPlatformCreationViewModel.cs | 137 ++++++++++++++++++ .../SkiaVaultCredentialsService.cs | 5 +- .../WindowsVaultCredentialsService.cs | 5 +- .../RegistrationTemplateSelector.cs | 3 + .../UserControls/LoginControl.xaml | 18 ++- .../UserControls/RegisterControl.xaml | 29 ++++ .../Services/IVaultManagerService.cs | 19 +++ .../IAppPlatformVaultRegistration.cs | 24 +++ .../ViewModels/Controls/LoginViewModel.cs | 5 +- .../Wizard/CredentialsWizardViewModel.cs | 20 +++ .../Models/AppPlatformVaultOptions.cs | 12 -- 20 files changed, 477 insertions(+), 30 deletions(-) create mode 100644 src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformCreationRoutine.cs create mode 100644 src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformUnlockRoutine.cs create mode 100644 src/Platforms/SecureFolderFS.UI/ViewModels/Authentication/AppPlatformCreationViewModel.cs create mode 100644 src/Sdk/SecureFolderFS.Sdk/ViewModels/Controls/Authentication/IAppPlatformVaultRegistration.cs diff --git a/src/Core/SecureFolderFS.Core.Cryptography/SecureFolderFS.Core.Cryptography.csproj b/src/Core/SecureFolderFS.Core.Cryptography/SecureFolderFS.Core.Cryptography.csproj index d29745225..d506b3a88 100644 --- a/src/Core/SecureFolderFS.Core.Cryptography/SecureFolderFS.Core.Cryptography.csproj +++ b/src/Core/SecureFolderFS.Core.Cryptography/SecureFolderFS.Core.Cryptography.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Core/SecureFolderFS.Core/Models/SecurityWrapper.cs b/src/Core/SecureFolderFS.Core/Models/SecurityWrapper.cs index 6300d8813..fdec13c68 100644 --- a/src/Core/SecureFolderFS.Core/Models/SecurityWrapper.cs +++ b/src/Core/SecureFolderFS.Core/Models/SecurityWrapper.cs @@ -8,7 +8,7 @@ namespace SecureFolderFS.Core.Models { - internal sealed class SecurityWrapper : IWrapper, IEnumerable>, IDisposable + internal sealed class SecurityWrapper : IWrapper, IWrapper, IEnumerable>, IDisposable { private readonly KeyPair _keyPair; private readonly VaultConfigurationDataModel _configDataModel; @@ -21,6 +21,9 @@ internal sealed class SecurityWrapper : IWrapper, IEnumerable + KeyPair IWrapper.Inner => _keyPair; + public SecurityWrapper(KeyPair keyPair, VaultConfigurationDataModel configDataModel) { _keyPair = keyPair; diff --git a/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformCreationRoutine.cs b/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformCreationRoutine.cs new file mode 100644 index 000000000..1d2e9409d --- /dev/null +++ b/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformCreationRoutine.cs @@ -0,0 +1,91 @@ +using System; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using OwlCore.Storage; +using SecureFolderFS.Core.Cryptography; +using SecureFolderFS.Core.DataModels; +using SecureFolderFS.Core.Models; +using SecureFolderFS.Core.VaultAccess; +using SecureFolderFS.Shared.ComponentModel; +using SecureFolderFS.Shared.Models; +using SecureFolderFS.Shared.SecureStore; +using static SecureFolderFS.Core.Constants.Vault; +using static SecureFolderFS.Core.Cryptography.Constants; + +namespace SecureFolderFS.Core.Routines.Operational +{ + /// + /// Creation routine for App Platform vaults. Generates DEK+MAC internally (no password, no keystore.cfg). + /// + public sealed class AppPlatformCreationRoutine : ICreationRoutine + { + private readonly IFolder _vaultFolder; + private readonly VaultWriter _vaultWriter; + private VaultConfigurationDataModel? _configDataModel; + private SecureKey? _dekKey; + private SecureKey? _macKey; + + public AppPlatformCreationRoutine(IFolder vaultFolder, VaultWriter vaultWriter) + { + _vaultFolder = vaultFolder; + _vaultWriter = vaultWriter; + } + + /// + public Task InitAsync(CancellationToken cancellationToken = default) + { + var dekKey = new byte[KeyTraits.DEK_KEY_LENGTH]; + var macKey = new byte[KeyTraits.MAC_KEY_LENGTH]; + + RandomNumberGenerator.Fill(dekKey); + RandomNumberGenerator.Fill(macKey); + + _dekKey = SecureKey.TakeOwnership(dekKey); + _macKey = SecureKey.TakeOwnership(macKey); + + return Task.CompletedTask; + } + + /// + public void SetCredentials(IKeyUsage passkey) + { + // No-op: App Platform vaults don't use passkey-derived keys + } + + /// + public void SetOptions(VaultOptions vaultOptions) + { + _configDataModel = VaultConfigurationDataModel.V4FromVaultOptions(vaultOptions); + } + + /// + public async Task FinalizeAsync(CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(_configDataModel); + ArgumentNullException.ThrowIfNull(_dekKey); + ArgumentNullException.ThrowIfNull(_macKey); + + _macKey.UseKey(macKey => + { + VaultParser.CalculateConfigMac(_configDataModel, macKey, _configDataModel.PayloadMac); + }); + + // Write only sfconfig.cfg - no keystore.cfg for App Platform vaults + await _vaultWriter.WriteConfigurationAsync(_configDataModel, cancellationToken); + + // Create the content folder + if (_vaultFolder is IModifiableFolder modifiableFolder) + await modifiableFolder.CreateFolderAsync(Names.VAULT_CONTENT_FOLDERNAME, true, cancellationToken); + + return new SecurityWrapper(KeyPair.ImportKeys(_dekKey, _macKey), _configDataModel); + } + + /// + public void Dispose() + { + _dekKey?.Dispose(); + _macKey?.Dispose(); + } + } +} diff --git a/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformUnlockRoutine.cs b/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformUnlockRoutine.cs new file mode 100644 index 000000000..602ed63ad --- /dev/null +++ b/src/Core/SecureFolderFS.Core/Routines/Operational/AppPlatformUnlockRoutine.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using SecureFolderFS.Core.Cryptography; +using SecureFolderFS.Core.DataModels; +using SecureFolderFS.Core.Models; +using SecureFolderFS.Core.Validators; +using SecureFolderFS.Core.VaultAccess; +using SecureFolderFS.Shared.ComponentModel; +using SecureFolderFS.Shared.SecureStore; + +namespace SecureFolderFS.Core.Routines.Operational +{ + /// + /// Unlock routine for App Platform vaults. Accepts DEK || MAC directly from the server-brokered key hierarchy. + /// + internal sealed class AppPlatformUnlockRoutine : ICredentialsRoutine + { + private readonly VaultReader _vaultReader; + private VaultConfigurationDataModel? _configDataModel; + private SecureKey? _dekKey; + private SecureKey? _macKey; + + public AppPlatformUnlockRoutine(VaultReader vaultReader) + { + _vaultReader = vaultReader; + } + + /// + public async Task InitAsync(CancellationToken cancellationToken) + { + _configDataModel = await _vaultReader.ReadConfigurationAsync(cancellationToken); + } + + /// + public void SetCredentials(IKeyUsage passkey) + { + ArgumentNullException.ThrowIfNull(_configDataModel); + + passkey.UseKey(key => + { + if (key.Length != Cryptography.Constants.KeyTraits.DEK_KEY_LENGTH + Cryptography.Constants.KeyTraits.MAC_KEY_LENGTH) + throw new ArgumentException($"Expected {Cryptography.Constants.KeyTraits.DEK_KEY_LENGTH + Cryptography.Constants.KeyTraits.MAC_KEY_LENGTH} bytes (DEK+MAC), got {key.Length}."); + + var dekBytes = new byte[Cryptography.Constants.KeyTraits.DEK_KEY_LENGTH]; + var macBytes = new byte[Cryptography.Constants.KeyTraits.MAC_KEY_LENGTH]; + + key.Slice(0, Cryptography.Constants.KeyTraits.DEK_KEY_LENGTH).CopyTo(dekBytes); + key.Slice(Cryptography.Constants.KeyTraits.DEK_KEY_LENGTH, Cryptography.Constants.KeyTraits.MAC_KEY_LENGTH).CopyTo(macBytes); + + _dekKey = SecureKey.TakeOwnership(dekBytes); + _macKey = SecureKey.TakeOwnership(macBytes); + }); + } + + /// + public async Task FinalizeAsync(CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(_dekKey); + ArgumentNullException.ThrowIfNull(_macKey); + ArgumentNullException.ThrowIfNull(_configDataModel); + + using (_dekKey) + using (_macKey) + { + var validator = new ConfigurationValidator(_macKey); + await validator.ValidateAsync(_configDataModel, cancellationToken); + + return new SecurityWrapper(KeyPair.ImportKeys(_dekKey, _macKey), _configDataModel); + } + } + + /// + public void Dispose() + { + _dekKey?.Dispose(); + _macKey?.Dispose(); + } + } +} diff --git a/src/Core/SecureFolderFS.Core/Routines/Operational/VaultRoutines.cs b/src/Core/SecureFolderFS.Core/Routines/Operational/VaultRoutines.cs index d12da28f4..7d97f70e9 100644 --- a/src/Core/SecureFolderFS.Core/Routines/Operational/VaultRoutines.cs +++ b/src/Core/SecureFolderFS.Core/Routines/Operational/VaultRoutines.cs @@ -33,12 +33,23 @@ public ICreationRoutine CreateVault() return new CreationRoutine(_vaultFolder, VaultWriter); } + public AppPlatformCreationRoutine CreateAppPlatformVault() + { + return new AppPlatformCreationRoutine(_vaultFolder, VaultWriter); + } + public ICredentialsRoutine UnlockVault() { CheckVaultValidation(); return new UnlockRoutine(VaultReader); } + public ICredentialsRoutine UnlockAppPlatformVault() + { + CheckVaultValidation(); + return new AppPlatformUnlockRoutine(VaultReader); + } + public ICredentialsRoutine RecoverVault() { CheckVaultValidation(); diff --git a/src/Core/SecureFolderFS.Core/VaultAccess/VaultParser.cs b/src/Core/SecureFolderFS.Core/VaultAccess/VaultParser.cs index b1d4203d4..5ab4712d2 100644 --- a/src/Core/SecureFolderFS.Core/VaultAccess/VaultParser.cs +++ b/src/Core/SecureFolderFS.Core/VaultAccess/VaultParser.cs @@ -30,11 +30,7 @@ public static void CalculateConfigMac(VaultConfigurationDataModel configDataMode hmacSha256.AppendData(BitConverter.GetBytes(configDataModel.ShorteningThreshold)); // ShorteningThreshold hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.FileNameEncodingId)); // FileNameEncodingId hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.Uid)); // Uid - // hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.ServerUrl ?? string.Empty)); - // hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.VaultResource ?? string.Empty)); - // hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.Organization ?? string.Empty)); - // hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.AccessTokenEndpoint ?? string.Empty)); - // hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.DeviceRegistrationEndpoint ?? string.Empty)); + hmacSha256.AppendData(Encoding.UTF8.GetBytes(configDataModel.AppPlatform?.ServerUrl ?? string.Empty)); // AppPlatform.ServerUrl hmacSha256.AppendFinalData(Encoding.UTF8.GetBytes(configDataModel.AuthenticationMethod)); // AuthenticationMethod // Fill the hash to payload diff --git a/src/Platforms/SecureFolderFS.Maui/Platforms/Android/ServiceImplementation/AndroidVaultCredentialsService.cs b/src/Platforms/SecureFolderFS.Maui/Platforms/Android/ServiceImplementation/AndroidVaultCredentialsService.cs index 0707c3654..2d3973852 100644 --- a/src/Platforms/SecureFolderFS.Maui/Platforms/Android/ServiceImplementation/AndroidVaultCredentialsService.cs +++ b/src/Platforms/SecureFolderFS.Maui/Platforms/Android/ServiceImplementation/AndroidVaultCredentialsService.cs @@ -50,7 +50,7 @@ protected override async IAsyncEnumerable GetLoginAsync Constants.Vault.Authentication.AUTH_ANDROID_BIOMETRIC => new AndroidBiometricLoginViewModel(vaultFolder, vaultId), // App Platform - Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(), + Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(vaultFolder), _ => throw new NotSupportedException($"The authentication method '{item}' is not supported by the platform.") }; diff --git a/src/Platforms/SecureFolderFS.Maui/Platforms/iOS/ServiceImplementation/IOSVaultCredentialsService.cs b/src/Platforms/SecureFolderFS.Maui/Platforms/iOS/ServiceImplementation/IOSVaultCredentialsService.cs index 366c907f5..24fc696d1 100644 --- a/src/Platforms/SecureFolderFS.Maui/Platforms/iOS/ServiceImplementation/IOSVaultCredentialsService.cs +++ b/src/Platforms/SecureFolderFS.Maui/Platforms/iOS/ServiceImplementation/IOSVaultCredentialsService.cs @@ -63,7 +63,7 @@ Constants.Vault.Authentication.AUTH_APPLE_BIOMETRIC when AreBiometricsAvailable( }), // App Platform - Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(), + Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(vaultFolder), _ => throw new NotSupportedException($"The authentication method '{item}' is not supported by the platform.") }; diff --git a/src/Platforms/SecureFolderFS.UI/ServiceImplementation/VaultManagerService.cs b/src/Platforms/SecureFolderFS.UI/ServiceImplementation/VaultManagerService.cs index 10da9d1b3..8beb16540 100644 --- a/src/Platforms/SecureFolderFS.UI/ServiceImplementation/VaultManagerService.cs +++ b/src/Platforms/SecureFolderFS.UI/ServiceImplementation/VaultManagerService.cs @@ -32,6 +32,27 @@ public virtual async Task CreateAsync(IFolder vaultFolder, IKeyUsag return await creationRoutine.FinalizeAsync(cancellationToken); } + /// + public virtual async Task<(IDisposable UnlockContract, IKeyUsage DekKey, IKeyUsage MacKey)> CreateAppPlatformAsync(IFolder vaultFolder, VaultOptions vaultOptions, CancellationToken cancellationToken = default) + { + var routines = await VaultRoutines.CreateRoutinesAsync(vaultFolder, StreamSerializer.Instance, cancellationToken); + using var creationRoutine = routines.CreateAppPlatformVault(); + await creationRoutine.InitAsync(cancellationToken); + creationRoutine.SetOptions(vaultOptions); + + if (vaultFolder is IModifiableFolder modifiableFolder) + { + var readmeFile = await modifiableFolder.CreateFileAsync(Sdk.Constants.Vault.VAULT_README_FILENAME, true, cancellationToken); + await readmeFile.WriteAllTextAsync(Sdk.Constants.Vault.VAULT_README_MESSAGE, Encoding.UTF8, cancellationToken); + } + + var unlockContract = await creationRoutine.FinalizeAsync(cancellationToken); + if (unlockContract is not IWrapper { Inner: { } keyPair }) + throw new InvalidOperationException("Could not retrieve the KeyPair from the unlock contract."); + + return (unlockContract, keyPair.DekKey, keyPair.MacKey); + } + /// public virtual async Task UnlockAsync(IFolder vaultFolder, IKeyUsage passkey, CancellationToken cancellationToken = default) { @@ -43,6 +64,17 @@ public virtual async Task UnlockAsync(IFolder vaultFolder, IKeyUsag return await unlockRoutine.FinalizeAsync(cancellationToken); } + /// + public virtual async Task UnlockAppPlatformAsync(IFolder vaultFolder, IKeyUsage passkey, CancellationToken cancellationToken = default) + { + var routines = await VaultRoutines.CreateRoutinesAsync(vaultFolder, StreamSerializer.Instance, cancellationToken); + using var unlockRoutine = routines.UnlockAppPlatformVault(); + + await unlockRoutine.InitAsync(cancellationToken); + unlockRoutine.SetCredentials(passkey); + return await unlockRoutine.FinalizeAsync(cancellationToken); + } + /// public virtual async Task RecoverAsync(IFolder vaultFolder, string encodedRecoveryKey, CancellationToken cancellationToken = default) { diff --git a/src/Platforms/SecureFolderFS.UI/ViewModels/Authentication/AppPlatformCreationViewModel.cs b/src/Platforms/SecureFolderFS.UI/ViewModels/Authentication/AppPlatformCreationViewModel.cs new file mode 100644 index 000000000..ff70a1454 --- /dev/null +++ b/src/Platforms/SecureFolderFS.UI/ViewModels/Authentication/AppPlatformCreationViewModel.cs @@ -0,0 +1,137 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using SecureFolderFS.Core.Cryptography.Jwe; +using SecureFolderFS.Sdk.AppPlatform; +using SecureFolderFS.Sdk.Enums; +using SecureFolderFS.Sdk.EventArguments; +using SecureFolderFS.Sdk.ViewModels.Controls.Authentication; +using SecureFolderFS.Shared; +using SecureFolderFS.Shared.ComponentModel; +using SecureFolderFS.Shared.Models; +using SecureFolderFS.Shared.SecureStore; + +namespace SecureFolderFS.UI.ViewModels.Authentication +{ + public sealed partial class AppPlatformCreationViewModel : AuthenticationViewModel, IVaultOptionsProvider, IAppPlatformVaultRegistration + { + private AppPlatformClient? _client; + + [ObservableProperty] private string? _ServerUrl; + [ObservableProperty] private bool _IsAuthenticated; + + /// + public override event EventHandler? StateChanged; + + /// + public override event EventHandler? CredentialsProvided; + + /// + public override bool CanComplement { get; } = false; + + /// + public override AuthenticationStage Availability { get; } = AuthenticationStage.FirstStageOnly; + + public AppPlatformCreationViewModel() + : base(Core.Constants.Vault.Authentication.AUTH_APP_PLATFORM) + { + Title = "App Platform"; + } + + /// + public override Task RevokeAsync(string? id, CancellationToken cancellationToken = default) + { + return Task.FromException(new NotSupportedException()); + } + + /// + public override Task> EnrollAsync(string id, byte[]? data, CancellationToken cancellationToken = default) + { + return Task.FromException>(new NotSupportedException()); + } + + /// + public override Task> AcquireAsync(string id, byte[]? data, CancellationToken cancellationToken = default) + { + return Task.FromException>(new NotSupportedException()); + } + + /// + protected override async Task ProvideCredentialsAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(ServerUrl)) + throw new InvalidOperationException("A server URL is required."); + + var authProvider = DI.Service(); + + _client?.Dispose(); + _client = new AppPlatformClient(ServerUrl); + + var authConfig = await _client.GetAuthConfigAsync(cancellationToken); + var accessToken = await authProvider.GetAccessTokenAsync( + authConfig.Authority, authConfig.ClientId, authConfig.Scopes, cancellationToken); + _client.SetAccessToken(accessToken); + + var user = await _client.GetMeAsync(cancellationToken); + if (!user.IsSetupComplete || string.IsNullOrWhiteSpace(user.PublicKeyJwk)) + throw new InvalidOperationException("Complete the App Platform first-time setup before creating a vault."); + + IsAuthenticated = true; + + var tcs = new TaskCompletionSource(); + CredentialsProvided?.Invoke(this, new(ManagedKey.Empty, tcs)); + await tcs.Task; + } + + /// + public VaultOptions AmendVaultOptions(VaultOptions options) + { + return options with + { + AppPlatform = new AppPlatformVaultOptions + { + ServerUrl = ServerUrl! + } + }; + } + + /// + public async Task RegisterVaultAsync(string vaultId, string? name, IKeyUsage dekKey, IKeyUsage macKey, CancellationToken cancellationToken = default) + { + if (_client is null) + throw new InvalidOperationException("The App Platform connection has not been authenticated."); + + var user = await _client.GetMeAsync(cancellationToken); + if (string.IsNullOrWhiteSpace(user.PublicKeyJwk)) + throw new InvalidOperationException("The user account is not set up."); + + var vaultKeyJwe = GetVaultJweKey(user, dekKey, macKey); + await _client.RegisterVaultAsync(vaultId, name, vaultKeyJwe, description: null, cancellationToken); + } + + private static unsafe string GetVaultJweKey(AppPlatformClient.UserInfo userInfo, IKeyUsage dekKey, IKeyUsage macKey) + { + return dekKey.UseKey(dek => + { + fixed (byte* dekPtr = dek) + { + var state = (dekPtr: (nint)dekPtr, dekLen: dek.Length); + return macKey.UseKey(state, (mac, s) => + { + var localDek = new ReadOnlySpan((byte*)s.dekPtr, s.dekLen); + return JweHelper.EncryptVaultKey(localDek, mac, userInfo.PublicKeyJwk); + }); + } + }); + } + + /// + public override void Dispose() + { + _client?.Dispose(); + _client = null; + base.Dispose(); + } + } +} diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/ServiceImplementation/SkiaVaultCredentialsService.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/ServiceImplementation/SkiaVaultCredentialsService.cs index d06020069..802e816c8 100644 --- a/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/ServiceImplementation/SkiaVaultCredentialsService.cs +++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/ServiceImplementation/SkiaVaultCredentialsService.cs @@ -47,6 +47,9 @@ public override async IAsyncEnumerable GetCreationAsync // Device Link yield return new DeviceLinkCreationViewModel(vaultFolder, vaultId) { Icon = new ImageGlyph("\uE8EA") }; + // App Platform + yield return new AppPlatformCreationViewModel() { Icon = new ImageGlyph("\uF69B") }; + await Task.CompletedTask; } @@ -78,7 +81,7 @@ protected override async IAsyncEnumerable GetLoginAsync Constants.Vault.Authentication.AUTH_DEVICE_LINK => new DeviceLinkLoginViewModel(vaultFolder, vaultId).WithInitAsync(cancellationToken), // App Platform - Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(), + Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(vaultFolder), _ => throw new NotSupportedException($"The authentication method '{item}' is not supported by the platform.") }; diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/ServiceImplementation/WindowsVaultCredentialsService.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/ServiceImplementation/WindowsVaultCredentialsService.cs index e08b855d6..6cf7025d6 100644 --- a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/ServiceImplementation/WindowsVaultCredentialsService.cs +++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/ServiceImplementation/WindowsVaultCredentialsService.cs @@ -45,6 +45,9 @@ public override async IAsyncEnumerable GetCreationAsync // Device Link yield return new DeviceLinkCreationViewModel(vaultFolder, vaultId) { Icon = new ImageGlyph("\uE8EA") }; + + // App Platform + yield return new AppPlatformCreationViewModel() { Icon = new ImageGlyph("\uF69B") }; } /// @@ -75,7 +78,7 @@ protected override async IAsyncEnumerable GetLoginAsync Constants.Vault.Authentication.AUTH_DEVICE_LINK => new DeviceLinkLoginViewModel(vaultFolder, vaultId) { Icon = new ImageGlyph("\uE8EA") }, // App Platform - Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(), + Constants.Vault.Authentication.AUTH_APP_PLATFORM => new AppPlatformLoginViewModel(vaultFolder), _ => throw new NotSupportedException($"The authentication method '{item}' is not supported by the platform.") }; diff --git a/src/Platforms/SecureFolderFS.Uno/TemplateSelectors/RegistrationTemplateSelector.cs b/src/Platforms/SecureFolderFS.Uno/TemplateSelectors/RegistrationTemplateSelector.cs index a9765595f..026c8aad6 100644 --- a/src/Platforms/SecureFolderFS.Uno/TemplateSelectors/RegistrationTemplateSelector.cs +++ b/src/Platforms/SecureFolderFS.Uno/TemplateSelectors/RegistrationTemplateSelector.cs @@ -25,6 +25,8 @@ internal sealed class RegistrationTemplateSelector : BaseTemplateSelector TouchIDTemplate, #endif DeviceLinkCreationViewModel => DeviceLinkTemplate, + AppPlatformCreationViewModel => AppPlatformTemplate, _ => base.SelectTemplateCore(item, container) }; } diff --git a/src/Platforms/SecureFolderFS.Uno/UserControls/LoginControl.xaml b/src/Platforms/SecureFolderFS.Uno/UserControls/LoginControl.xaml index 6cc2f28b5..666f08c93 100644 --- a/src/Platforms/SecureFolderFS.Uno/UserControls/LoginControl.xaml +++ b/src/Platforms/SecureFolderFS.Uno/UserControls/LoginControl.xaml @@ -144,15 +144,19 @@ + Glyph="" /> - - + + + +