Skip to content

Commit 0771144

Browse files
committed
Added registration and login mechanism.
1 parent a6749c0 commit 0771144

11 files changed

Lines changed: 121 additions & 26 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
##
44
## Get latest from `dotnet new gitignore`
55

6+
# API test files
7+
requests.http
8+
69
# dotenv files
710
.env
811

CashFlowAnalyzer.Client/CashFlowAnalyzer.Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
1313
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
1414
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
15+
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
1516
</ItemGroup>
1617

1718
<ItemGroup>

CashFlowAnalyzer.Client/Layout/MainLayout.razor

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
@inherits LayoutComponentBase
2+
@* Issue: Cannot provide a value for property 'AccountService' on type
3+
'CashFlowAnalyzer.Client.Layout.MainLayout'. There is no registered
4+
service of type 'CashFlowAnalyzer.Client.Services.AccountService'. *@
5+
@* @inject NavigationManager Navigation
6+
@inject AccountService AccountService
7+
@inject AuthenticationStateProvider AuthenticationStateProvider *@
28

39
<div class="page">
410
<div class="sidebar">
@@ -7,6 +13,10 @@
713

814
<main>
915
<div class="top-row px-4">
16+
@* @if (isAuthenticated)
17+
{
18+
<button class="btn btn-danger" @onclick="Logout">Logout</button>
19+
} *@
1020
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
1121
</div>
1222

@@ -21,3 +31,19 @@
2131
<a href="." class="reload">Reload</a>
2232
<span class="dismiss">🗙</span>
2333
</div>
34+
35+
@* @code {
36+
private bool isAuthenticated;
37+
38+
protected override async Task OnInitializedAsync()
39+
{
40+
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
41+
isAuthenticated = authState.User.Identity.IsAuthenticated;
42+
}
43+
44+
private async Task Logout()
45+
{
46+
await AccountService.Logout();
47+
Navigation.NavigateTo("/Identity/Account/Login");
48+
}
49+
} *@

CashFlowAnalyzer.Client/Pages/Security/Login.razor

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
@page "/Identity/Account/Login"
2+
@* custom loginForm paths issue: *@
3+
@* https://github.com/dotnet/aspnetcore/issues/58811 *@
24
@using CashFlowAnalyzer.Client.Services
35
@inject NavigationManager Navigation
46
@inject HttpClient Http
@@ -34,11 +36,23 @@
3436
<NavLink href="security/register" class="btn btn-link">Register</NavLink>
3537
</div>
3638
</EditForm>
39+
@if (errors != null && errors.Count > 0)
40+
{
41+
<div class="alert alert-danger mt-3">
42+
<ul>
43+
@foreach (var error in errors)
44+
{
45+
<li>@error</li>
46+
}
47+
</ul>
48+
</div>
49+
}
3750
</div>
3851
</div>
3952

4053
@code {
4154
private bool loading;
55+
private List<string>? errors;
4256
private string returnUrl = "/";
4357
private LoginModel loginModel = new();
4458

@@ -54,15 +68,15 @@
5468
try
5569
{
5670
loading = true;
57-
var success = await AccountService.Login(loginModel);
58-
if (success)
71+
var result = await AccountService.Login(loginModel);
72+
if (result.Success)
5973
{
6074
Navigation.NavigateTo(returnUrl);
6175
}
6276
else
6377
{
6478
loading = false;
65-
// ToDo: Handle login failure
79+
errors = result.Errors;
6680
Console.WriteLine($"Logging failed");
6781
}
6882
}

CashFlowAnalyzer.Client/Pages/Security/Register.razor

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,23 @@
2424
</div>
2525
<button type="submit" class="btn btn-primary w-100">Register</button>
2626
</EditForm>
27+
@if (errors != null && errors.Count > 0)
28+
{
29+
<div class="alert alert-danger mt-3">
30+
<ul>
31+
@foreach (var error in errors)
32+
{
33+
<li>@error</li>
34+
}
35+
</ul>
36+
</div>
37+
}
2738
</div>
2839
</div>
2940

3041
@code {
3142
private string returnUrl = "/";
43+
private List<string>? errors;
3244
private RegisterModel registerModel = new RegisterModel();
3345

3446
protected override void OnInitialized()
@@ -40,14 +52,14 @@
4052

4153
private async Task HandleRegister()
4254
{
43-
var success = await AccountService.Register(registerModel);
44-
if (success)
55+
var result = await AccountService.Register(registerModel);
56+
if (result.Success)
4557
{
4658
Navigation.NavigateTo($"/Identity/Account/Login?returnUrl={returnUrl}");
4759
}
4860
else
4961
{
50-
// ToDo: Handle registration failure
62+
errors = result.Errors;
5163
Console.WriteLine($"Logging failed");
5264
}
5365
}

CashFlowAnalyzer.Client/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
builder.Services.AddApiAuthorization(options =>
2222
{
23+
// https://github.com/dotnet/AspNetCore.Docs/blob/b1e1428d5899fda009f65e2c4e41dac6a60df7b6/aspnetcore/blazor/security/webassembly/additional-scenarios.md#customize-app-routes
24+
// doesn't work because of auto interactivity, see https://github.com/dotnet/aspnetcore/issues/58811
2325
options.AuthenticationPaths.LogInPath = "security/login";
2426
options.AuthenticationPaths.RegisterPath = "security/register";
2527
});
Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11

22
using CashFlowAnalyzer.Shared.Models;
33
using System.Net;
4+
using System.Net.Http.Json;
45
using System.Text;
56
using System.Text.Json;
67

78
namespace CashFlowAnalyzer.Client.Services;
89

910
public class AccountService : IAccountService
1011
{
12+
// workaround for a bug? https://github.com/dotnet/aspnetcore/issues/51986
13+
// or misconfiguration https://github.com/dotnet/aspnetcore/issues/51468
14+
private string baseAddress = "http://localhost:5130";
1115
private readonly HttpClient _http;
1216
private readonly ILogger<AccountService> _log;
1317
public AccountService(HttpClient http, ILogger<AccountService> log)
@@ -16,27 +20,36 @@ public AccountService(HttpClient http, ILogger<AccountService> log)
1620
_log = log;
1721
}
1822

19-
public async Task<bool> Login(LoginModel model)
23+
public async Task<AccountServiceResult> Login(LoginModel model)
2024
{
21-
var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/login");
25+
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseAddress}/api/auth/login");
2226
request.Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json");
23-
using var response = await _http.SendAsync(request);
24-
if (response.StatusCode == HttpStatusCode.OK)
25-
{
26-
_log.LogInformation("Login successful for user {Username}", model.Username);
27-
return true;
28-
}
29-
_log.LogWarning("Login failed for user {Username}", model.Username);
30-
return false;
27+
return await SendRequest(request);
28+
}
29+
30+
public async Task<AccountServiceResult> Logout()
31+
{
32+
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseAddress}/api/auth/logout");
33+
return await SendRequest(request);
3134
}
3235

