diff --git a/Middlewares.cs b/Middlewares.cs new file mode 100644 index 0000000..73d7087 --- /dev/null +++ b/Middlewares.cs @@ -0,0 +1,126 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; + +namespace Simple_API; + +public static class Middlewares +{ + public class ClaimsValidationMiddleware (RequestDelegate next) + { + public async Task InvokeAsync(HttpContext context) + { + if (context.User.Identity?.IsAuthenticated == true) + { + var userId = context.User.FindFirst(ClaimTypes.GivenName)?.Value; + var role = context.User.FindFirst(ClaimTypes.Role)?.Value; + + if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(role)) + { + Console.WriteLine(context.Connection.RemoteIpAddress?.ToString()); + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("Reported detected spoofed JWT in request header to admins."); + return; + } + } + await next(context); + } + } +} + +public static class LightCrypto +{ + private const string IdCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private const int IdLength = 28; + + public static string GenerateRandomId() + { + byte[] randomBytes = new byte[IdLength]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomBytes); + } + + char[] idChars = new char[IdLength]; + + for (int i = 0; i < IdLength; i++) + { + idChars[i] = IdCharacters[randomBytes[i] % IdCharacters.Length]; + } + + return new string(idChars); + } + + public static string GenerateJwtToken(string email, string role, string userId, IConfiguration configuration) + { + var claims = new[] + { + new Claim(ClaimTypes.Email, email), + new Claim(ClaimTypes.Role, role), + new Claim(ClaimTypes.GivenName, userId), + }; + + var configKey = configuration["Jwt:Key"]; + + if (string.IsNullOrEmpty(configKey)) + { + return string.Empty; + } + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configKey)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: configuration["Jwt:Issuer"], + audience: configuration["Jwt:Audience"], + claims: claims, + expires: DateTime.Now.AddMinutes(190), + signingCredentials: credentials); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public static string HashPassword(string password) + { + using var hmac = new HMACSHA256(); + var salt = hmac.Key; + var hash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password)); + + return Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash); + } + + public static bool VerifyPassword(string hashedPassword, string password) + { + var parts = hashedPassword.Split(':'); + var salt = Convert.FromBase64String(parts[0]); + var hash = Convert.FromBase64String(parts[1]); + + using var hmac = new HMACSHA256(salt); + var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password)); + + return computedHash.SequenceEqual(hash); + } +} + +public static class DatabaseUtils +{ + public static async Task GenerateUniqueIdAsync(Database.Database context) + { + string uniqueId; + + do + { + uniqueId = LightCrypto.GenerateRandomId(); + } while (await IdExistsInDatabase(context, uniqueId)); + + return uniqueId; + } + + private static async Task IdExistsInDatabase(Database.Database context, string id) + { + return await context.Users.AnyAsync(user => user.Id == id); + } +} \ No newline at end of file