Refactoring, database structure changed

This commit is contained in:
2025-03-03 00:56:32 +01:00
Unverified
parent d3805ef3db
commit c603c41c0b
913 changed files with 21764 additions and 32775 deletions

View File

@@ -0,0 +1,164 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
using Refit;
using WatchIt.DTO.Models.Controllers.Accounts.Account;
using WatchIt.DTO.Models.Controllers.Accounts.AccountLogout;
using WatchIt.DTO.Models.Controllers.Authentication;
using WatchIt.Website.Clients;
using WatchIt.Website.Services.Tokens;
namespace WatchIt.Website.Services.Authentication;
public class AuthenticationService : IAuthenticationService
{
#region FIELDS
private readonly ITokensService _tokensService;
private readonly IAuthenticationClient _authenticationClient;
private readonly IAccountsClient _accountsClient;
#endregion
#region CONSTRUCTORS
public AuthenticationService(ITokensService tokensService, IAuthenticationClient authenticationClient, IAccountsClient accountsClient)
{
_tokensService = tokensService;
_authenticationClient = authenticationClient;
_accountsClient = accountsClient;
}
#endregion
#region PUBLIC METHODS
public async Task<string?> GetRawAccessTokenAsync()
{
string? accessToken = await _tokensService.GetAccessToken();
if (string.IsNullOrWhiteSpace(accessToken))
{
return null;
}
if (ValidateToken(accessToken))
{
return accessToken;
}
string? refreshToken = await _tokensService.GetRefreshToken();
if (string.IsNullOrWhiteSpace(refreshToken))
{
return null;
}
IApiResponse<AuthenticationResponse> refreshResponse = await _authenticationClient.AuthenticateRefresh(new AuthenticationRefreshRequest
{
AccessToken = accessToken,
RefreshToken = refreshToken
});
if (refreshResponse.IsSuccessful)
{
await UpdateTokens(refreshResponse.Content);
return refreshResponse.Content.AccessToken;
}
return null;
}
public async Task<long?> GetAccountIdAsync()
{
string? accessToken = await GetRawAccessTokenAsync();
if (string.IsNullOrWhiteSpace(accessToken))
{
return null;
}
IEnumerable<Claim> claims = GetClaimsFromToken(accessToken);
Claim? subClaim = claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub);
if (subClaim is null || !long.TryParse(subClaim.Value, out long accountId))
{
return null;
}
return accountId;
}
public async Task<IApiResponse> Login(AuthenticationRequest data)
{
IApiResponse<AuthenticationResponse> response = await _authenticationClient.Authenticate(data);
if (response.IsSuccessful)
{
await UpdateTokens(response.Content);
}
return response;
}
public async Task<IApiResponse> Logout()
{
IApiResponse response = await _accountsClient.Logout(new AccountLogoutRequest
{
RefreshToken = await _tokensService.GetRefreshToken(),
});
if (response.IsSuccessful)
{
await Task.WhenAll(
[
_tokensService.DeleteAccessToken(),
_tokensService.DeleteRefreshToken()
]);
}
return response;
}
#endregion
#region PRIVATE METHODS
private async Task UpdateTokens(AuthenticationResponse tokens) => await Task.WhenAll(
[
_tokensService.SetAccessToken(tokens.AccessToken),
_tokensService.SetRefreshToken(tokens.RefreshToken)
]);
private static bool ValidateToken(string token)
{
IEnumerable<Claim> claims = GetClaimsFromToken(token);
Claim? claim = claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp);
if (claim is null || !long.TryParse(claim.Value, out long expiration))
{
return false;
}
DateTime expirationDate = DateTime.UnixEpoch.AddSeconds(expiration);
return expirationDate > DateTime.UtcNow;
}
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)
{
return [];
}
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
#endregion
}

View File

