Website services organized

This commit is contained in:
2024-10-27 22:09:46 +01:00
Unverified
parent 17b2a8c98e
commit 291982cf95
105 changed files with 465 additions and 669 deletions

View File

@@ -0,0 +1,85 @@
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Components.Authorization;
using WatchIt.Website.Services.Client.Accounts;
using WatchIt.Website.Services.Tokens;
namespace WatchIt.Website.Services.Authentication;
public class AuthenticationService : IAuthenticationService
{
#region SERVICES
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly HttpClient _httpClient;
private readonly ITokensService _tokensService;
private readonly IAccountsClientService _accountsClientService;
#endregion
#region CONSTRUCTORS
public AuthenticationService(AuthenticationStateProvider authenticationStateProvider, HttpClient httpClient, ITokensService tokensService, IAccountsClientService accountsClientService)
{
_authenticationStateProvider = authenticationStateProvider;
_httpClient = httpClient;
_tokensService = tokensService;
_accountsClientService = accountsClientService;
}
#endregion
#region PUBLIC METHODS
public async Task<User?> GetUserAsync()
{
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
if (!GetAuthenticationStatusAsync(state))
{
return null;
}
return new User
{
Id = int.Parse(state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Sub)!.Value),
Username = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.UniqueName)!.Value,
Email = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Email)!.Value,
IsAdmin = bool.Parse(state.User.FindFirst(x => x.Type == "admin")!.Value),
};
}
public async Task<bool> GetAuthenticationStatusAsync()
{
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return GetAuthenticationStatusAsync(state);
}
public async Task LogoutAsync()
{
string? refreshToken = await _tokensService.GetRefreshToken();
if (refreshToken is not null)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("refresh", refreshToken.Replace("\"", ""));
await _accountsClientService.Logout();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
#endregion
#region PRIVATE METHODS
private bool GetAuthenticationStatusAsync(AuthenticationState state)
{
return state.User.HasClaim(x => x.Type == JwtRegisteredClaimNames.Iss && x.Value == "WatchIt");
}
#endregion
}

View File

@@ -0,0 +1,8 @@
namespace WatchIt.Website.Services.Authentication;
public interface IAuthenticationService
{
Task<User?> GetUserAsync();
Task<bool> GetAuthenticationStatusAsync();
Task LogoutAsync();
}

View File

@@ -0,0 +1,165 @@
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using WatchIt.Common.Model.Accounts;
using WatchIt.Website.Services.Tokens;
using WatchIt.Website.Services.Client.Accounts;
namespace WatchIt.Website.Services.Authentication;
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
{
#region SERVICES
private readonly HttpClient _httpClient;
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
private readonly ITokensService _tokensService;
private readonly IAccountsClientService _accountsService;
#endregion
#region CONSTRUCTORS
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsClientService accountsService)
{
_httpClient = httpClient;
_logger = logger;
_tokensService = tokensService;
_accountsService = accountsService;
}
#endregion
#region PUBLIC METHODS
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
AuthenticationState state = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
Task<string?> accessTokenTask = _tokensService.GetAccessToken();
Task<string?> refreshTokenTask = _tokensService.GetRefreshToken();
await Task.WhenAll(accessTokenTask, refreshTokenTask);
string? accessToken = await accessTokenTask;
string? refreshToken = await refreshTokenTask;
bool refreshed = false;
if (string.IsNullOrWhiteSpace(accessToken))
{
if (string.IsNullOrWhiteSpace(refreshToken))
{
return state;
}
string? accessTokenNew = await Refresh(refreshToken);
if (string.IsNullOrWhiteSpace(accessToken))
{
return state;
}
accessToken = accessTokenNew;
refreshed = true;
}
IEnumerable<Claim> claims = GetClaimsFromToken(accessToken);
Claim? expClaim = claims.FirstOrDefault(c => c.Type == "exp");
if (expClaim is not null && ConvertFromUnixTimestamp(int.Parse(expClaim.Value)) > DateTime.UtcNow)
{
if (refreshed)
{
return state;
}
}
else
{
if (string.IsNullOrWhiteSpace(refreshToken))
{
return state;
}
string? accessTokenNew = await Refresh(refreshToken);
if (accessTokenNew is null)
{
return state;
}
accessToken = accessTokenNew;
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("\"", ""));
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims)));
}
#endregion
#region PRIVATE METHODS
private async Task<string?> Refresh(string refreshToken)
{
AuthenticateResponse? response = null;
void SetResponse(AuthenticateResponse data)
{
response = data;
}
await _accountsService.AuthenticateRefresh(SetResponse);
if (response is not null)
{
await _tokensService.SaveAuthenticationData(response);
}
else
{
await _tokensService.RemoveAuthenticationData();
}
return response?.AccessToken;
}
private static IEnumerable<Claim> GetClaimsFromToken(string token)
{
string payload = token.Split('.')[1];
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] jsonBytes = Convert.FromBase64String(payload);
Dictionary<string, object>? keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
if (keyValuePairs is null)
{
throw new Exception("Incorrect token");
}
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
public static DateTime ConvertFromUnixTimestamp(int timestamp)
{
DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
date = date.AddSeconds(timestamp);
return date;
}
#endregion
}

View File

@@ -0,0 +1,13 @@
namespace WatchIt.Website.Services.Authentication;
public class User
{
#region PROPERTIES
public required long Id { get; init; }
public required string Username { get; init; }
public required string Email { get; init; }
public required bool IsAdmin { get; init; }
#endregion
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Authorization">
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.10\Microsoft.AspNetCore.Components.Authorization.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WatchIt.Website.Services.Client\WatchIt.Website.Services.Client.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services.Tokens\WatchIt.Website.Services.Tokens.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
</ItemGroup>
</Project>