From 515a765232cdb78f25e63f6a5ca252b93a2679e9 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 24 Jan 2025 17:22:26 +0100 Subject: [PATCH] Account refactoring Closes #111. --- BTCPayApp.Core/Auth/AuthStateProvider.cs | 218 ++++++++---------- BTCPayApp.Core/Auth/IAccountManager.cs | 12 +- BTCPayApp.Core/BTCPayAccount.cs | 20 +- BTCPayApp.Core/BTCPayAppConfig.cs | 3 +- .../BTCPayServer/BTCPayConnectionManager.cs | 10 +- BTCPayApp.Core/Backup/SyncService.cs | 2 +- BTCPayApp.Core/Helpers/StoreHelpers.cs | 2 +- BTCPayApp.Tests/CoreTests.cs | 4 +- BTCPayApp.UI/App.razor | 5 +- BTCPayApp.UI/Components/SetupStatus.razor | 2 +- BTCPayApp.UI/Components/UserSwitch.razor | 152 +++++------- BTCPayApp.UI/Features/RootState.cs | 4 +- BTCPayApp.UI/Features/UserState.cs | 9 +- BTCPayApp.UI/Pages/CheckoutPage.razor | 2 +- BTCPayApp.UI/Pages/InvoicePage.razor | 2 +- BTCPayApp.UI/Pages/LoadingPage.razor | 20 -- .../Pages/Settings/ChangePasscodePage.razor | 2 +- .../Pages/Settings/EncryptionKey.razor | 2 +- BTCPayApp.UI/Pages/Settings/IndexPage.razor | 44 ++-- .../Pages/Settings/SelectStorePage.razor | 53 ++++- .../Pages/SignedOut/ForgotPasswordPage.razor | 2 +- BTCPayApp.UI/Pages/SignedOut/IndexPage.razor | 21 +- .../IndexPage.razor.css} | 0 BTCPayApp.UI/Pages/SignedOut/LoginPage.razor | 2 +- .../Pages/SignedOut/SignedOutBasePage.razor | 66 ++++-- BTCPayApp.UI/StateMiddleware.cs | 15 +- 26 files changed, 314 insertions(+), 360 deletions(-) delete mode 100644 BTCPayApp.UI/Pages/LoadingPage.razor rename BTCPayApp.UI/Pages/{LoadingPage.razor.css => SignedOut/IndexPage.razor.css} (100%) diff --git a/BTCPayApp.Core/Auth/AuthStateProvider.cs b/BTCPayApp.Core/Auth/AuthStateProvider.cs index beae1cbe..f80a6d05 100644 --- a/BTCPayApp.Core/Auth/AuthStateProvider.cs +++ b/BTCPayApp.Core/Auth/AuthStateProvider.cs @@ -13,38 +13,41 @@ namespace BTCPayApp.Core.Auth; public class AuthStateProvider( IHttpClientFactory clientFactory, - ConfigProvider configProvider, IAuthorizationService authService, + ConfigProvider configProvider, IOptionsMonitor identityOptions) : AuthenticationStateProvider, IAccountManager, IHostedService { - private const string AccountKeyPrefix = "Account"; - private const string CurrentAccountKey = "CurrentAccount"; private bool _isInitialized; private bool _refreshUserInfo; - private BTCPayAccount? _account; - private AppUserInfo? _userInfo; private CancellationTokenSource? _pingCts; private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly ClaimsPrincipal _unauthenticated = new(new ClaimsIdentity()); - public BTCPayAccount? GetAccount() => _account; - public AppUserInfo? GetUserInfo() => _userInfo; - + public BTCPayAccount? Account { get; private set; } + public AppUserInfo? UserInfo { get; private set; } + public string? CurrentStoreId { get; private set; } public AsyncEventHandler? OnBeforeAccountChange { get; set; } public AsyncEventHandler? OnAfterAccountChange { get; set; } public AsyncEventHandler? OnBeforeStoreChange { get; set; } public AsyncEventHandler? OnAfterStoreChange { get; set; } - public AsyncEventHandler? OnAccountInfoChange { get; set; } public AsyncEventHandler? OnUserInfoChange { get; set; } public Task StartAsync(CancellationToken cancellationToken) { _pingCts = new CancellationTokenSource(); + //syncService.LocalUpdated += SyncServiceLocalUpdated; _ = PingOccasionally(_pingCts.Token); return Task.CompletedTask; } + public Task StopAsync(CancellationToken cancellationToken) + { + //syncService.LocalUpdated -= SyncServiceLocalUpdated; + _pingCts?.Cancel(); + return Task.CompletedTask; + } + private async Task PingOccasionally(CancellationToken pingCtsToken) { while (pingCtsToken.IsCancellationRequested is false) @@ -54,17 +57,20 @@ private async Task PingOccasionally(CancellationToken pingCtsToken) } } - public Task StopAsync(CancellationToken cancellationToken) + /*private Task SyncServiceLocalUpdated(object? sender, string[] keys) { - _pingCts?.Cancel(); + if (keys.Contains(BTCPayAppConfig.Key)) + { + // TODO: Implement this method + } return Task.CompletedTask; - } + }*/ public BTCPayAppClient GetClient(string? baseUri = null) { - if (string.IsNullOrEmpty(baseUri) && string.IsNullOrEmpty(_account?.BaseUri)) + if (string.IsNullOrEmpty(baseUri) && string.IsNullOrEmpty(Account?.BaseUri)) throw new ArgumentException("No base URI present or provided.", nameof(baseUri)); - return new BTCPayAppClient(baseUri ?? _account!.BaseUri, _account?.AccessToken, clientFactory.CreateClient()); + return new BTCPayAppClient(baseUri ?? Account!.BaseUri, Account?.AccessToken, clientFactory.CreateClient()); } public override async Task GetAuthenticationStateAsync() @@ -76,48 +82,46 @@ public override async Task GetAuthenticationStateAsync() await _semaphore.WaitAsync(); // initialize with persisted account - if (!_isInitialized && _account == null) + if (!_isInitialized && Account == null) { - _account = await GetCurrentAccount(); + Account = await configProvider.Get(BTCPayAccount.Key); + CurrentStoreId = (await configProvider.Get(BTCPayAppConfig.Key))?.CurrentStoreId; _isInitialized = true; } - var oldUserInfo = _userInfo; - var needsRefresh = _refreshUserInfo || _userInfo == null; - if (needsRefresh && !string.IsNullOrEmpty(_account?.AccessToken)) + var oldUserInfo = UserInfo; + var needsRefresh = _refreshUserInfo || UserInfo == null; + if (needsRefresh && !string.IsNullOrEmpty(Account?.AccessToken)) { var cts = new CancellationTokenSource(5000); - _userInfo = await GetClient().GetUserInfo(cts.Token); + UserInfo = await GetClient().GetUserInfo(cts.Token); _refreshUserInfo = false; } - if (_userInfo != null) + if (Account != null && UserInfo != null) { var claims = new List { - new(identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, _userInfo.UserId!), - new(identityOptions.CurrentValue.ClaimsIdentity.UserNameClaimType, _userInfo.Name ?? _userInfo.Email!), - new(identityOptions.CurrentValue.ClaimsIdentity.EmailClaimType, _userInfo.Email!) + new(identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, UserInfo.UserId!), + new(identityOptions.CurrentValue.ClaimsIdentity.UserNameClaimType, UserInfo.Name ?? UserInfo.Email!), + new(identityOptions.CurrentValue.ClaimsIdentity.EmailClaimType, UserInfo.Email!) }; - if (_userInfo.Roles?.Any() is true) - claims.AddRange(_userInfo.Roles.Select(role => + if (UserInfo.Roles?.Any() is true) + claims.AddRange(UserInfo.Roles.Select(role => new Claim(identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, role))); - if (_userInfo.Stores?.Any() is true) - claims.AddRange(_userInfo.Stores.Select(store => + if (UserInfo.Stores?.Any() is true) + claims.AddRange(UserInfo.Stores.Select(store => new Claim(store.Id, string.Join(',', store.Permissions ?? [])))); user = new ClaimsPrincipal(new ClaimsIdentity(claims, "Greenfield")); } var res = new AuthenticationState(user); - if (AppUserInfo.Equals(oldUserInfo, _userInfo)) return res; + if (AppUserInfo.Equals(oldUserInfo, UserInfo)) return res; - if (_userInfo != null) + if (Account != null && UserInfo != null) { - OnUserInfoChange?.Invoke(this, _userInfo); - // update account user info - _account!.SetInfo(_userInfo.Email!, _userInfo.Name, _userInfo.ImageUrl); - OnAccountInfoChange?.Invoke(this, _account); - await UpdateAccount(_account); + OnUserInfoChange?.Invoke(this, UserInfo); + await UpdateAccount(Account); } NotifyAuthenticationStateChanged(Task.FromResult(res)); @@ -125,7 +129,7 @@ public override async Task GetAuthenticationStateAsync() } catch { - _userInfo = null; + UserInfo = null; return new AuthenticationState(user); } finally @@ -138,7 +142,7 @@ public async Task CheckAuthenticated(bool refreshUser = false) { if (refreshUser) _refreshUserInfo = true; await GetAuthenticationStateAsync(); - return _userInfo != null; + return UserInfo != null; } public async Task IsAuthorized(string policy, object? resource = null) @@ -148,46 +152,37 @@ public async Task IsAuthorized(string policy, object? resource = null) return result.Succeeded; } - public async Task Logout() - { - _userInfo = null; - OnUserInfoChange?.Invoke(this, _userInfo); - if (_account == null) return; - _account.AccessToken = null; - await UpdateAccount(_account); - await SetCurrentAccount(null); - } - - public async Task SetCurrentStoreId(string storeId) + public async Task SetCurrentStoreId(string? storeId) { - var store = GetUserStore(storeId); - if (store == null) return new FormResult(false, $"Store with ID '{storeId}' does not exist or belong to the user."); - - if (store.Id != GetCurrentStore()?.Id) - await SetCurrentStore(store); + if (!string.IsNullOrEmpty(storeId)) + { + var store = GetUserStore(storeId); + if (store == null) return new FormResult(false, $"Store with ID '{storeId}' does not exist or belong to the user."); + if (store.Id != GetCurrentStore()?.Id) + await SetCurrentStore(store); + } + else + { + await SetCurrentStore(null); + } return new FormResult(true); } - private async Task SetCurrentStore(AppUserStoreInfo store) + private async Task SetCurrentStore(AppUserStoreInfo? store) { OnBeforeStoreChange?.Invoke(this, GetCurrentStore()); - // create associated POS app if there is none - store = await EnsureStorePos(store); + if (store != null) + store = await EnsureStorePos(store); - _account!.CurrentStoreId = store.Id; - await UpdateAccount(_account); + CurrentStoreId = store?.Id; - OnAfterStoreChange?.Invoke(this, store); - } + var appConfig = await configProvider.Get(BTCPayAppConfig.Key) ?? new BTCPayAppConfig(); + appConfig.CurrentStoreId = CurrentStoreId; + await configProvider.Set(BTCPayAppConfig.Key, appConfig, true); - public async Task UnsetCurrentStore() - { - OnBeforeStoreChange?.Invoke(this, GetCurrentStore()); - _account!.CurrentStoreId = null; - await UpdateAccount(_account); - OnAfterStoreChange?.Invoke(this, null); + OnAfterStoreChange?.Invoke(this, store); } public async Task EnsureStorePos(AppUserStoreInfo store, bool? forceCreate = false) @@ -209,15 +204,14 @@ public async Task EnsureStorePos(AppUserStoreInfo store, bool? return store; } - public AppUserStoreInfo? GetUserStore(string storeId) + private AppUserStoreInfo? GetUserStore(string storeId) { - return _userInfo?.Stores?.FirstOrDefault(store => store.Id == storeId); + return UserInfo?.Stores?.FirstOrDefault(store => store.Id == storeId); } public AppUserStoreInfo? GetCurrentStore() { - var storeId = _account?.CurrentStoreId; - return string.IsNullOrEmpty(storeId) ? null : GetUserStore(storeId); + return string.IsNullOrEmpty(CurrentStoreId) ? null : GetUserStore(CurrentStoreId); } public async Task> AcceptInvite(string inviteUrl, CancellationToken? cancellation = default) @@ -233,8 +227,8 @@ public async Task> AcceptInvite(string inviteUrl, try { var response = await GetClient(serverUrl).AcceptInvite(payload, cancellation.GetValueOrDefault()); - var account = await GetAccount(serverUrl, response.Email!); - await SetCurrentAccount(account); + var account = new BTCPayAccount(serverUrl, response.Email!); + await SetAccount(account); var message = "Invitation accepted."; if (response.EmailHasBeenConfirmed is true) message += " Your email has been confirmed."; @@ -263,9 +257,8 @@ public async Task Login(string serverUrl, string email, string passw { var response = await GetClient(serverUrl).Login(payload, cancellation.GetValueOrDefault()); if (string.IsNullOrEmpty(response.AccessToken)) throw new Exception("Did not obtain valid API token."); - var account = await GetAccount(serverUrl, email); - account.AccessToken = response.AccessToken; - await SetCurrentAccount(account); + var account = new BTCPayAccount(serverUrl, email, response.AccessToken); + await SetAccount(account); return new FormResult(true); } catch (Exception e) @@ -281,9 +274,8 @@ public async Task LoginWithCode(string serverUrl, string email, stri var client = GetClient(serverUrl); var response = await client.Login(code, cancellation.GetValueOrDefault()); if (string.IsNullOrEmpty(response.AccessToken)) throw new Exception("Did not obtain valid API token."); - var account = await GetAccount(serverUrl, email); - account.AccessToken = response.AccessToken; - await SetCurrentAccount(account); + var account = new BTCPayAccount(serverUrl, email, response.AccessToken); + await SetAccount(account); return new FormResult(true); } catch (Exception e) @@ -302,7 +294,7 @@ public async Task Register(string serverUrl, string email, string pa try { var response = await GetClient(serverUrl).RegisterUser(payload, cancellation.GetValueOrDefault()); - var account = await GetAccount(serverUrl, email); + var account = new BTCPayAccount(serverUrl, email); var message = "Account created."; if (response.ContainsKey("accessToken")) { @@ -318,7 +310,7 @@ public async Task Register(string serverUrl, string email, string pa if (signup?.RequiresApproval is true) message += " The new account requires approval by an admin before you can log in."; } - await SetCurrentAccount(account); + await SetAccount(account); return new FormResult(true, message); } catch (Exception e) @@ -342,9 +334,8 @@ public async Task ResetPassword(string serverUrl, string email, stri if (response?.ContainsKey("accessToken") is true) { var access = response.ToObject(); - var account = await GetAccount(serverUrl, email); - account.AccessToken = access!.AccessToken; - await SetCurrentAccount(account); + var account = new BTCPayAccount(serverUrl, email, access!.AccessToken); + await SetAccount(account); } return new FormResult(true, isForgotStep @@ -386,12 +377,10 @@ public async Task> ChangeAccountInfo(string emai try { var userData = await GetClient().UpdateCurrentUser(payload, cancellation.GetValueOrDefault()); - _account!.SetInfo(userData.Email!, userData.Name, userData.ImageUrl); - OnAccountInfoChange?.Invoke(this, _account); - if (_userInfo != null) + if (UserInfo != null) { - _userInfo.SetInfo(userData.Email!, userData.Name, userData.ImageUrl); - OnUserInfoChange?.Invoke(this, _userInfo); + UserInfo.SetInfo(userData.Email!, userData.Name, userData.ImageUrl); + OnUserInfoChange?.Invoke(this, UserInfo); } return new FormResult(true, "Your account info has been changed.", userData); } @@ -401,55 +390,28 @@ public async Task> ChangeAccountInfo(string emai } } - private static string GetKey(string accountId) => $"{AccountKeyPrefix}:{accountId}"; - - public async Task> GetAccounts(string? hostFilter = null) - { - var prefix = $"{AccountKeyPrefix}:" + (hostFilter == null ? "" : $"{hostFilter}:"); - var keys = (await configProvider.List(prefix)).ToArray(); - var accounts = new List(); - foreach (var key in keys) - { - var account = await configProvider.Get(key); - accounts.Add(account!); - } - return accounts; - } - - public async Task UpdateAccount(BTCPayAccount account) - { - await configProvider.Set(GetKey(account.Id), account, false); - } - - public async Task RemoveAccount(BTCPayAccount account) - { - await configProvider.Set(GetKey(account.Id), null, false); - } - - private async Task GetAccount(string serverUrl, string email) + public async Task Logout() { - var accountId = BTCPayAccount.GetId(serverUrl, email); - var account = await configProvider.Get(GetKey(accountId)); - return account ?? new BTCPayAccount(serverUrl, email); + if (Account == null) return; + Account.AccessToken = null; + await SetAccount(Account); } - private async Task GetCurrentAccount() + private async Task UpdateAccount(BTCPayAccount account) { - var accountId = await configProvider.Get(CurrentAccountKey); - if (string.IsNullOrEmpty(accountId)) return null; - return await configProvider.Get(GetKey(accountId)); + await configProvider.Set(BTCPayAccount.Key, account, false); } - private async Task SetCurrentAccount(BTCPayAccount? account) + private async Task SetAccount(BTCPayAccount account) { - OnBeforeAccountChange?.Invoke(this, _account); - if (account != null) await UpdateAccount(account); - await configProvider.Set(CurrentAccountKey, account?.Id, false); - _account = account; - _userInfo = null; + OnBeforeAccountChange?.Invoke(this, Account); + await UpdateAccount(account); + Account = account; + UserInfo = null; + OnUserInfoChange?.Invoke(this, UserInfo); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); - OnAfterAccountChange?.Invoke(this, _account); + OnAfterAccountChange?.Invoke(this, Account); var store = GetCurrentStore(); if (store != null) await SetCurrentStore(store); diff --git a/BTCPayApp.Core/Auth/IAccountManager.cs b/BTCPayApp.Core/Auth/IAccountManager.cs index 94fa745e..485a476c 100644 --- a/BTCPayApp.Core/Auth/IAccountManager.cs +++ b/BTCPayApp.Core/Auth/IAccountManager.cs @@ -6,9 +6,8 @@ namespace BTCPayApp.Core.Auth; public interface IAccountManager { - public BTCPayAccount? GetAccount(); - public Task> GetAccounts(string? hostFilter = null); - public AppUserInfo? GetUserInfo(); + public BTCPayAccount? Account { get; } + public AppUserInfo? UserInfo { get; } public BTCPayAppClient GetClient(string? baseUri = null); public Task CheckAuthenticated(bool refreshUser = false); public Task IsAuthorized(string policy, object? resource = null); @@ -19,17 +18,12 @@ public interface IAccountManager public Task ResetPassword(string serverUrl, string email, string? resetCode, string? newPassword, CancellationToken? cancellation = default); public Task> ChangePassword(string currentPassword, string newPassword, CancellationToken? cancellation = default); public Task> ChangeAccountInfo(string email, string? name, string? imageUrl, CancellationToken? cancellation = default); - public Task SetCurrentStoreId(string storeId); - public Task UnsetCurrentStore(); + public Task SetCurrentStoreId(string? storeId); public AppUserStoreInfo? GetCurrentStore(); - public AppUserStoreInfo? GetUserStore(string storeId); public Task EnsureStorePos(AppUserStoreInfo store, bool? forceCreate = false); public Task Logout(); - public Task UpdateAccount(BTCPayAccount account); - public Task RemoveAccount(BTCPayAccount account); public AsyncEventHandler? OnBeforeAccountChange { get; set; } public AsyncEventHandler? OnAfterAccountChange { get; set; } - public AsyncEventHandler? OnAccountInfoChange { get; set; } public AsyncEventHandler? OnUserInfoChange { get; set; } public AsyncEventHandler? OnBeforeStoreChange { get; set; } public AsyncEventHandler? OnAfterStoreChange { get; set; } diff --git a/BTCPayApp.Core/BTCPayAccount.cs b/BTCPayApp.Core/BTCPayAccount.cs index ff04fc39..ae7ecd99 100644 --- a/BTCPayApp.Core/BTCPayAccount.cs +++ b/BTCPayApp.Core/BTCPayAccount.cs @@ -1,24 +1,12 @@ namespace BTCPayApp.Core; -public class BTCPayAccount(string baseUri, string email) +public class BTCPayAccount(string baseUri, string email, string? accessToken = null) { - public static string GetId(string baseUri, string email) => $"{new Uri(baseUri).Host}:{email}"; - public readonly string Id = GetId(baseUri, email); + public const string Key = "account"; + public string Id { get; private set; } = $"{new Uri(baseUri).Host}:{email}"; public string BaseUri { get; private set; } = WithTrailingSlash(baseUri); public string Email { get; private set; } = email; - public string? AccessToken { get; set; } - public string? Name { get; set; } - public string? ImageUrl { get; set; } - - // TODO: Store this separately - public string? CurrentStoreId { get; set; } - - public void SetInfo(string email, string? name, string? imageUrl) - { - Email = email; - Name = name; - ImageUrl = imageUrl; - } + public string? AccessToken { get; set; } = accessToken; private static string WithTrailingSlash(string s) { diff --git a/BTCPayApp.Core/BTCPayAppConfig.cs b/BTCPayApp.Core/BTCPayAppConfig.cs index e5f2bc37..d33ed43b 100644 --- a/BTCPayApp.Core/BTCPayAppConfig.cs +++ b/BTCPayApp.Core/BTCPayAppConfig.cs @@ -2,6 +2,7 @@ namespace BTCPayApp.Core; public class BTCPayAppConfig { - public const string Key = "AppConfig"; + public const string Key = "appconfig"; public string? Passcode { get; set; } + public string? CurrentStoreId { get; set; } } diff --git a/BTCPayApp.Core/BTCPayServer/BTCPayConnectionManager.cs b/BTCPayApp.Core/BTCPayServer/BTCPayConnectionManager.cs index b850c3fe..2ea48e7d 100644 --- a/BTCPayApp.Core/BTCPayServer/BTCPayConnectionManager.cs +++ b/BTCPayApp.Core/BTCPayServer/BTCPayConnectionManager.cs @@ -115,7 +115,7 @@ private async Task OnConnectionChanged(object? sender, (BTCPayConnectionState Ol var newState = e.New; try { - var account = accountManager.GetAccount(); + var account = accountManager.Account; switch (e.New) { case BTCPayConnectionState.Init: @@ -144,7 +144,7 @@ private async Task OnConnectionChanged(object? sender, (BTCPayConnectionState Ol .WithUrl(url, options => { options.AccessTokenProvider = () => - Task.FromResult(accountManager.GetAccount()?.AccessToken); + Task.FromResult(accountManager.Account?.AccessToken); options.HttpMessageHandlerFactory = serviceProvider .GetService>(); options.WebSocketConfiguration = @@ -202,6 +202,12 @@ private async Task OnConnectionChanged(object? sender, (BTCPayConnectionState Ol await syncService.SyncToLocal(); } newState = BTCPayConnectionState.ConnectedFinishedInitialSync; + + var config = await configProvider.Get(BTCPayAppConfig.Key); + if (!string.IsNullOrEmpty(config?.CurrentStoreId)) + { + await accountManager.SetCurrentStoreId(config.CurrentStoreId); + } } break; case BTCPayConnectionState.ConnectedFinishedInitialSync: diff --git a/BTCPayApp.Core/Backup/SyncService.cs b/BTCPayApp.Core/Backup/SyncService.cs index 15f7ba16..de73fe69 100644 --- a/BTCPayApp.Core/Backup/SyncService.cs +++ b/BTCPayApp.Core/Backup/SyncService.cs @@ -138,7 +138,7 @@ await api.PutObjectAsync(new PutObjectRequest private Task GetUnencryptedVSSAPI() { - var account = accountManager.GetAccount(); + var account = accountManager.Account; if (account is null) throw new InvalidOperationException("Account not found"); var vssUri = new Uri(new Uri(account.BaseUri), "vss/"); diff --git a/BTCPayApp.Core/Helpers/StoreHelpers.cs b/BTCPayApp.Core/Helpers/StoreHelpers.cs index 75251d80..3121e89f 100644 --- a/BTCPayApp.Core/Helpers/StoreHelpers.cs +++ b/BTCPayApp.Core/Helpers/StoreHelpers.cs @@ -26,7 +26,7 @@ public static class StoreHelpers OnChainWalletManager onChainWalletManager, LightningNodeManager lightningNodeService, bool applyOnchain, bool applyLighting) { var storeId = accountManager.GetCurrentStore()?.Id; - var userId = accountManager.GetUserInfo()?.UserId; + var userId = accountManager.UserInfo?.UserId; var config = await onChainWalletManager.GetConfig(); if (// are user and store present? string.IsNullOrEmpty(userId) || diff --git a/BTCPayApp.Tests/CoreTests.cs b/BTCPayApp.Tests/CoreTests.cs index 58d65a65..2bd2e649 100644 --- a/BTCPayApp.Tests/CoreTests.cs +++ b/BTCPayApp.Tests/CoreTests.cs @@ -53,7 +53,7 @@ public async Task CanStartAppCore() Assert.False(await node.AuthStateProvider.CheckAuthenticated()); Assert.True((await node.AccountManager.Login(btcpayUri.AbsoluteUri, username, username, null)).Succeeded); Assert.True(await node.AuthStateProvider.CheckAuthenticated()); - Assert.NotNull(node.AccountManager.GetAccount()?.AccessToken); + Assert.NotNull(node.AccountManager.Account?.AccessToken); TestUtils.Eventually(() => Assert.Equal(BTCPayConnectionState.ConnectedAsMaster, node.ConnectionManager.ConnectionState), 30_000); @@ -68,7 +68,7 @@ public async Task CanStartAppCore() TestUtils.Eventually(() => Assert.Equal(LightningNodeState.WaitingForConnection, node.LNManager.State)); Assert.True((await node.AccountManager.Login(btcpayUri.AbsoluteUri, username, username, null)).Succeeded); Assert.True(await node.AuthStateProvider.CheckAuthenticated()); - Assert.NotNull(node.AccountManager.GetAccount()?.AccessToken); + Assert.NotNull(node.AccountManager.Account?.AccessToken); TestUtils.Eventually(() => Assert.Equal(OnChainWalletState.NotConfigured, node.OnChainWalletManager.State)); TestUtils.Eventually(() => Assert.Equal(LightningNodeState.WaitingForConnection, node.LNManager.State)); diff --git a/BTCPayApp.UI/App.razor b/BTCPayApp.UI/App.razor index 96368995..16c0b067 100644 --- a/BTCPayApp.UI/App.razor +++ b/BTCPayApp.UI/App.razor @@ -2,6 +2,7 @@ @using BTCPayApp.UI.Features @using BTCPayApp.UI.Pages @using BTCPayApp.Core.Contracts +@using BTCPayApp.UI.Pages.SignedOut @inherits Fluxor.Blazor.Web.Components.FluxorComponent @inject IDispatcher Dispatcher @inject IAccountManager AccountManager @@ -40,7 +41,7 @@ // signed in if (AuthState == null) return; var authState = await AuthState; - var account = AccountManager.GetAccount(); + var account = AccountManager.Account; if (authState.User.Identity?.IsAuthenticated is true && !string.IsNullOrEmpty(account?.BaseUri)) { Dispatcher.Dispatch(new UIState.FetchInstanceInfo(account.BaseUri)); @@ -63,7 +64,7 @@ - + diff --git a/BTCPayApp.UI/Components/SetupStatus.razor b/BTCPayApp.UI/Components/SetupStatus.razor index 7447ad2e..f209359b 100644 --- a/BTCPayApp.UI/Components/SetupStatus.razor +++ b/BTCPayApp.UI/Components/SetupStatus.razor @@ -49,7 +49,7 @@ } public SetupState SetupStateAccount() { - return string.IsNullOrEmpty(AccountManager.GetAccount()?.CurrentStoreId) ? SetupState.Pending : SetupState.Completed; + return string.IsNullOrEmpty(AccountManager.GetCurrentStore()?.Id) ? SetupState.Pending : SetupState.Completed; } public SetupState SetupStateOnchain() { diff --git a/BTCPayApp.UI/Components/UserSwitch.razor b/BTCPayApp.UI/Components/UserSwitch.razor index 0a01376e..2b789ba0 100644 --- a/BTCPayApp.UI/Components/UserSwitch.razor +++ b/BTCPayApp.UI/Components/UserSwitch.razor @@ -1,114 +1,88 @@ -@using BTCPayApp.Core @using BTCPayApp.Core.Auth +@using BTCPayApp.Core.Models @using BTCPayApp.UI.Models @using BTCPayServer.Client.Models @inject IJSRuntime JS @inject IAccountManager AccountManager @inject NavigationManager NavigationManager -@implements IDisposable
- @if (Users?.Count() > 1) - { - - - @code { - private BTCPayAccount? _account; private bool _sending; private string? _errorMessage; private LoginModel? Model { get; set; } - [Parameter] - public IEnumerable? Users { get; set; } - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - AccountManager.OnAccountInfoChange += OnAccountInfoChange; - - _account = AccountManager.GetAccount(); - } - - public void Dispose() - { - AccountManager.OnAccountInfoChange -= OnAccountInfoChange; - } + [Parameter, EditorRequired] + public required AppUserInfo CurrentUser { get; set; } - private async Task OnAccountInfoChange(object? sender, BTCPayAccount? account) - { - _account = account; - await InvokeAsync(StateHasChanged); - } + [Parameter, EditorRequired] + public required IEnumerable Users { get; set; } private Task SwitchToUser(StoreUserData user) { @@ -118,7 +92,7 @@ //AccountManager.OnBeforeAccountChange += OnBeforeAccountChange; } - private async Task OnBeforeAccountChange(object? sender, BTCPayAccount? previousAccount) + /*private async Task OnBeforeAccountChange(object? sender, BTCPayAccount? previousAccount) { await JS.InvokeVoidAsync("Interop.closeModal", "#UserSwitchModal"); } @@ -144,5 +118,5 @@ ? string.Join(",", result.Messages) : "Invalid login attempt."; } - } + }*/ } diff --git a/BTCPayApp.UI/Features/RootState.cs b/BTCPayApp.UI/Features/RootState.cs index b4305de6..ae825fd4 100644 --- a/BTCPayApp.UI/Features/RootState.cs +++ b/BTCPayApp.UI/Features/RootState.cs @@ -22,12 +22,10 @@ public class ConnectionEffects(NavigationManager navigationManager) [EffectMethod] public Task HandleConnectionStateUpdatedAction(RootState.ConnectionStateUpdatedAction action, IDispatcher dispatcher) { - if(action.State == BTCPayConnectionState.WaitingForEncryptionKey) + if (action.State == BTCPayConnectionState.WaitingForEncryptionKey) { - dispatcher.Dispatch(new GoAction(navigationManager.ToAbsoluteUri(Routes.EncryptionKey).ToString())); } - return Task.CompletedTask; } } diff --git a/BTCPayApp.UI/Features/UserState.cs b/BTCPayApp.UI/Features/UserState.cs index cc634fcf..2a3e120e 100644 --- a/BTCPayApp.UI/Features/UserState.cs +++ b/BTCPayApp.UI/Features/UserState.cs @@ -10,10 +10,7 @@ public record UserState { public RemoteData? Info; - public record FetchInfo( - TaskCompletionSource TaskCompletionSource, - CancellationToken CancellationToken = default); - + public record FetchInfo; public record SetInfo(AppUserInfo? Info, string? Error); public record UpdateUser(UpdateApplicationUserRequest Request); public record UpdatedUser(ApplicationUserData? UserData, string? Error); @@ -91,14 +88,12 @@ public async Task FetchInfoEffect(FetchInfo action, IDispatcher dispatcher) { try { - var info = await accountManager.GetClient().GetUserInfo(action.CancellationToken); - action.TaskCompletionSource.SetResult(info); + var info = await accountManager.GetClient().GetUserInfo(); dispatcher.Dispatch(new SetInfo(info, null)); } catch (Exception e) { var error = e.InnerException?.Message ?? e.Message; - action.TaskCompletionSource.SetResult(null); dispatcher.Dispatch(new SetInfo(null, error)); } } diff --git a/BTCPayApp.UI/Pages/CheckoutPage.razor b/BTCPayApp.UI/Pages/CheckoutPage.razor index a2d7475c..18883acc 100644 --- a/BTCPayApp.UI/Pages/CheckoutPage.razor +++ b/BTCPayApp.UI/Pages/CheckoutPage.razor @@ -20,5 +20,5 @@ private bool _iframeLoaded; - private string? CheckoutUrl => string.IsNullOrEmpty(InvoiceId) ? null : $"{AccountManager.GetAccount()!.BaseUri}i/{InvoiceId}"; + private string? CheckoutUrl => string.IsNullOrEmpty(InvoiceId) ? null : $"{AccountManager.Account!.BaseUri}i/{InvoiceId}"; } diff --git a/BTCPayApp.UI/Pages/InvoicePage.razor b/BTCPayApp.UI/Pages/InvoicePage.razor index a467b3f4..55a6509d 100644 --- a/BTCPayApp.UI/Pages/InvoicePage.razor +++ b/BTCPayApp.UI/Pages/InvoicePage.razor @@ -338,7 +338,7 @@ private InvoicePaymentMethodDataModel[]? PaymentMethods => !string.IsNullOrEmpty(InvoiceId) ? StoreState.Value.GetInvoicePaymentMethods(InvoiceId!)?.Data : null; private bool PaymentMethodsLoading => !string.IsNullOrEmpty(InvoiceId) && StoreState.Value.GetInvoicePaymentMethods(InvoiceId!)?.Loading is true; private string? PaymentMethodsError => !string.IsNullOrEmpty(InvoiceId) ? StoreState.Value.GetInvoicePaymentMethods(InvoiceId!)?.Error : null; - private string? ReceiptUrl => Invoice is { Status: InvoiceStatus.Settled, Receipt.Enabled: true } ? $"{AccountManager.GetAccount()!.BaseUri}i/{InvoiceId}/receipt" : null; + private string? ReceiptUrl => Invoice is { Status: InvoiceStatus.Settled, Receipt.Enabled: true } ? $"{AccountManager.Account!.BaseUri}i/{InvoiceId}/receipt" : null; private List? Payments => PaymentMethods?.SelectMany(pm => pm.Payments.Select(p => new PaymentViewModel { Payment = p, PaymentMethod = pm })).ToList(); private class PaymentViewModel diff --git a/BTCPayApp.UI/Pages/LoadingPage.razor b/BTCPayApp.UI/Pages/LoadingPage.razor deleted file mode 100644 index 4bb877fb..00000000 --- a/BTCPayApp.UI/Pages/LoadingPage.razor +++ /dev/null @@ -1,20 +0,0 @@ -@attribute [Route(Routes.Loading)] -@attribute [AllowAnonymous] - -@Title - -
- - @if (!string.IsNullOrEmpty(Message)) - { -
@Message
- } -
- -@code { - [Parameter, EditorRequired, SupplyParameterFromQuery] - public string Title { get; set; } = null!; - - [Parameter, SupplyParameterFromQuery] - public string? Message { get; set; } -} diff --git a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor index a882f344..0a3306d9 100644 --- a/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor +++ b/BTCPayApp.UI/Pages/Settings/ChangePasscodePage.razor @@ -77,7 +77,7 @@ public async Task HandleValidSubmit() { _config!.Passcode = Model.NewPasscode; - await ConfigProvider.Set(BTCPayAppConfig.Key, _config, false); + await ConfigProvider.Set(BTCPayAppConfig.Key, _config, true); NavigationManager.NavigateTo(Routes.Settings); } diff --git a/BTCPayApp.UI/Pages/Settings/EncryptionKey.razor b/BTCPayApp.UI/Pages/Settings/EncryptionKey.razor index 2498318f..e69fb480 100644 --- a/BTCPayApp.UI/Pages/Settings/EncryptionKey.razor +++ b/BTCPayApp.UI/Pages/Settings/EncryptionKey.razor @@ -36,7 +36,7 @@
- + diff --git a/BTCPayApp.UI/Pages/Settings/IndexPage.razor b/BTCPayApp.UI/Pages/Settings/IndexPage.razor index eda5c5e5..f03248e0 100644 --- a/BTCPayApp.UI/Pages/Settings/IndexPage.razor +++ b/BTCPayApp.UI/Pages/Settings/IndexPage.razor @@ -15,7 +15,6 @@ @inject ConfigProvider ConfigProvider @inject IAccountManager AccountManager @inject IFingerprint Fingerprint -@inject IState UIState @inject IState UserState @inject IState StoreState @inject BTCPayConnectionManager ConnectionManager @@ -41,9 +40,19 @@
} -
- -
+ @if (CurrentUser != null) + { +
+ @if (StoreUsers?.Count() > 1 && HasPasscode) + { + + } + else + { + + } +
+ }