@@ -0,0 +1,20 @@
namespace WatchIt.Website.Services.Authentication;
public static class AuthenticationTokenProvider
{
private static Func<CancellationToken, Task<string>>? _getTokenAsyncFunc;
public static void SetTokenGetterFunc(Func<CancellationToken, Task<string>> getTokenAsyncFunc)
{
_getTokenAsyncFunc = getTokenAsyncFunc;
}
public static Task<string> GetTokenAsync(CancellationToken cancellationToken)
{
if (_getTokenAsyncFunc is null)
{
throw new InvalidOperationException("Token getter func must be set before using it");
}
return _getTokenAsyncFunc!(cancellationToken);
}
}

View File

@@ -0,0 +1,12 @@
using Refit;
using WatchIt.DTO.Models.Controllers.Authentication;
namespace WatchIt.Website.Services.Authentication;
public interface IAuthenticationService
{
Task<string?> GetRawAccessTokenAsync();
Task<long?> GetAccountIdAsync();
Task<IApiResponse> Login(AuthenticationRequest data);
Task<IApiResponse> Logout();
}

View File

@@ -0,0 +1,7 @@
namespace WatchIt.Website.Services.Tokens.Configuration;
public class StorageKeys
{
public string AccessToken { get; set; } = null!;
public string RefreshToken { get; set; } = null!;
}

View File

@@ -0,0 +1,6 @@
namespace WatchIt.Website.Services.Tokens.Configuration;
public class Tokens
{
public StorageKeys StorageKeys { get; set; } = null!;
}

View File

@@ -0,0 +1,20 @@
namespace WatchIt.Website.Services.Tokens;
public interface ITokensService
{
#region Access token
Task<string?> GetAccessToken();
Task SetAccessToken(string accessToken);
Task DeleteAccessToken();
#endregion
#region Refresh token
Task<string?> GetRefreshToken();
Task SetRefreshToken(string accessToken);
Task DeleteRefreshToken();
#endregion
}

View File

@@ -0,0 +1,76 @@
using System.Security.Cryptography;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using Microsoft.Extensions.Caching.Memory;
namespace WatchIt.Website.Services.Tokens;
public class TokensService : ITokensService
{
#region SERVICES
private readonly ILogger<TokensService> _logger;
private readonly ProtectedLocalStorage _localStorageService;
private readonly Configuration.Tokens _configuration;
#endregion
#region CONSTRUCTORS
public TokensService(ILogger<TokensService> logger, IConfiguration configuration, ProtectedLocalStorage localStorageService)
{
_logger = logger;
_localStorageService = localStorageService;
_configuration = configuration.GetSection("Tokens").Get<Configuration.Tokens>()!;
}
#endregion
#region PUBLIC METHODS
#region Access token
public async Task<string?> GetAccessToken() => await GetValueAsync<string>(_configuration.StorageKeys.AccessToken);
public async Task SetAccessToken(string accessToken) => await _localStorageService.SetAsync(_configuration.StorageKeys.AccessToken, accessToken);
public async Task DeleteAccessToken() => await _localStorageService.DeleteAsync(_configuration.StorageKeys.AccessToken);
#endregion
#region Refresh token
public async Task<string?> GetRefreshToken() => await GetValueAsync<string>(_configuration.StorageKeys.RefreshToken);
public async Task SetRefreshToken(string accessToken) => await _localStorageService.SetAsync(_configuration.StorageKeys.RefreshToken, accessToken);
public async Task DeleteRefreshToken() => await _localStorageService.DeleteAsync(_configuration.StorageKeys.RefreshToken);
#endregion
#endregion
#region PRIVATE METHODS
private async Task<T?> GetValueAsync<T>(string key)
{
try
{
ProtectedBrowserStorageResult<T> result = await _localStorageService.GetAsync<T>(key);
return result.Success ? result.Value : default;
}
catch (CryptographicException ex)
{
_logger.LogError(ex, "Browser storage error has occurred. Deleting value.");
await _localStorageService.DeleteAsync(key);
return default;
}
}
#endregion
}