fronted: login
This commit is contained in:
@@ -14,12 +14,15 @@ public class EventModel
|
||||
public string? Description { get; set; }
|
||||
public DateTime? Date { get; set; }
|
||||
public string? Location { get; set; }
|
||||
[ForeignKey("TagId")]
|
||||
public List<TagModel> RelatedTags { get; set; } = [];
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
public EventModel() { }
|
||||
|
||||
public EventModel(string id)
|
||||
{
|
||||
Id = id;
|
||||
|
@@ -6,6 +6,8 @@ namespace back.DataModels;
|
||||
[Table("Galleries")]
|
||||
public class GalleryModel
|
||||
{
|
||||
public GalleryModel() { }
|
||||
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
[MaxLength(100)]
|
||||
@@ -16,7 +18,8 @@ public class GalleryModel
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public string? UpdatedBy { get; set; }
|
||||
public List<PhotoModel> Photos { get; set; } = new();
|
||||
[ForeignKey("PhotoId")]
|
||||
public List<PhotoModel> Photos { get; set; } = [];
|
||||
public bool? IsPublic { get; set; } = true;
|
||||
public bool? IsArchived { get; set; } = false;
|
||||
public bool? IsFavorite { get; set; } = false;
|
||||
|
@@ -6,6 +6,8 @@ namespace back.DataModels;
|
||||
[Table("Permissions")]
|
||||
public class PermissionModel
|
||||
{
|
||||
public PermissionModel() { }
|
||||
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
[Required, MaxLength(100)]
|
||||
|
@@ -6,12 +6,15 @@ namespace back.DataModels;
|
||||
[Table("Persons")]
|
||||
public class PersonModel
|
||||
{
|
||||
public PersonModel() { }
|
||||
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
[Required, MaxLength(100)]
|
||||
public string? Name { get; set; }
|
||||
public string? ProfilePicture { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
[ForeignKey("SocialMediaId")]
|
||||
public SocialMedia? SocialMedia { get; set; }
|
||||
[MaxLength(250)]
|
||||
public string? Bio { get; set; } // Optional field for a short biography or description
|
||||
@@ -33,9 +36,11 @@ public class PersonModel
|
||||
}
|
||||
}
|
||||
|
||||
[Table("SocialMediaLinks")]
|
||||
[Table("SocialMedia")]
|
||||
public class SocialMedia
|
||||
{
|
||||
[Key]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public string? Facebook { get; set; }
|
||||
public string? Instagram { get; set; }
|
||||
public string? Twitter { get; set; }
|
||||
|
@@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Photo")]
|
||||
[Table("Photos")]
|
||||
public class PhotoModel
|
||||
{
|
||||
public PhotoModel() { }
|
||||
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
[Required, MaxLength(100), MinLength(1)]
|
||||
@@ -20,12 +22,16 @@ public class PhotoModel
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public string CreatedBy { get; set; }
|
||||
public string UpdatedBy { get; set; }
|
||||
[ForeignKey("EventId")]
|
||||
public EventModel? Event { get; set; } = null;
|
||||
[ForeignKey("TagId")]
|
||||
public List<TagModel> Tags { get; set; } = [];
|
||||
[ForeignKey("RankingId")]
|
||||
public RankingModel Ranking { get; set; } = new RankingModel(0);
|
||||
public bool IsFavorite { get; set; } = false;
|
||||
public bool IsPublic { get; set; } = true;
|
||||
public bool IsArchived { get; set; } = false;
|
||||
[ForeignKey("PersonId")]
|
||||
public List<PersonModel>? Persons { get; set; }
|
||||
|
||||
public PhotoModel(
|
||||
|
@@ -1,7 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Rankings")]
|
||||
public class RankingModel
|
||||
{
|
||||
[Key]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
private int totalVotes;
|
||||
private int upVotes;
|
||||
private int downVotes;
|
||||
@@ -13,6 +20,13 @@ public class RankingModel
|
||||
this.downVotes = downVotes;
|
||||
}
|
||||
|
||||
public RankingModel()
|
||||
{
|
||||
totalVotes = 0;
|
||||
upVotes = 0;
|
||||
downVotes = 0;
|
||||
}
|
||||
|
||||
public void DownVote()
|
||||
{
|
||||
downVotes++;
|
||||
|
@@ -1,17 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Tags("Roles")]
|
||||
public class RoleModel
|
||||
{
|
||||
public RoleModel() { }
|
||||
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
[Required, MaxLength(100)]
|
||||
public string Name { get; set; }
|
||||
[MaxLength(250)]
|
||||
public string Description { get; set; }
|
||||
[ForeignKey("PermissionId")]
|
||||
public List<PermissionModel> Permissions { get; set; }
|
||||
[ForeignKey("RoleId")]
|
||||
public RoleModel? BaseRoleModel { get; set; }
|
||||
|
||||
public RoleModel(string id, string name, string description, List<PermissionModel>? permissions = null, RoleModel? baseRoleModel = null)
|
||||
|
@@ -4,11 +4,18 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Tags")]
|
||||
public class TagModel(string id, string? name = null)
|
||||
public class TagModel
|
||||
{
|
||||
public TagModel() { }
|
||||
public TagModel(string id, string? name = null)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[Key]
|
||||
public string Id { get; init; } = id;
|
||||
public string Id { get; init; }
|
||||
[Required, MaxLength(25)]
|
||||
public string? Name { get; init; } = name;
|
||||
public string? Name { get; init; }
|
||||
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
|
||||
}
|
||||
|
@@ -10,6 +10,9 @@ public class UserModel : PersonModel
|
||||
public string Email { get; set; }
|
||||
[Required, MinLength(8)]
|
||||
public string Password { get; set; }
|
||||
[Required]
|
||||
public string Salt { get; set; }
|
||||
[ForeignKey("RoleId")]
|
||||
public List<RoleModel> Role { get; set; }
|
||||
|
||||
public UserModel(string id, string email, string password, string name, List<RoleModel> role, DateTime createdAt, DateTime updatedAt)
|
||||
@@ -22,17 +25,30 @@ public class UserModel : PersonModel
|
||||
UpdatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public UserModel() { }
|
||||
|
||||
public bool IsAdmin => Role.Exists(r => r.IsAdmin);
|
||||
public bool IsContentManager => Role.Exists(r => r.IsContentManager);
|
||||
public bool IsUser => Role.Exists(r => r.IsUser);
|
||||
|
||||
public static readonly UserModel DefaultUser = new(
|
||||
"0",
|
||||
"default@example.com",
|
||||
string.Empty,
|
||||
"Default User",
|
||||
[RoleModel.UserRole],
|
||||
DateTime.UtcNow,
|
||||
DateTime.UtcNow
|
||||
);
|
||||
|
||||
public UserDto ToDto()
|
||||
{
|
||||
return new UserDto
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
Email = Email,
|
||||
ProfilePicture = ProfilePicture,
|
||||
Avatar = Avatar,
|
||||
SocialMedia = SocialMedia,
|
||||
Bio = Bio,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class UserDto : PersonModel
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
}
|
@@ -49,7 +49,9 @@ public static partial class DbContextOptionsBuilderExtensions
|
||||
|
||||
public static void UseDatabaseConfig(this DbContextOptionsBuilder options, DatabaseConfig config)
|
||||
{
|
||||
if(!Enum.TryParse(Enum.GetName(typeof(DatabaseProvider), config.Provider)?.ToLowerInvariant(), out DatabaseProvider provider))
|
||||
var providerName = Enum.GetNames<DatabaseProvider>()
|
||||
.FirstOrDefault(name => name.Equals(config.Provider, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (!Enum.TryParse(providerName, out DatabaseProvider provider))
|
||||
{
|
||||
throw new InvalidOperationException($"Unsupported database provider: {config.Provider} -- Supported providers are: {SupportedDbs()}");
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using back.persistance.blob;
|
||||
using back.services.Crypto;
|
||||
using back.services.ImageResizer;
|
||||
|
||||
namespace back.ServicesExtensions;
|
||||
@@ -15,6 +16,7 @@ public static partial class ServicesExtensions
|
||||
services.AddSingleton<IBlobStorageService, FileSystemImageStorageService>();
|
||||
|
||||
services.AddSingleton<IImageResizer, ImageResizer>();
|
||||
services.AddSingleton<ICryptoService, CryptoService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ namespace back.context;
|
||||
public class EventContext : DbContext
|
||||
{
|
||||
private DbSet<EventModel> Events { get; set; }
|
||||
public EventContext(DbContextOptions<EventContext> options)
|
||||
public EventContext(DbContextOptions<EventContext> options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
@@ -6,8 +6,9 @@ namespace back.context;
|
||||
public class PersonContext : DbContext
|
||||
{
|
||||
private DbSet<PersonModel> Persons { get; set; }
|
||||
public PersonContext(DbContextOptions<PersonContext> options)
|
||||
public PersonContext(DbContextOptions<PersonContext> options) : base(options)
|
||||
{
|
||||
// Ensure database is created
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
||||
|
@@ -162,36 +162,71 @@ public class PhotoContext : DbContext
|
||||
|
||||
public async Task<PhotoModel?> GetById(Guid id)
|
||||
{
|
||||
return await Photos.FindAsync(id);
|
||||
try
|
||||
{
|
||||
return await Photos.FindAsync(id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalItems()
|
||||
{
|
||||
return await Photos.CountAsync();
|
||||
try
|
||||
{
|
||||
return await Photos.CountAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PhotoModel>> GetPage(int page = 1, int pageSize = 20)
|
||||
public async Task<IEnumerable<PhotoModel>?> GetPage(int page = 1, int pageSize = 20)
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1) pageSize = 20;
|
||||
|
||||
return await Photos
|
||||
try
|
||||
{
|
||||
return await Photos
|
||||
.OrderByDescending(p => p.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Exists(PhotoModel? photo)
|
||||
{
|
||||
if (photo == null) return false;
|
||||
if (string.IsNullOrEmpty(photo.Id)) return false;
|
||||
return await Photos.AnyAsync(p => p.Id == photo.Id);
|
||||
try
|
||||
{
|
||||
if (photo == null) return false;
|
||||
if (string.IsNullOrEmpty(photo.Id)) return false;
|
||||
return await Photos.AnyAsync(p => p.Id == photo.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // Handle exceptions gracefully
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Exists(string id)
|
||||
{
|
||||
return await Photos.AnyAsync(p => p.Id == id);
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(id)) return false;
|
||||
return await Photos.AnyAsync(p => p.Id == id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // Handle exceptions gracefully
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Delete(PhotoModel photo)
|
||||
|
@@ -6,7 +6,7 @@ namespace back.context;
|
||||
public class TagContext : DbContext
|
||||
{
|
||||
private DbSet<TagModel> Tags { get; set; }
|
||||
public TagContext(DbContextOptions<TagContext> options)
|
||||
public TagContext(DbContextOptions<TagContext> options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
@@ -1,17 +1,111 @@
|
||||
using back.DataModels;
|
||||
using back.services.Crypto;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Net;
|
||||
|
||||
namespace back.context;
|
||||
|
||||
public class UserContext : DbContext
|
||||
{
|
||||
public DbSet<UserModel> Users { get; set; }
|
||||
public UserContext(DbContextOptions<UserContext> options) : base(options)
|
||||
public record HttpErrorMap(HttpStatusCode Code, string Description);
|
||||
|
||||
public static class Errors
|
||||
{
|
||||
public static readonly HttpErrorMap Unauthorized =
|
||||
new(HttpStatusCode.Unauthorized, "Invalid user data. Email or password are wrong.");
|
||||
public static readonly HttpErrorMap BadRequest =
|
||||
new(HttpStatusCode.BadRequest, "Missing user data.");
|
||||
}
|
||||
|
||||
public DbSet<UserModel> Users { get; set; }
|
||||
private readonly ICryptoService _cryptoService;
|
||||
public UserContext(
|
||||
DbContextOptions<UserContext> options,
|
||||
ICryptoService cryptoService
|
||||
) : base(options)
|
||||
{
|
||||
_cryptoService = cryptoService ?? throw new ArgumentNullException(nameof(cryptoService));
|
||||
// Ensure database is created
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
||||
public async Task<UserModel?> Create(string clientId, UserModel user)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
if (await Exists(user))
|
||||
{
|
||||
return await GetById(Guid.Parse(user.Id)) ?? null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(user.Id))
|
||||
{
|
||||
user.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(user.Salt))
|
||||
{
|
||||
user.Salt = _cryptoService.Salt();
|
||||
}
|
||||
user.Password = _cryptoService.Decrypt(clientId, user.Password) ?? string.Empty;
|
||||
user.Password = _cryptoService.Hash(user.Password + user.Salt + _cryptoService.Pepper()) ?? string.Empty;
|
||||
|
||||
user.CreatedAt = DateTime.UtcNow;
|
||||
Users.Add(user);
|
||||
await SaveChangesAsync();
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<UserModel?> Update(UserModel user)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
if (!await Exists(user))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var existingUser = await GetById(Guid.Parse(user.Id));
|
||||
if (existingUser == null) return null;
|
||||
existingUser.Name = user.Name;
|
||||
existingUser.Email = user.Email;
|
||||
existingUser.UpdatedAt = DateTime.UtcNow;
|
||||
Users.Update(existingUser);
|
||||
await SaveChangesAsync();
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
public async Task<bool> Delete(Guid id)
|
||||
{
|
||||
var user = await GetById(id);
|
||||
if (user == null) return false;
|
||||
Users.Remove(user);
|
||||
await SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<UserModel?> GetByEmail(string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email)) return null;
|
||||
return await Users.FirstOrDefaultAsync(u => u.Email == email);
|
||||
}
|
||||
|
||||
public async Task<string> GetUserSaltByEmail(string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email)) return string.Empty;
|
||||
var user = await Users.FirstOrDefaultAsync(u => u.Email == email);
|
||||
return user?.Salt ?? string.Empty;
|
||||
}
|
||||
|
||||
public async Task<UserModel?> Login(string email, string password, string clientId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
|
||||
|
||||
var pass = _cryptoService.Decrypt(clientId, password) + await GetUserSaltByEmail(email) + _cryptoService.Pepper();
|
||||
var hashedPassword = _cryptoService.Hash(pass);
|
||||
var user = await Users
|
||||
.FirstOrDefaultAsync(u => u.Email == email && u.Password == hashedPassword);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<UserModel?> GetById(Guid id)
|
||||
{
|
||||
return await Users.FindAsync(id);
|
||||
|
54
back/controllers/UsersController.cs
Normal file
54
back/controllers/UsersController.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using back.context;
|
||||
using back.DataModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace back.controllers;
|
||||
|
||||
[ApiController, Route("api/[controller]")]
|
||||
public class UsersController(UserContext userContext) : ControllerBase
|
||||
{
|
||||
private readonly UserContext _userContext = userContext;
|
||||
// GET: api/<UsersController>
|
||||
//[HttpGet]
|
||||
//public async Task<ActionResult<IEnumerable<UserModel>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
//{
|
||||
// var users = await _userContext.GetPage(page, pageSize);
|
||||
// var totalItems = await _userContext.GetTotalItems();
|
||||
// Response.Headers.Append("X-Total-Count", totalItems.ToString());
|
||||
// return Ok(users);
|
||||
//}
|
||||
//// GET api/<UsersController>/5
|
||||
//[HttpGet("{id}")]
|
||||
//public async Task<IActionResult> Get(Guid id)
|
||||
//{
|
||||
// var user = await _userContext.GetById(id);
|
||||
// if (user == null)
|
||||
// return NotFound();
|
||||
// return Ok(user);
|
||||
//}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Login(
|
||||
[FromHeader(Name = "X-client-thumbprint")] string clientId,
|
||||
[FromBody] UserModel user
|
||||
)
|
||||
{
|
||||
if (user == null || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
|
||||
return BadRequest(UserContext.Errors.BadRequest.Description);
|
||||
var existingUser = await _userContext.Login(user.Email, user.Password, clientId);
|
||||
if (existingUser == null)
|
||||
return Unauthorized(UserContext.Errors.Unauthorized.Description);
|
||||
return Ok(existingUser.ToDto());
|
||||
}
|
||||
|
||||
//// POST api/<UsersController>
|
||||
//[HttpPost]
|
||||
//public async Task<IActionResult> Post([FromBody] UserModel user)
|
||||
//{
|
||||
// if (user == null)
|
||||
// return BadRequest("User cannot be null");
|
||||
// var createdUser = await _userContext.Create(user);
|
||||
// return CreatedAtAction(nameof(Get), new { id = createdUser.Id }, createdUser);
|
||||
//}
|
||||
}
|
168
back/services/Crypto/CryptoService.cs
Normal file
168
back/services/Crypto/CryptoService.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace back.services.Crypto;
|
||||
|
||||
public class CryptoService(IMemoryCache cache) : ICryptoService
|
||||
{
|
||||
private readonly IMemoryCache _cache = cache;
|
||||
private readonly MemoryCacheEntryOptions _CacheOptions = new()
|
||||
{
|
||||
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1),
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
|
||||
SlidingExpiration = TimeSpan.FromMinutes(30),
|
||||
Priority = CacheItemPriority.High,
|
||||
PostEvictionCallbacks =
|
||||
{
|
||||
new PostEvictionCallbackRegistration
|
||||
{
|
||||
EvictionCallback = (key, value, reason, state) =>
|
||||
{
|
||||
var clientId = key.ToString()?.Replace("_public","").Replace("_private","");
|
||||
if(string.IsNullOrEmpty(clientId)) { return; }
|
||||
// Handle the eviction of the certificate - removing public/private keys from the cache
|
||||
try{ cache.Remove($"{clientId}_public"); } catch{ }
|
||||
try{ cache.Remove($"{clientId}_private"); } catch{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public string? Encrypt(string plainText, string clientId)
|
||||
{
|
||||
// get keys from cache
|
||||
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
throw new InvalidOperationException("Public certificate not found for the client.");
|
||||
}
|
||||
// import rsa keys and configure RSA for encryption
|
||||
using var rsa = RSA.Create(2048);
|
||||
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicCert), out _);
|
||||
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
|
||||
// Encrypt the plain text using RSA
|
||||
string? encryptedText = null;
|
||||
try
|
||||
{
|
||||
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
var encryptedBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
encryptedText = Convert.ToBase64String(encryptedBytes);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
// Handle encryption errors
|
||||
throw new InvalidOperationException("Encryption failed.", ex);
|
||||
}
|
||||
return encryptedText;
|
||||
}
|
||||
|
||||
public string? Decrypt(string encryptedText, string clientId)
|
||||
{
|
||||
// get keys from cache
|
||||
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
// import rsa keys and configure RSA for decryption
|
||||
using var rsa = RSA.Create(2048);
|
||||
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicCert), out _);
|
||||
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
|
||||
// Decrypt the encrypted text using RSA
|
||||
string? plainText = null;
|
||||
try
|
||||
{
|
||||
var encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||
var decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
plainText = System.Text.Encoding.UTF8.GetString(decryptedBytes);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
// Handle decryption errors
|
||||
throw new InvalidOperationException("Decryption failed.", ex);
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
|
||||
public string GetPublicCertificate(string clientId)
|
||||
{
|
||||
if (_cache.TryGetValue($"{clientId}_public", out string? publicCert) && !string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
return publicCert;
|
||||
}
|
||||
(publicCert, _) = GenerateCertificate();
|
||||
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
|
||||
return publicCert;
|
||||
}
|
||||
|
||||
public string GetPrivateCertificate(string clientId)
|
||||
{
|
||||
if (_cache.TryGetValue($"{clientId}_private", out string? privateCert) && !string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
return privateCert;
|
||||
}
|
||||
(_, privateCert) = GenerateCertificate();
|
||||
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
|
||||
return privateCert;
|
||||
}
|
||||
|
||||
private static (string publicCert, string privateCert) GenerateCertificate()
|
||||
{
|
||||
// Generate a new RSA key pair for the client
|
||||
using var rsa = RSA.Create(2048);
|
||||
var publicKey = rsa.ExportRSAPublicKey();
|
||||
var privateKey = rsa.ExportRSAPrivateKey();
|
||||
// Convert to Base64 strings for storage
|
||||
var publicCert = Convert.ToBase64String(publicKey);
|
||||
var privateCert = Convert.ToBase64String(privateKey);
|
||||
return (publicCert, privateCert);
|
||||
}
|
||||
|
||||
public string? Hash(string plainText)
|
||||
{
|
||||
string? hash = null;
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
var hashBytes = SHA256.HashData(plainBytes);
|
||||
hash = Convert.ToBase64String(hashBytes);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public bool VerifyHash(string plainText, string hash)
|
||||
{
|
||||
var plainTextHash = Hash(plainText);
|
||||
if (string.IsNullOrEmpty(plainTextHash) || string.IsNullOrEmpty(hash))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return plainTextHash.Equals(hash, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public string Pepper()
|
||||
{
|
||||
// get pepper from environtment variable
|
||||
var pepper = Environment.GetEnvironmentVariable("PEPPER");
|
||||
if (string.IsNullOrEmpty(pepper))
|
||||
{
|
||||
return "BactilForteFlash20mg";
|
||||
}
|
||||
return pepper;
|
||||
}
|
||||
|
||||
public string Salt()
|
||||
{
|
||||
var saltBytes = new byte[32]; // 256 bits
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(saltBytes);
|
||||
return Convert.ToBase64String(saltBytes);
|
||||
}
|
||||
}
|
13
back/services/Crypto/ICryptoService.cs
Normal file
13
back/services/Crypto/ICryptoService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace back.services.Crypto;
|
||||
|
||||
public interface ICryptoService
|
||||
{
|
||||
string? Encrypt(string clientId, string plainText);
|
||||
string? Decrypt(string clientId, string encryptedText);
|
||||
string? Hash(string plainText);
|
||||
bool VerifyHash(string plainText, string hash);
|
||||
string Salt();
|
||||
string Pepper();
|
||||
string GetPublicCertificate(string clientId);
|
||||
string GetPrivateCertificate(string clientId);
|
||||
}
|
Reference in New Issue
Block a user