-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
513 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Collections.Generic; | ||
|
||
namespace Damselfly.Core.Models; | ||
|
||
public class LoginResult | ||
{ | ||
public bool Successful { get; set; } | ||
public string Error { get; set; } | ||
public string Token { get; set; } | ||
} | ||
|
||
|
||
public class LoginModel | ||
{ | ||
[Required] | ||
public string Email { get; set; } | ||
|
||
[Required] | ||
public string Password { get; set; } | ||
|
||
public bool RememberMe { get; set; } | ||
} | ||
|
||
public class RegisterModel | ||
{ | ||
[Required] | ||
[EmailAddress] | ||
[Display(Name = "Email")] | ||
public string Email { get; set; } | ||
|
||
[Required] | ||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] | ||
[DataType(DataType.Password)] | ||
[Display(Name = "Password")] | ||
public string Password { get; set; } | ||
|
||
[DataType(DataType.Password)] | ||
[Display(Name = "Confirm password")] | ||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] | ||
public string ConfirmPassword { get; set; } | ||
} | ||
|
||
public class RegisterResult | ||
{ | ||
public bool Successful { get; set; } | ||
public IEnumerable<string> Errors { get; set; } | ||
} | ||
|
||
public class UserModel | ||
{ | ||
public string Email { get; set; } | ||
public bool IsAuthenticated { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
using System; | ||
|
||
using Blazored.LocalStorage; | ||
using Microsoft.AspNetCore.Components.Authorization; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Security.Claims; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using Damselfly.Core.ScopedServices.ClientServices; | ||
|
||
namespace ChrisSaintyExample.Client; | ||
|
||
public class ApiAuthenticationStateProvider : AuthenticationStateProvider | ||
{ | ||
private readonly RestClient _httpClient; | ||
private readonly ILocalStorageService _localStorage; | ||
|
||
public ApiAuthenticationStateProvider(RestClient httpClient, ILocalStorageService localStorage) | ||
{ | ||
_httpClient = httpClient; | ||
_localStorage = localStorage; | ||
} | ||
public override async Task<AuthenticationState> GetAuthenticationStateAsync() | ||
{ | ||
var savedToken = await _localStorage.GetItemAsync<string>("authToken"); | ||
|
||
if (string.IsNullOrWhiteSpace(savedToken)) | ||
{ | ||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | ||
} | ||
|
||
_httpClient.AuthHeader = new AuthenticationHeaderValue("bearer", savedToken); | ||
|
||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt"))); | ||
} | ||
|
||
public void MarkUserAsAuthenticated(string email) | ||
{ | ||
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth")); | ||
var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); | ||
NotifyAuthenticationStateChanged(authState); | ||
} | ||
|
||
public void MarkUserAsLoggedOut() | ||
{ | ||
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity()); | ||
var authState = Task.FromResult(new AuthenticationState(anonymousUser)); | ||
NotifyAuthenticationStateChanged(authState); | ||
} | ||
|
||
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt) | ||
{ | ||
var claims = new List<Claim>(); | ||
var payload = jwt.Split('.')[1]; | ||
var jsonBytes = ParseBase64WithoutPadding(payload); | ||
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); | ||
|
||
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles); | ||
|
||
if (roles != null) | ||
{ | ||
if (roles.ToString().Trim().StartsWith("[")) | ||
{ | ||
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString()); | ||
|
||
foreach (var parsedRole in parsedRoles) | ||
{ | ||
claims.Add(new Claim(ClaimTypes.Role, parsedRole)); | ||
} | ||
} | ||
else | ||
{ | ||
claims.Add(new Claim(ClaimTypes.Role, roles.ToString())); | ||
} | ||
|
||
keyValuePairs.Remove(ClaimTypes.Role); | ||
} | ||
|
||
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); | ||
|
||
return claims; | ||
} | ||
|
||
private byte[] ParseBase64WithoutPadding(string base64) | ||
{ | ||
switch (base64.Length % 4) | ||
{ | ||
case 2: base64 += "=="; break; | ||
case 3: base64 += "="; break; | ||
} | ||
return Convert.FromBase64String(base64); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
@page "/login" | ||
@inject IAuthService AuthService | ||
@inject NavigationManager NavigationManager | ||
|
||
@using ChrisSaintyExample.Client.Services; | ||
|
||
<h1>Login</h1> | ||
|
||
@if (ShowErrors) | ||
{ | ||
<div class="alert alert-danger" role="alert"> | ||
<p>@Error</p> | ||
</div> | ||
} | ||
|
||
<div class="card"> | ||
<div class="card-body"> | ||
<h5 class="card-title">Please enter your details</h5> | ||
<EditForm Model="loginModel" OnValidSubmit="HandleLogin"> | ||
<DataAnnotationsValidator /> | ||
<ValidationSummary /> | ||
|
||
<div class="form-group"> | ||
<label for="email">Email address</label> | ||
<InputText Id="email" Class="form-control" @bind-Value="loginModel.Email" /> | ||
<ValidationMessage For="@(() => loginModel.Email)" /> | ||
</div> | ||
<div class="form-group"> | ||
<label for="password">Password</label> | ||
<InputText Id="password" type="password" Class="form-control" @bind-Value="loginModel.Password" /> | ||
<ValidationMessage For="@(() => loginModel.Password)" /> | ||
</div> | ||
<button type="submit" class="btn btn-primary">Submit</button> | ||
</EditForm> | ||
</div> | ||
</div> | ||
|
||
@code { | ||
|
||
private LoginModel loginModel = new LoginModel(); | ||
private bool ShowErrors; | ||
private string Error = ""; | ||
|
||
private async Task HandleLogin() | ||
{ | ||
ShowErrors = false; | ||
|
||
var result = await AuthService.Login(loginModel); | ||
|
||
if (result.Successful) | ||
{ | ||
NavigationManager.NavigateTo("/"); | ||
} | ||
else | ||
{ | ||
Error = result.Error; | ||
ShowErrors = true; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@page "/logout" | ||
@inject IAuthService AuthService | ||
@inject NavigationManager NavigationManager | ||
@using ChrisSaintyExample.Client.Services; | ||
|
||
|
||
@code { | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
await AuthService.Logout(); | ||
NavigationManager.NavigateTo("/"); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
@page "/register" | ||
@inject IAuthService AuthService | ||
@inject NavigationManager NavigationManager | ||
@using ChrisSaintyExample.Client.Services; | ||
|
||
<h1>Register</h1> | ||
|
||
@if (ShowErrors) | ||
{ | ||
<div class="alert alert-danger" role="alert"> | ||
@foreach (var error in Errors) | ||
{ | ||
<p>@error</p> | ||
} | ||
</div> | ||
} | ||
|
||
<div class="card"> | ||
<div class="card-body"> | ||
<h5 class="card-title">Please enter your details</h5> | ||
<EditForm Model="RegisterModel" OnValidSubmit="HandleRegistration"> | ||
<DataAnnotationsValidator /> | ||
<ValidationSummary /> | ||
|
||
<div class="form-group"> | ||
<label for="email">Email address</label> | ||
<InputText Id="email" class="form-control" @bind-Value="RegisterModel.Email" /> | ||
<ValidationMessage For="@(() => RegisterModel.Email)" /> | ||
</div> | ||
<div class="form-group"> | ||
<label for="password">Password</label> | ||
<InputText Id="password" type="password" class="form-control" @bind-Value="RegisterModel.Password" /> | ||
<ValidationMessage For="@(() => RegisterModel.Password)" /> | ||
</div> | ||
<div class="form-group"> | ||
<label for="password">Confirm Password</label> | ||
<InputText Id="password" type="password" class="form-control" @bind-Value="RegisterModel.ConfirmPassword" /> | ||
<ValidationMessage For="@(() => RegisterModel.ConfirmPassword)" /> | ||
</div> | ||
<button type="submit" class="btn btn-primary">Submit</button> | ||
</EditForm> | ||
</div> | ||
</div> | ||
|
||
@code { | ||
|
||
private RegisterModel RegisterModel = new RegisterModel(); | ||
private bool ShowErrors; | ||
private IEnumerable<string> Errors; | ||
|
||
private async Task HandleRegistration() | ||
{ | ||
ShowErrors = false; | ||
|
||
var result = await AuthService.Register(RegisterModel); | ||
|
||
if (result.Successful) | ||
{ | ||
NavigationManager.NavigateTo("/login"); | ||
} | ||
else | ||
{ | ||
Errors = result.Errors; | ||
ShowErrors = true; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.