diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs new file mode 100644 index 0000000000..12b91b9ac0 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs @@ -0,0 +1,157 @@ +//+:cnd:noEmit +using Microsoft.Extensions.Logging; +//#if (signalr == true) +using Microsoft.AspNetCore.SignalR.Client; +//#endif + +namespace Boilerplate.Client.Core.Components; + +public partial class AppInitializer : AppComponentBase +{ + //#if (signalr == true) + private HubConnection? hubConnection; + //#endif + + [AutoInject] private MessageBoxService messageBoxService = default!; + [AutoInject] private AuthenticationManager authManager = default!; + [AutoInject] private IJSRuntime jsRuntime = default!; + [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; + [AutoInject] private IStorageService storageService = default!; + [AutoInject] private CultureInfoManager cultureInfoManager = default!; + [AutoInject] private ILogger authLogger = default!; + + protected async override Task OnInitAsync() + { + AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged; + + AuthenticationStateChanged(AuthenticationManager.GetAuthenticationStateAsync()); + + if (AppPlatform.IsBlazorHybrid) + { + if (CultureInfoManager.MultilingualEnabled) + { + cultureInfoManager.SetCurrentCulture(await storageService.GetItem("Culture") ?? // 1- User settings + CultureInfo.CurrentUICulture.Name); // 2- OS settings + } + + await SetupBodyClasses(); + } + + await base.OnInitAsync(); + } + + protected override async Task OnAfterFirstRenderAsync() + { + await base.OnAfterFirstRenderAsync(); + + if (AppPlatform.IsBlazorHybrid is false) + { + AppPlatform.OSDescription = await jsRuntime.GetBrowserPlatform(); + } + } + + private async void AuthenticationStateChanged(Task task) + { + try + { + var user = (await AuthenticationStateTask).User; + + var (isUserAuthenticated, userId, userName, email, sessionId) = user.IsAuthenticated() ? (user.IsAuthenticated(), user.GetUserId().ToString(), user.GetUserName(), user.GetEmail(), user.GetSessionId()) : default; + + LogAuthenticationState(authLogger, isUserAuthenticated, userId, userName, email, sessionId); + + //#if (signalr == true) + if (InPrerenderSession is false) + { + await ConnectSignalR(); + } + //#endif + } + catch (Exception exp) + { + ExceptionHandler.Handle(exp); + } + } + + [LoggerMessage(Level = LogLevel.Information, Message = "Authentication State: {IsUserAuthenticated}, {UserId}, {UserName}, {Email}, {UserSessionId}")] + private static partial void LogAuthenticationState(ILogger logger, bool isUserAuthenticated, string userId, string userName, string? email, string? userSessionId); + + //#if (signalr == true) + private async Task ConnectSignalR() + { + if (hubConnection is not null) + { + await hubConnection.DisposeAsync(); + } + + var access_token = await AuthTokenProvider.GetAccessTokenAsync(); + + hubConnection = new HubConnectionBuilder() + .WithUrl($"{Configuration.GetServerAddress()}/app-hub?access_token={access_token}") + .Build(); + + hubConnection.On("TwoFactorToken", async (token) => + { + await messageBoxService.Show(Localizer[nameof(AppStrings.TwoFactorTokenPushText), token]); + + // The following code block is not required for Bit.BlazorUI components to perform UI changes. However, it may be necessary in other scenarios. + /*await InvokeAsync(async () => + { + StateHasChanged(); + });*/ + + // You can also leverage IPubSubService to notify other components in the application. + }); + + await hubConnection.StartAsync(CurrentCancellationToken); + } + //#endif + + private async Task SetupBodyClasses() + { + var cssClasses = new List { }; + + if (AppPlatform.IsWindows) + { + cssClasses.Add("bit-windows"); + } + else if (AppPlatform.IsMacOS) + { + cssClasses.Add("bit-macos"); + } + else if (AppPlatform.IsIOS) + { + cssClasses.Add("bit-ios"); + } + else if (AppPlatform.IsAndroid) + { + cssClasses.Add("bit-android"); + } + + var cssVariables = new Dictionary(); + var statusBarHeight = bitDeviceCoordinator.GetStatusBarHeight(); + + if (AppPlatform.IsMacOS is false) + { + //For iOS this is handled in css using safe-area env() variables + //For Android there's an issue with keyboard in fullscreen mode. more info: https://github.com/bitfoundation/bitplatform/issues/5626 + //For Windows there's an issue with TitleBar. more info: https://github.com/bitfoundation/bitplatform/issues/5695 + statusBarHeight = 0; + } + + cssVariables.Add("--bit-status-bar-height", $"{statusBarHeight.ToString("F3", CultureInfo.InvariantCulture)}px"); + await jsRuntime.ApplyBodyElementClasses(cssClasses, cssVariables); + } + + //#if (signalr == true) + protected override async ValueTask DisposeAsync(bool disposing) + { + if (hubConnection is not null) + { + await hubConnection.DisposeAsync(); + } + + await base.DisposeAsync(disposing); + } + //#endif +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AuthenticationStateLogger.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AuthenticationStateLogger.cs deleted file mode 100644 index e0656fc9c5..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AuthenticationStateLogger.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace Boilerplate.Client.Core.Components; - -public partial class AuthenticationStateLogger : AppComponentBase -{ - [AutoInject] private ILogger authLogger = default!; - - protected async override Task OnInitAsync() - { - AuthenticationManager.AuthenticationStateChanged += LogAuthenticationState; - - LogAuthenticationState(AuthenticationManager.GetAuthenticationStateAsync()); - - await base.OnInitAsync(); - } - - private async void LogAuthenticationState(Task task) - { - try - { - var user = (await AuthenticationStateTask).User; - - var (isUserAuthenticated, userId, userName, email, sessionId) = user.IsAuthenticated() ? (user.IsAuthenticated(), user.GetUserId().ToString(), user.GetUserName(), user.GetEmail(), user.GetSessionId()) : default; - - LogAuthenticationState(authLogger, isUserAuthenticated, userId, userName, email, sessionId); - } - catch (Exception exp) - { - ExceptionHandler.Handle(exp); - } - } - - [LoggerMessage(Level = LogLevel.Information, Message = "Authentication State: {IsUserAuthenticated}, {UserId}, {UserName}, {Email}, {UserSessionId}")] - private static partial void LogAuthenticationState(ILogger logger, bool isUserAuthenticated, string userId, string userName, string? email, string? userSessionId); -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs index 3641291d7e..3ddecd0929 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs @@ -22,7 +22,7 @@ protected override async Task OnInitializedAsync() { try { - authManager.AuthenticationStateChanged += IsUserAuthenticated; + authManager.AuthenticationStateChanged += AuthenticationStateChanged; isUserAuthenticated = await prerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); @@ -64,7 +64,7 @@ private void ToggleMenuHandler() isMenuOpen = !isMenuOpen; } - private async void IsUserAuthenticated(Task task) + private async void AuthenticationStateChanged(Task task) { try { @@ -91,7 +91,7 @@ protected virtual void Dispose(bool disposing) { if (disposed || disposing is false) return; - authManager.AuthenticationStateChanged -= IsUserAuthenticated; + authManager.AuthenticationStateChanged -= AuthenticationStateChanged; unsubscribeCultureChange(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor index 50df2ccaf5..4ecef90a33 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor @@ -38,8 +38,4 @@
- - -@*#if (signalr == true)*@ - -@*#endif*@ \ No newline at end of file + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor.cs index 0ed3dd6c17..21aa017abb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Profile/ProfilePage.razor.cs @@ -1,7 +1,4 @@ //+:cnd:noEmit -//#if (signalr == true) -using Microsoft.AspNetCore.SignalR.Client; -//#endif using Boilerplate.Shared.Dtos.Identity; using Boilerplate.Shared.Controllers.Identity; @@ -12,10 +9,6 @@ public partial class ProfilePage { private UserDto? user; private bool isLoading; - //#if (signalr == true) - private HubConnection? hubConnection; - private BitSnackBar snackBar = default!; - //#endif [AutoInject] private IUserController userController = default!; @@ -35,40 +28,4 @@ protected override async Task OnInitAsync() await base.OnInitAsync(); } - - //#if (signalr == true) - protected async override Task OnAfterFirstRenderAsync() - { - await base.OnAfterFirstRenderAsync(); - - var access_token = await AuthTokenProvider.GetAccessTokenAsync(); - - hubConnection = new HubConnectionBuilder() - .WithUrl($"{Configuration.GetServerAddress()}/identity-hub?access_token={access_token}") - .Build(); - - hubConnection.On("NewUserSession", async (userSession) => - { - await snackBar.Info(Localizer[nameof(AppStrings.NewUserSessionSnackbarTitle)], Localizer[nameof(AppStrings.DeviceDetails), userSession.Device!]); - - // The following code block is not required for Bit.BlazorUI components to perform UI changes. However, it may be necessary in other scenarios. - /*await InvokeAsync(async () => - { - StateHasChanged(); - });*/ - }); - - await hubConnection.StartAsync(); - } - - protected override async ValueTask DisposeAsync(bool disposing) - { - if (hubConnection is not null) - { - await hubConnection.DisposeAsync(); - } - - await base.DisposeAsync(disposing); - } - //#endif } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor index 40eacae7b0..03ef42a6f1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor @@ -1,5 +1,5 @@  - + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs index df4497a517..e6cdbad4b5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs @@ -2,73 +2,6 @@ public partial class Routes { - [AutoInject] IJSRuntime jsRuntime = default!; - [AutoInject] IBitDeviceCoordinator bitDeviceCoordinator = default!; - [AutoInject] IStorageService storageService = default!; - [AutoInject] CultureInfoManager cultureInfoManager = default!; - - protected override async Task OnInitializedAsync() - { - if (AppPlatform.IsBlazorHybrid) - { - if (CultureInfoManager.MultilingualEnabled) - { - cultureInfoManager.SetCurrentCulture(await storageService.GetItem("Culture") ?? // 1- User settings - CultureInfo.CurrentUICulture.Name); // 2- OS settings - } - - await SetupBodyClasses(); - } - - await base.OnInitializedAsync(); - } - - private async Task SetupBodyClasses() - { - var cssClasses = new List { }; - - if (AppPlatform.IsWindows) - { - cssClasses.Add("bit-windows"); - } - else if (AppPlatform.IsMacOS) - { - cssClasses.Add("bit-macos"); - } - else if (AppPlatform.IsIOS) - { - cssClasses.Add("bit-ios"); - } - else if (AppPlatform.IsAndroid) - { - cssClasses.Add("bit-android"); - } - - var cssVariables = new Dictionary(); - var statusBarHeight = bitDeviceCoordinator.GetStatusBarHeight(); - - if (AppPlatform.IsMacOS is false) - { - //For iOS this is handled in css using safe-area env() variables - //For Android there's an issue with keyboard in fullscreen mode. more info: https://github.com/bitfoundation/bitplatform/issues/5626 - //For Windows there's an issue with TitleBar. more info: https://github.com/bitfoundation/bitplatform/issues/5695 - statusBarHeight = 0; - } - - cssVariables.Add("--bit-status-bar-height", $"{statusBarHeight.ToString("F3", CultureInfo.InvariantCulture)}px"); - await jsRuntime.ApplyBodyElementClasses(cssClasses, cssVariables); - } - - protected async override Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - - if (firstRender && AppPlatform.IsBlazorHybrid is false) - { - AppPlatform.OSDescription = await jsRuntime.GetBrowserPlatform(); - } - } - [AutoInject] NavigationManager? navigationManager { set => universalLinksNavigationManager = value; get => universalLinksNavigationManager; } public static NavigationManager? universalLinksNavigationManager; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs index e6a0dc4754..d406468ad0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs @@ -24,7 +24,7 @@ public partial class IdentityController : AppControllerBase, IIdentityController [AutoInject] private IOptionsMonitor bearerTokenOptions = default!; [AutoInject] private IUserClaimsPrincipalFactory userClaimsPrincipalFactory = default!; //#if (signalr == true) - [AutoInject] private IHubContext identityHubContext = default!; + [AutoInject] private IHubContext appHubContext = default!; //#endif //#if (captcha == "reCaptcha") @@ -146,10 +146,6 @@ public async Task SignIn(SignInRequestDto request, CancellationToken cancellatio var addUserSessionResult = await userManager.UpdateAsync(user); if (addUserSessionResult.Succeeded is false) throw new ResourceValidationException(addUserSessionResult.Errors.Select(e => new LocalizedString(e.Code, e.Description)).ToArray()); - - //#if (signalr == true) - await identityHubContext.Clients.User(user.Id.ToString()).SendAsync("NewUserSession", userSession, cancellationToken); - //#endif } /// @@ -301,6 +297,10 @@ async Task SendSms() } } + //#if (signalr == true) + await appHubContext.Clients.User(user.Id.ToString()).SendAsync("TwoFactorToken", token, cancellationToken); + //#endif + await Task.WhenAll([SendEmail(), SendSms()]); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/IdentityHub.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/AppHub.cs similarity index 62% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/IdentityHub.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/AppHub.cs index 8e0b4a8420..d9ec736bb1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/IdentityHub.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Hubs/AppHub.cs @@ -2,7 +2,7 @@ namespace Boilerplate.Server.Api.Hubs; -[Authorize] -public partial class IdentityHub : Hub +[AllowAnonymous] +public partial class AppHub : Hub { } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs index 51d3e9bd57..8a8cb45acb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Middlewares.cs @@ -65,7 +65,7 @@ public static void ConfiureMiddlewares(this WebApplication app) }).WithTags("Test"); //#if (signalr == true) - app.MapHub("/identity-hub"); + app.MapHub("/app-hub"); //#endif app.MapControllers().RequireAuthorization(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs index bfc0d73bc2..bd5ad8d2f0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Middlewares.cs @@ -113,7 +113,7 @@ private static void ConfiureMiddlewares(this WebApplication app) }).WithTags("Test"); //#if (signalr == true) - app.MapHub("/identity-hub"); + app.MapHub("/app-hub"); //#endif app.MapControllers().RequireAuthorization(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx index 2d56f236dd..3ea89ee495 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx @@ -235,11 +235,8 @@ - - لاگین جدید - - - دستگاه: {0} + + {0} توکن احراز هویت مرحله دو شما است در Boilerplate. diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fr.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fr.resx index 5ac85305ba..7282eacc82 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fr.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fr.resx @@ -235,11 +235,8 @@ - - Nouvelle session ! - - - Appareil : {0} + + {0} est votre jeton à deux facteurs dans Boilerplate. diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx index c33e8c1c08..67a6d8a7b2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx @@ -235,11 +235,8 @@ - - New session! - - - Device: {0} + + {0} is your two factor token in Boilerplate. diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json index 4862199055..79f45d7e26 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json @@ -2,14 +2,14 @@ "Logging": { "LogLevel": { "Default": "Warning", - "Boilerplate.Client.Core.Components.AuthenticationStateLogger": "Information", + "Boilerplate.Client.Core.Services.AuthenticationManager": "Information", "Microsoft.EntityFrameworkCore.Database.Command": "Information" }, //#if (appCenter == true) "AppCenterLoggerProvider": { "LogLevel": { "Default": "Warning", - "Boilerplate.Client.Core.Components.AuthenticationStateLogger": "Information", + "Boilerplate.Client.Core.Services.AuthenticationManager": "Information", "Microsoft.EntityFrameworkCore.Database.Command": "Information" } }, @@ -18,7 +18,7 @@ "ApplicationInsights": { "LogLevel": { "Default": "Warning", - "Boilerplate.Client.Core.Components.AuthenticationStateLogger": "Information", + "Boilerplate.Client.Core.Services.AuthenticationManager": "Information", "Microsoft.EntityFrameworkCore.Database.Command": "Information" } }