From 302550fb13d358504382a70ad2cc1c487ffac94a Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 14:32:19 +0200 Subject: [PATCH 1/8] Move login components into namespace --- src/Client/Shared/{ => Login}/LogInButton.razor | 0 src/Client/Shared/{ => Login}/LoginModal.razor | 0 src/Client/Shared/{ => Login}/LoginModal.razor.css | 0 src/Client/_Imports.razor | 2 ++ 4 files changed, 2 insertions(+) rename src/Client/Shared/{ => Login}/LogInButton.razor (100%) rename src/Client/Shared/{ => Login}/LoginModal.razor (100%) rename src/Client/Shared/{ => Login}/LoginModal.razor.css (100%) diff --git a/src/Client/Shared/LogInButton.razor b/src/Client/Shared/Login/LogInButton.razor similarity index 100% rename from src/Client/Shared/LogInButton.razor rename to src/Client/Shared/Login/LogInButton.razor diff --git a/src/Client/Shared/LoginModal.razor b/src/Client/Shared/Login/LoginModal.razor similarity index 100% rename from src/Client/Shared/LoginModal.razor rename to src/Client/Shared/Login/LoginModal.razor diff --git a/src/Client/Shared/LoginModal.razor.css b/src/Client/Shared/Login/LoginModal.razor.css similarity index 100% rename from src/Client/Shared/LoginModal.razor.css rename to src/Client/Shared/Login/LoginModal.razor.css diff --git a/src/Client/_Imports.razor b/src/Client/_Imports.razor index 391a45cc..3cdd4b14 100644 --- a/src/Client/_Imports.razor +++ b/src/Client/_Imports.razor @@ -11,4 +11,6 @@ @using Client.Shared @using Client.Shared.Accordion @using Client.Shared.Lists +@using Client.Shared.Login @using Client.Pages.Contracts + From 1b0a62809c24f40964d14d3ae60a09b0de73f097 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 14:38:11 +0200 Subject: [PATCH 2/8] Refactor login button component to more general --- src/Client/Shared/Login/LogInButton.razor | 13 --------- src/Client/Shared/Login/Login.razor | 35 +++++++++++++++++++++++ src/Client/Shared/Login/Login.razor.css | 3 ++ src/Client/Shared/NavMenu.razor | 13 +-------- src/Client/Shared/NavMenu.razor.css | 4 --- 5 files changed, 39 insertions(+), 29 deletions(-) delete mode 100644 src/Client/Shared/Login/LogInButton.razor create mode 100644 src/Client/Shared/Login/Login.razor create mode 100644 src/Client/Shared/Login/Login.razor.css diff --git a/src/Client/Shared/Login/LogInButton.razor b/src/Client/Shared/Login/LogInButton.razor deleted file mode 100644 index 9c30179d..00000000 --- a/src/Client/Shared/Login/LogInButton.razor +++ /dev/null @@ -1,13 +0,0 @@ -@inject IJSRuntime _js - - - -@code { - private async Task OnClick() - { - // Focuses the username text input element. - await _js.InvokeVoidAsync("focusElement", "#username"); - } -} diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor new file mode 100644 index 00000000..c80adfeb --- /dev/null +++ b/src/Client/Shared/Login/Login.razor @@ -0,0 +1,35 @@ +@using Blazorise.Extensions +@inject IJSRuntime _js + +@if (LoggedInUser.IsNullOrEmpty()) +{ + +} +else +{ +
+

+ Inloggad som: @LoggedInUser +

+
+} +@code { + + /// + /// The name of the currently logged in user. + /// + [CascadingParameter(Name = nameof(LoggedInUser))] + public string? LoggedInUser { get; set; } + + private async Task FocusUsernameField() + { + // Focuses the username text input element. + await _js.InvokeVoidAsync("focusElement", "#username"); + } +} diff --git a/src/Client/Shared/Login/Login.razor.css b/src/Client/Shared/Login/Login.razor.css new file mode 100644 index 00000000..bf109a8a --- /dev/null +++ b/src/Client/Shared/Login/Login.razor.css @@ -0,0 +1,3 @@ +#logged-in { + color: var(--prodigo-white); +} diff --git a/src/Client/Shared/NavMenu.razor b/src/Client/Shared/NavMenu.razor index d4f7e8af..eaaeac78 100644 --- a/src/Client/Shared/NavMenu.razor +++ b/src/Client/Shared/NavMenu.razor @@ -38,18 +38,7 @@ } -
- @if (LoggedInUser.IsNullOrEmpty()) - { - - } - else - { -

- Inloggad som: @LoggedInUser -

- } -
+ diff --git a/src/Client/Shared/NavMenu.razor.css b/src/Client/Shared/NavMenu.razor.css index c9694a97..d1889509 100644 --- a/src/Client/Shared/NavMenu.razor.css +++ b/src/Client/Shared/NavMenu.razor.css @@ -9,7 +9,3 @@ .navbar { padding: 1rem 0; } - -#logged-in { - color: var(--prodigo-white); -} From 8e3f792871850b9375fbfc46ec7278e48bca41eb Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 14:50:58 +0200 Subject: [PATCH 3/8] Implement logout button --- src/Client/Shared/Login/Login.razor | 19 +++++++++++++-- src/Client/Shared/Login/LoginModal.razor | 8 +++---- src/Client/Shared/MainLayout.razor | 30 ++++++++++++++---------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor index c80adfeb..8033384b 100644 --- a/src/Client/Shared/Login/Login.razor +++ b/src/Client/Shared/Login/Login.razor @@ -1,5 +1,7 @@ @using Blazorise.Extensions -@inject IJSRuntime _js +@using System.Net.Http.Headers +@inject IJSRuntime Js +@inject HttpClient Http @if (LoggedInUser.IsNullOrEmpty()) { @@ -17,6 +19,7 @@ else

Inloggad som: @LoggedInUser

+ } @code { @@ -27,9 +30,21 @@ else [CascadingParameter(Name = nameof(LoggedInUser))] public string? LoggedInUser { get; set; } + /// + /// Called to notify that the username has been changed. + /// + [CascadingParameter(Name = nameof(OnUserLoginChange))] + public EventCallback OnUserLoginChange { get; set; } = EventCallback.Empty; + private async Task FocusUsernameField() { // Focuses the username text input element. - await _js.InvokeVoidAsync("focusElement", "#username"); + await Js.InvokeVoidAsync("focusElement", "#username"); + } + + private async Task LogOut() + { + Http.DefaultRequestHeaders.Authorization = null; + await OnUserLoginChange.InvokeAsync(string.Empty); } } diff --git a/src/Client/Shared/Login/LoginModal.razor b/src/Client/Shared/Login/LoginModal.razor index d376d691..816ff40e 100644 --- a/src/Client/Shared/Login/LoginModal.razor +++ b/src/Client/Shared/Login/LoginModal.razor @@ -43,10 +43,10 @@ bool _logInFailed = false; /// - /// Called when the username is changed. + /// Called to notify that the username has been changed. /// - [Parameter] - public EventCallback OnUserLogInChange { get; set; } = EventCallback.Empty; + [CascadingParameter(Name=nameof(OnUserLoginChange))] + public EventCallback OnUserLoginChange { get; set; } = EventCallback.Empty; private async Task ValidateUser() { @@ -60,7 +60,7 @@ // TODO: When making the login-state persist then this auth token should be stored in a cookie. _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", authResponse?.Token); - await OnUserLogInChange.InvokeAsync(_user.Name); + await OnUserLoginChange.InvokeAsync(_user.Name); await _js.InvokeVoidAsync("closeModal", "#log-in"); _navigationManager.NavigateTo("dashboard"); } diff --git a/src/Client/Shared/MainLayout.razor b/src/Client/Shared/MainLayout.razor index 81ec7036..3b54b555 100644 --- a/src/Client/Shared/MainLayout.razor +++ b/src/Client/Shared/MainLayout.razor @@ -1,22 +1,26 @@ @inherits LayoutComponentBase - -
-
-
- -
-
- @Body - -
-
-
-
+ + +
+
+
+ +
+
+ @Body + +
+
+
+
+
@code { + private EventCallback OnUserLoginEventHandler => new(this, OnUserLogIn); + private void OnUserLogIn(string username) { _loggedInUser = username; From 5001b687a84506fed1abd9c3d96c44bf0985116c Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 14:52:44 +0200 Subject: [PATCH 4/8] Redirect to index.html on logout --- src/Client/Shared/Login/Login.razor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor index 8033384b..1f684c64 100644 --- a/src/Client/Shared/Login/Login.razor +++ b/src/Client/Shared/Login/Login.razor @@ -2,6 +2,7 @@ @using System.Net.Http.Headers @inject IJSRuntime Js @inject HttpClient Http +@inject NavigationManager NavigationManager @if (LoggedInUser.IsNullOrEmpty()) { @@ -46,5 +47,6 @@ else { Http.DefaultRequestHeaders.Authorization = null; await OnUserLoginChange.InvokeAsync(string.Empty); + NavigationManager.NavigateTo("/"); } } From 0148d747510bbd897c8938523b68cb262bc83392 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 15:08:29 +0200 Subject: [PATCH 5/8] Add merge TODO --- src/Client/Shared/Login/Login.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor index 1f684c64..722d238f 100644 --- a/src/Client/Shared/Login/Login.razor +++ b/src/Client/Shared/Login/Login.razor @@ -45,6 +45,7 @@ else private async Task LogOut() { + // TODO: this method should be moved into the session management service when merging with #118 Http.DefaultRequestHeaders.Authorization = null; await OnUserLoginChange.InvokeAsync(string.Empty); NavigationManager.NavigateTo("/"); From fd96a6f26fa72d594e5eebf0e09e9314b9fb59f4 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 18:52:19 +0200 Subject: [PATCH 6/8] Add test to make sure logout button renders --- src/Client/Shared/Login/Login.razor | 2 +- tests/Client.Tests/Shared/NavMenuTests.cs | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor index 0e0b4ea4..c9083b9f 100644 --- a/src/Client/Shared/Login/Login.razor +++ b/src/Client/Shared/Login/Login.razor @@ -7,7 +7,7 @@ @if (Session.IsAuthenticated) {
-

+

Inloggad som: @Session.Username

diff --git a/tests/Client.Tests/Shared/NavMenuTests.cs b/tests/Client.Tests/Shared/NavMenuTests.cs index c9d2f803..154a44d4 100644 --- a/tests/Client.Tests/Shared/NavMenuTests.cs +++ b/tests/Client.Tests/Shared/NavMenuTests.cs @@ -2,15 +2,8 @@ using Application.Users; -using Blazored.SessionStorage; - -using Client.Services.Authentication; using Client.Shared; -using Domain.Users; - -using Microsoft.Extensions.DependencyInjection; - namespace Client.Tests.Shared; public class NavMenuTests : UITestFixture @@ -29,6 +22,20 @@ public async Task NavMenu_DisplaysLoginButton_WhenUserNotLoggedIn() cut.Find("#login-button").Should().NotBeNull(); } + [Fact] + public async Task NavMenu_DisplaysLogoutButton_WhenUserLoggedIn() + { + // Arrange + MockSession.Setup(session => session.IsAuthenticated).Returns(true); + await SessionStorage.SetItemAsync("user", new AuthenticateResponse(LoggedInUser, FakeToken)); + + // Act + IRenderedComponent cut = Context.RenderComponent(); + + // Assert + cut.Find("#logout-button").Should().NotBeNull(); + } + [Fact] public async Task NavMenu_DisplaysLoginText_WhenUserLoggedIn() { @@ -39,7 +46,7 @@ public async Task NavMenu_DisplaysLoginText_WhenUserLoggedIn() IRenderedComponent cut = Context.RenderComponent(); // Assert - cut.Find("#logged-in").Should().NotBeNull(); + cut.Find("#logout-button").Should().NotBeNull(); } [Fact] From 5c8d16c01f3dc0c73c8d1e1266304834e98a9193 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 19:07:28 +0200 Subject: [PATCH 7/8] Make logging out notify about auth state change --- .../Authentication/SessionManagerService.cs | 6 ++++ src/Client/Shared/Login/Login.razor | 4 +-- tests/Client.Tests/Shared/Login/LoginTests.cs | 28 +++++++++++++++++++ tests/Client.Tests/Shared/NavMenuTests.cs | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tests/Client.Tests/Shared/Login/LoginTests.cs diff --git a/src/Client/Services/Authentication/SessionManagerService.cs b/src/Client/Services/Authentication/SessionManagerService.cs index d790c185..f9e104d8 100644 --- a/src/Client/Services/Authentication/SessionManagerService.cs +++ b/src/Client/Services/Authentication/SessionManagerService.cs @@ -51,7 +51,13 @@ public async Task BeginAsync(AuthenticateResponse authentication) public async Task EndAsync() { await _storage.RemoveItemAsync("user").ConfigureAwait(true); + _http.DefaultRequestHeaders.Authorization = null; + + IsAuthenticated = false; + Username = null; + + AuthenticationStateChanged?.Invoke(this, new AuthenticationEventArgs { State = null, }); _navigationManager.NavigateTo("/"); } diff --git a/src/Client/Shared/Login/Login.razor b/src/Client/Shared/Login/Login.razor index c9083b9f..e12105a5 100644 --- a/src/Client/Shared/Login/Login.razor +++ b/src/Client/Shared/Login/Login.razor @@ -7,10 +7,10 @@ @if (Session.IsAuthenticated) {
-

+

Inloggad som: @Session.Username

- +
} else diff --git a/tests/Client.Tests/Shared/Login/LoginTests.cs b/tests/Client.Tests/Shared/Login/LoginTests.cs new file mode 100644 index 00000000..4c70e372 --- /dev/null +++ b/tests/Client.Tests/Shared/Login/LoginTests.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; + +using Application.Users; + +using Client.Shared; + +using Microsoft.AspNetCore.Components.Web; + +namespace Client.Tests.Shared.Login; + +public class LoginTests : UITestFixture +{ + [Fact] + public async Task PressLogout_EndsSession_WhenUserWasAuthenticated() + { + // Arrange + MockSession.Setup(session => session.IsAuthenticated).Returns(true); + await SessionStorage.SetItemAsync("user", new AuthenticateResponse(LoggedInUser, FakeToken)); + + IRenderedComponent cut = Context.RenderComponent(); + + // Act + await cut.Find("#logout-button").ClickAsync(new MouseEventArgs()); + + // Assert + MockSession.Verify(session => session.EndAsync(), Times.Once); + } +} diff --git a/tests/Client.Tests/Shared/NavMenuTests.cs b/tests/Client.Tests/Shared/NavMenuTests.cs index 154a44d4..579df0e9 100644 --- a/tests/Client.Tests/Shared/NavMenuTests.cs +++ b/tests/Client.Tests/Shared/NavMenuTests.cs @@ -46,7 +46,7 @@ public async Task NavMenu_DisplaysLoginText_WhenUserLoggedIn() IRenderedComponent cut = Context.RenderComponent(); // Assert - cut.Find("#logout-button").Should().NotBeNull(); + cut.Find("#logged-in").Should().NotBeNull(); } [Fact] From e7be995899e809fd94066bf70323b1fcabe30656 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 May 2022 19:14:07 +0200 Subject: [PATCH 8/8] Prevent infinite loop when unauthenticated --- src/Client/Pages/Contracts/RecentlyViewed.razor | 4 +++- src/Client/Shared/FetchData.razor | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Client/Pages/Contracts/RecentlyViewed.razor b/src/Client/Pages/Contracts/RecentlyViewed.razor index 81bfff66..f4dd862e 100644 --- a/src/Client/Pages/Contracts/RecentlyViewed.razor +++ b/src/Client/Pages/Contracts/RecentlyViewed.razor @@ -2,11 +2,13 @@ @using Application.Contracts @using System.Collections.ObjectModel @inject HttpClient _http +@inject ISessionService _session +@inherits AuthenticatedView + ShouldShowErrors="@_session.IsAuthenticated"> diff --git a/src/Client/Shared/FetchData.razor b/src/Client/Shared/FetchData.razor index 32489892..c09d872c 100644 --- a/src/Client/Shared/FetchData.razor +++ b/src/Client/Shared/FetchData.razor @@ -54,7 +54,7 @@ else /// protected override async Task OnAfterRenderAsync(bool firstRender) { - if (Data is null) + if (Data is null && _errorMessage is null) await Fetch(Url); }