33-
public Task<bool> Logout()
36+
public async Task<AccountServiceResult> Register(RegisterModel model)
3437
{
35-
throw new NotImplementedException();
38+
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseAddress}/api/auth/register");
39+
request.Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json");
40+
return await SendRequest(request);
3641
}
3742

38-
public Task<bool> Register(RegisterModel model)
43+
private async Task<AccountServiceResult> SendRequest(HttpRequestMessage request)
3944
{
40-
throw new NotImplementedException();
45+
using var response = await _http.SendAsync(request);
46+
if (response.StatusCode == HttpStatusCode.OK)
47+
{
48+
_log.LogInformation("Registration was successful");
49+
return new AccountServiceResult() { Success = true };
50+
}
51+
_log.LogWarning("Registration failed");
52+
var errors = await response.Content.ReadFromJsonAsync<List<string>>();
53+
return new AccountServiceResult() { Success = false, Errors = errors };
4154
}
4255
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class AccountServiceResult
2+
{
3+
public bool Success { get; set; }
4+
public List<string>? Errors { get; set; }
5+
}

CashFlowAnalyzer.Client/Services/IAccountService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace CashFlowAnalyzer.Client.Services;
44

55
public interface IAccountService
66
{
7-
Task<bool> Login(LoginModel model);
8-
Task<bool> Logout();
9-
Task<bool> Register(RegisterModel model);
7+
Task<AccountServiceResult> Login(LoginModel model);
8+
Task<AccountServiceResult> Logout();
9+
Task<AccountServiceResult> Register(RegisterModel model);
1010
}

CashFlowAnalyzer/Controllers/AuthController.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public AuthController(SignInManager<ApplicationUser> signInManager, UserManager<
2020
[HttpPost("login")]
2121
public async Task<IActionResult> Login([FromBody] LoginModel model)
2222
{
23+
if (model == null)
24+
{
25+
return BadRequest("Invalid client request");
26+
}
2327
var user = await _userManager.FindByNameAsync(model.Username);
2428
if (user != null)
2529
{
@@ -32,6 +36,13 @@ public async Task<IActionResult> Login([FromBody] LoginModel model)
3236
return Unauthorized();
3337
}
3438

39+
[HttpPost("logout")]
40+
public async Task<IActionResult> Logout()
41+
{
42+
await _signInManager.SignOutAsync();
43+
return Ok("Logout successful");
44+
}
45+
3546
[HttpPost("register")]
3647
public async Task<IActionResult> Register([FromBody] RegisterModel model)
3748
{
@@ -42,7 +53,13 @@ public async Task<IActionResult> Register([FromBody] RegisterModel model)
4253
await _signInManager.SignInAsync(user, isPersistent: false);
4354
return Ok();
4455
}
45-
return BadRequest(result.Errors);
56+
var errors = new List<string>();
57+
foreach (var error in result.Errors)
58+
{
59+
errors.Add(error.Description);
60+
}
61+
62+
return BadRequest(errors);
4663
}
4764
}
4865
}

0 commit comments

Comments
 (0)