Final Form

This commit is contained in:
2024-10-20 15:39:08 -04:00
parent 905d3747d2
commit 070babb17a
105 changed files with 1027 additions and 3212 deletions

View File

@@ -0,0 +1,63 @@
using System.Security.Claims;
namespace Hutopy.Web.Common.Security;
public static class ClaimsPrincipalExtensions
{
public static Guid GetUserId(this ClaimsPrincipal claims)
{
return (Guid)claims.GetRequiredClaim<Guid>(ClaimTypes.NameIdentifier);
}
public static string GetName(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Name);
}
public static string? GetAlias(this ClaimsPrincipal claims)
{
return (string?)claims.GetClaim<string?>(KnownClaims.Alias);
}
public static string? GetPortraitUrl(this ClaimsPrincipal claims)
{
return (string?)claims.GetClaim<string?>(KnownClaims.PortraitUrl);
}
public static string GetFirstName(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.GivenName);
}
public static string GetLastName(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Surname);
}
public static string GetEmail(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Email);
}
public static object? GetClaim<TValue>(this ClaimsPrincipal claims, string key)
{
var claim = claims.FindFirst(key);
if (claim is null) return default;
return claims.GetRequiredClaim<TValue>(key);
}
public static object GetRequiredClaim<TValue>(this ClaimsPrincipal claims, string key)
{
var claim = claims.FindFirst(key);
if (claim is null) throw new MissingClaimException(key);
if (typeof(TValue) == typeof(Guid))
{
return Guid.Parse(claim.Value);
}
return Convert.ChangeType(claim.Value, typeof(TValue));
}
}

View File

@@ -0,0 +1,53 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace Hutopy.Web.Common.Security;
public static class JwtTokenHelper
{
public static string GenerateJwtToken(
TimeSpan expiresIn,
string issuer,
string audience,
string key,
string userId,
string email,
string? alias,
string firstname,
string lastname,
string? portraitUrl)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.Name, email), new Claim(ClaimTypes.GivenName, firstname),
new Claim(ClaimTypes.Surname, lastname)
});
if (alias is not null)
{
claims.Add(new(KnownClaims.Alias, alias));
}
if (portraitUrl is not null)
{
claims.Add(new(KnownClaims.PortraitUrl, portraitUrl));
}
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.Now.Add(expiresIn),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

View File

@@ -0,0 +1,7 @@
namespace Hutopy.Web.Common.Security;
public static class KnownClaims
{
public const string Alias = "alias";
public const string PortraitUrl = "portraitUrl";
}

View File

@@ -0,0 +1,5 @@
namespace Hutopy.Web.Common.Security;
public class MissingClaimException(
string claimName)
: Exception;

View File

@@ -0,0 +1,70 @@
using System.Text;
namespace Hutopy.Web.Common.Security;
// If we need to add special characters we can alternate between 2 pools.
public static class PasswordGenerator
{
private const string LowerLetters = "abcdefghijklmnopqrstuvwxyz";
private const string UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private const string Numbers = "0123456789";
private const string SpecialCharacters = "!@#$%^&*()_+-=[];',./`~{}|:\"<>?";
private static readonly Random Random = new();
public static string GeneratePassword(
int minLength,
int maxLength,
bool requireNumber = true,
bool requireCapital = true,
bool requireSpecialCharacter = true)
{
// Create pools based on the requirements
var characterPool = new StringBuilder(LowerLetters);
if (requireCapital)
characterPool.Append(UpperLetters);
if (requireNumber)
characterPool.Append(Numbers);
if (requireSpecialCharacter)
characterPool.Append(SpecialCharacters);
// Ensure that the length is within the specified bounds
int length = Random.Next(minLength, maxLength + 1);
var password = new char[length];
// Ensure at least one character from each required category is included
int index = 0;
if (requireCapital)
password[index++] = UpperLetters[Random.Next(UpperLetters.Length)];
if (requireNumber)
password[index++] = Numbers[Random.Next(Numbers.Length)];
if (requireSpecialCharacter)
password[index++] = SpecialCharacters[Random.Next(SpecialCharacters.Length)];
// Fill the rest of the password
for (int i = index; i < length; i++)
{
password[i] = characterPool[Random.Next(characterPool.Length)];
}
// Shuffle the password to randomize the placement of the required characters
Shuffle(password);
return new string(password);
}
private static void Shuffle(
char[] array)
{
for (int i = array.Length - 1; i > 0; i--)
{
int j = Random.Next(i + 1);
(array[i], array[j]) = (array[j], array[i]); // Swap elements
}
}
}