diff --git a/src/Koogle.Application/DTOs/AuthDto.cs b/src/Koogle.Application/DTOs/AuthDto.cs index 8065a97..9fa7392 100644 --- a/src/Koogle.Application/DTOs/AuthDto.cs +++ b/src/Koogle.Application/DTOs/AuthDto.cs @@ -52,6 +52,15 @@ namespace Koogle.Application.DTOs public string ConfirmPassword { get; set; } = ""; } + /// + /// From DTO for switching session from one club to another + /// + public class SwitchClubFormDto + { + public Guid UserProfileId { get; set; } + + public Guid ClubId { get; set; } + } /// /// DTO for user registration. /// diff --git a/src/Koogle.Application/Services/UserService.cs b/src/Koogle.Application/Services/UserService.cs index 04489f2..c75f326 100644 --- a/src/Koogle.Application/Services/UserService.cs +++ b/src/Koogle.Application/Services/UserService.cs @@ -709,6 +709,22 @@ public class UserService : IUserService profile.CurrentClubId = clubId; await _appDb.SaveChangesAsync(ct); + + // Refresh signin to update claims with new club + var identityUser = await _userManager.FindByIdAsync(profile.IdentityUserId.ToString()); + if (identityUser != null) + { + try + { + await _signInManager.RefreshSignInAsync(identityUser); + } + catch (Exception e) + { + + throw; + } + } + return true; } diff --git a/src/Koogle.Web/Components/Shared/ClubSwitcher.razor b/src/Koogle.Web/Components/Shared/ClubSwitcher.razor index 7d1af7c..64f962c 100644 --- a/src/Koogle.Web/Components/Shared/ClubSwitcher.razor +++ b/src/Koogle.Web/Components/Shared/ClubSwitcher.razor @@ -1,10 +1,16 @@ +@using System.Net @using Fluxor +@using Koogle.Application.DTOs @using Koogle.Web.Store.AuthState @using Koogle.Application.Interfaces @inject IState AuthState @inject NavigationManager NavigationManager @inject IUserService UserService +@inject HttpClient HttpClient; +@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery +@inject IHttpContextAccessor HttpContextAccessor +@inject IDispatcher Dispatcher @inherits Fluxor.Blazor.Web.Components.FluxorComponent @@ -22,10 +28,10 @@ @foreach (var club in AuthState.Value.AvailableClubs) { + Disabled="@(club.ClubId == AuthState.Value.CurrentClub?.ClubId)"> @if (club.ClubId == AuthState.Value.CurrentClub?.ClubId) { - + } @club.ClubName @@ -47,23 +53,64 @@ else if (AuthState.Value.IsAuthenticated && AuthState.Value.HasNoClub) } @code { + private string _antiToken; + + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + var http = HttpContextAccessor.HttpContext!; + var tokens = Antiforgery.GetAndStoreTokens(http); + _antiToken = tokens.RequestToken!; + } + } + private async Task SwitchClubAsync(Guid clubId) { if (AuthState.Value.CurrentUser == null) return; + var model = new SwitchClubFormDto() + { + ClubId = clubId, + UserProfileId = AuthState.Value.CurrentUser.ProfileId + }; + try { - var success = await UserService.SwitchClubAsync(AuthState.Value.CurrentUser.ProfileId, clubId); - if (success) - { - // Force page reload to refresh claims - NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); - } + HttpClient.DefaultRequestHeaders.Remove("RequestVerificationToken"); + HttpClient.DefaultRequestHeaders.Add( + "RequestVerificationToken", + _antiToken + ); + + var basepath = NavigationManager.BaseUri; + var url = $"{basepath}auth/switch-club"; + await HttpClient.PostAsJsonAsync(url, model); + + // Dispatcher.Dispatch(new AuthState.InitializeAuthSuccessAction(model.UserProfileId, model.ClubId, roles)); + + + NavigationManager.NavigateTo("/dashboard", forceLoad: true); } - catch (Exception ex) + catch (Exception e) { - Console.WriteLine($"Club switch failed: {ex.Message}"); + Console.WriteLine(e); + throw; } + + // try + // { + // var success = await UserService.SwitchClubAsync(AuthState.Value.CurrentUser.ProfileId, clubId); + // if (success) + // { + // // Force page reload to refresh claims + // NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + // } + // } + // catch (Exception ex) + // { + // Console.WriteLine($"Club switch failed: {ex.Message}"); + // } } } diff --git a/src/Koogle.Web/Controllers/AuthController.cs b/src/Koogle.Web/Controllers/AuthController.cs index 825d1bc..31b4958 100644 --- a/src/Koogle.Web/Controllers/AuthController.cs +++ b/src/Koogle.Web/Controllers/AuthController.cs @@ -7,6 +7,7 @@ using Koogle.Web.Store.AuthState; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using static QRCoder.PayloadGenerator; namespace Koogle.Web.Controllers { @@ -18,7 +19,8 @@ namespace Koogle.Web.Controllers /// Aus dem Grund werden Login/Logout Aktionen hier im MVC Controller mit einem "normalen" Post-Request abgewickelt. /// [Microsoft.AspNetCore.Mvc.Route("auth")] - public class AuthController(IDispatcher dispatcher, IUserService userService, IEmailService emailService) : Controller + public class AuthController(IDispatcher dispatcher, IUserService userService, IEmailService emailService) + : Controller { private readonly IDispatcher _dispatcher = dispatcher; private readonly IUserService _userService = userService; @@ -37,6 +39,7 @@ namespace Koogle.Web.Controllers // (club-setup if no clubs, dashboard otherwise) return LocalRedirect(result.RedirectUrl ?? "/dashboard"); } + return LocalRedirect("/account/login?error=true"); } @@ -67,8 +70,10 @@ namespace Koogle.Web.Controllers // If invite token was provided, redirect to join page if (!string.IsNullOrWhiteSpace(input.InviteToken)) { - return LocalRedirect($"/account/login?registered=true&invite={Uri.EscapeDataString(input.InviteToken)}"); + return LocalRedirect( + $"/account/login?registered=true&invite={Uri.EscapeDataString(input.InviteToken)}"); } + return LocalRedirect("/account/login?registered=true"); } @@ -93,7 +98,8 @@ namespace Koogle.Web.Controllers if (token != null) { var baseUrl = $"{Request.Scheme}://{Request.Host}"; - var resetUrl = $"{baseUrl}/account/reset-password?email={Uri.EscapeDataString(email)}&token={Uri.EscapeDataString(token)}"; + var resetUrl = + $"{baseUrl}/account/reset-password?email={Uri.EscapeDataString(email)}&token={Uri.EscapeDataString(token)}"; await _emailService.SendPasswordResetEmailAsync(email, resetUrl); } @@ -110,7 +116,8 @@ namespace Koogle.Web.Controllers // Validate password confirmation if (input.NewPassword != input.ConfirmPassword) { - return LocalRedirect($"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error=PasswordMismatch"); + return LocalRedirect( + $"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error=PasswordMismatch"); } var dto = new ResetPasswordDto @@ -127,9 +134,20 @@ namespace Koogle.Web.Controllers } var errors = string.Join(",", result.Errors.Select(e => e.Code)); - return LocalRedirect($"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error={errors}"); + return LocalRedirect( + $"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error={errors}"); + } + + /// + /// Handles switch club. + /// + [HttpPost("switch-club")] + //[ValidateAntiForgeryToken] + public async Task SwitchClub([FromBody] SwitchClubFormDto input) + { + await _userService.SwitchClubAsync(input.UserProfileId, input.ClubId); + return LocalRedirect($"/dashboard"); } } - } diff --git a/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/534a4e93-ceb8-4886-8965-f1e299443ebc.gif b/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/534a4e93-ceb8-4886-8965-f1e299443ebc.gif new file mode 100644 index 0000000..4bd66d6 Binary files /dev/null and b/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/534a4e93-ceb8-4886-8965-f1e299443ebc.gif differ diff --git a/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/a1744438-6fb7-4ef4-8278-24cb841d33f2.gif b/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/a1744438-6fb7-4ef4-8278-24cb841d33f2.gif new file mode 100644 index 0000000..169e2a7 Binary files /dev/null and b/src/Koogle.Web/wwwroot/club-media/demozwei/gifs/a1744438-6fb7-4ef4-8278-24cb841d33f2.gif differ