Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1e8a834b2 | |||
| 61f3d2889c |
@@ -7,6 +7,6 @@ public class GetMessageDetailsDto
|
||||
public DateTime Date { get; set; }
|
||||
public Boolean Type { get; set; }
|
||||
|
||||
public int AuthorId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string AuthorName { get; set; } = "";
|
||||
}
|
||||
@@ -3,10 +3,11 @@ using Knots.DTO.Discussion;
|
||||
using Knots.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Security.Claims;
|
||||
using Knots.Services;
|
||||
|
||||
namespace Knots.Endpoints.Discussion;
|
||||
|
||||
public class CreateGroupDiscussionEndpoint(KnotsDbContext db) : Endpoint<CreateGroupDiscussionRequest, GetDiscussionDto>
|
||||
public class CreateGroupDiscussionEndpoint(KnotsDbContext db, EncryptionService encryption) : Endpoint<CreateGroupDiscussionRequest, GetDiscussionDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@@ -44,6 +45,7 @@ public class CreateGroupDiscussionEndpoint(KnotsDbContext db) : Endpoint<CreateG
|
||||
Models.Discussion discussion = new()
|
||||
{
|
||||
IsGroup = true,
|
||||
Key = new Models.Key { EnKey = encryption.GenerateKey() },
|
||||
UserDiscussions = targets
|
||||
.Select(t => new UserDiscussion { UserId = t.Id })
|
||||
.Append(new UserDiscussion { UserId = currentUserId })
|
||||
|
||||
@@ -3,10 +3,11 @@ using Knots.DTO.Discussion;
|
||||
using Knots.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Security.Claims;
|
||||
using Knots.Services;
|
||||
|
||||
namespace Knots.Endpoints.Discussion;
|
||||
|
||||
public class CreatePrivateDiscussionEndpoint(KnotsDbContext db)
|
||||
public class CreatePrivateDiscussionEndpoint(KnotsDbContext db, EncryptionService encryption)
|
||||
: Endpoint<CreatePrivateDiscussionRequest, GetDiscussionDto>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -57,6 +58,7 @@ public class CreatePrivateDiscussionEndpoint(KnotsDbContext db)
|
||||
Models.Discussion discussion = new()
|
||||
{
|
||||
IsGroup = false,
|
||||
Key = new Models.Key { EnKey = encryption.GenerateKey() },
|
||||
UserDiscussions =
|
||||
[
|
||||
new UserDiscussion { UserId = currentUserId },
|
||||
|
||||
@@ -2,11 +2,12 @@ using System.Security.Claims;
|
||||
using FastEndpoints;
|
||||
using Knots.DTO.Message;
|
||||
using Knots.DTO.User;
|
||||
using Knots.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Knots.Endpoints.Message;
|
||||
|
||||
public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<GetDiscussionMessagesRequest, List<GetMessageDetailsDto>>
|
||||
public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper, EncryptionService encryption) : Endpoint<GetDiscussionMessagesRequest, List<GetMessageDetailsDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@@ -31,19 +32,26 @@ public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
|
||||
return;
|
||||
}
|
||||
|
||||
List<GetMessageDetailsDto> messages = await db.Messages
|
||||
string? key = await db.Discussions
|
||||
.Where(d => d.Id == req.DiscussionId)
|
||||
.Select(d => d.Key!.EnKey)
|
||||
.SingleAsync(ct);
|
||||
|
||||
var rows = await db.Messages
|
||||
.Where(m => m.DiscussionId == req.DiscussionId)
|
||||
.OrderBy(m => m.Date)
|
||||
.Select(m => new GetMessageDetailsDto
|
||||
{
|
||||
Id = m.Id,
|
||||
Contenu = m.Contenu!,
|
||||
Date = m.Date,
|
||||
AuthorId = m.UserId,
|
||||
AuthorName = m.User.Username!
|
||||
})
|
||||
.Select(m => new { m.Id, m.Contenu, m.Date, m.UserId, AuthorName = m.User.Username! })
|
||||
.ToListAsync(ct);
|
||||
|
||||
List<GetMessageDetailsDto> messages = rows.Select(m => new GetMessageDetailsDto
|
||||
{
|
||||
Id = m.Id,
|
||||
Contenu = encryption.Decrypt(m.Contenu!, key!),
|
||||
Date = m.Date,
|
||||
UserId = m.UserId,
|
||||
AuthorName = m.AuthorName
|
||||
}).ToList();
|
||||
|
||||
await SendOkAsync(messages, ct);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-6
@@ -1,10 +1,12 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Knots.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Knots.Hubs;
|
||||
|
||||
[Authorize]
|
||||
public class ChatHub(KnotsDbContext db, AutoMapper.IMapper mapper) : Hub
|
||||
public class ChatHub(KnotsDbContext db, AutoMapper.IMapper mapper, EncryptionService encryption) : Hub
|
||||
{
|
||||
// Rejoindre une conversation (room)
|
||||
public async Task JoinConversation(string discussionId)
|
||||
@@ -21,19 +23,32 @@ public class ChatHub(KnotsDbContext db, AutoMapper.IMapper mapper) : Hub
|
||||
// Envoyer un message à une conversation
|
||||
public async Task SendMessage(string discussionId, string content)
|
||||
{
|
||||
int id = int.Parse(discussionId);
|
||||
|
||||
Models.Discussion discussion = await db.Discussions
|
||||
.Include(d => d.Key)
|
||||
.SingleAsync(d => d.Id == id);
|
||||
|
||||
var message = new Models.Message
|
||||
{
|
||||
Contenu = content,
|
||||
Contenu = encryption.Encrypt(content, discussion.Key!.EnKey!), // chiffré en base
|
||||
Date = DateTime.UtcNow,
|
||||
Type = false,
|
||||
Type = false,
|
||||
UserId = int.Parse(Context.UserIdentifier!),
|
||||
DiscussionId = int.Parse(discussionId)
|
||||
DiscussionId = id
|
||||
};
|
||||
|
||||
db.Messages.Add(message);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await Clients.Group(discussionId).SendAsync("ReceiveMessage", message);
|
||||
// diffusion en clair, avec les noms de champs attendus par le front
|
||||
await Clients.Group(discussionId).SendAsync("ReceiveMessage", new
|
||||
{
|
||||
id = message.Id,
|
||||
contenu = content,
|
||||
date = message.Date,
|
||||
userId = message.UserId
|
||||
});
|
||||
}
|
||||
|
||||
// Notifier que l'utilisateur est en train d'écrire
|
||||
|
||||
@@ -11,6 +11,9 @@ public class Discussion
|
||||
public int? GroupId { get; set; }
|
||||
public Group? Group { get; set; }
|
||||
|
||||
public int? KeyId { get; set; }
|
||||
public Key? Key { get; set; }
|
||||
|
||||
public List<Message> Messages { get; set; } = [];
|
||||
public List<UserDiscussion> UserDiscussions { get; set; } = [];
|
||||
}
|
||||
@@ -6,5 +6,4 @@ public class Key
|
||||
{
|
||||
[Key] public int Id { get; set; }
|
||||
[Required, MaxLength(50)] public string? EnKey { get; set; }
|
||||
public List<Message> Messages { get; set; } = [];
|
||||
}
|
||||
@@ -16,5 +16,4 @@ public class Message
|
||||
public Discussion Discussion { get; set; } = null!;
|
||||
|
||||
public Group? Group { get; set; }
|
||||
public Key? Key { get; set; }
|
||||
}
|
||||
@@ -73,6 +73,8 @@ builder.Services.AddSignalR();
|
||||
|
||||
builder.Services.AddAutoMapper(cfg => { }, typeof(Program).Assembly);
|
||||
|
||||
builder.Services.AddSingleton<EncryptionService>();
|
||||
|
||||
// On construit l'application en lui donnant vie
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Knots.Services;
|
||||
|
||||
public class EncryptionService
|
||||
{
|
||||
private const int NonceSize = 12; // AesGcm.NonceByteSizes.MaxSize
|
||||
private const int TagSize = 16; // AesGcm.TagByteSizes.MaxSize
|
||||
|
||||
// Génère une clé AES-256 (32 octets) encodée en Base64
|
||||
public string GenerateKey()
|
||||
=> Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
||||
|
||||
// Chiffre → renvoie Base64(nonce + tag + ciphertext)
|
||||
public string Encrypt(string plainText, string base64Key)
|
||||
{
|
||||
byte[] key = Convert.FromBase64String(base64Key);
|
||||
byte[] plain = Encoding.UTF8.GetBytes(plainText);
|
||||
|
||||
byte[] nonce = RandomNumberGenerator.GetBytes(NonceSize);
|
||||
byte[] cipher = new byte[plain.Length];
|
||||
byte[] tag = new byte[TagSize];
|
||||
|
||||
using AesGcm aes = new(key, TagSize);
|
||||
aes.Encrypt(nonce, plain, cipher, tag);
|
||||
|
||||
byte[] result = new byte[NonceSize + TagSize + cipher.Length];
|
||||
Buffer.BlockCopy(nonce, 0, result, 0, NonceSize);
|
||||
Buffer.BlockCopy(tag, 0, result, NonceSize, TagSize);
|
||||
Buffer.BlockCopy(cipher, 0, result, NonceSize + TagSize, cipher.Length);
|
||||
|
||||
return Convert.ToBase64String(result);
|
||||
}
|
||||
|
||||
// Déchiffre Base64(nonce + tag + ciphertext)
|
||||
public string Decrypt(string base64Cipher, string base64Key)
|
||||
{
|
||||
byte[] key = Convert.FromBase64String(base64Key);
|
||||
byte[] data = Convert.FromBase64String(base64Cipher);
|
||||
|
||||
byte[] nonce = new byte[NonceSize];
|
||||
byte[] tag = new byte[TagSize];
|
||||
byte[] cipher = new byte[data.Length - NonceSize - TagSize];
|
||||
|
||||
Buffer.BlockCopy(data, 0, nonce, 0, NonceSize);
|
||||
Buffer.BlockCopy(data, NonceSize, tag, 0, TagSize);
|
||||
Buffer.BlockCopy(data, NonceSize + TagSize, cipher, 0, cipher.Length);
|
||||
|
||||
byte[] plain = new byte[cipher.Length];
|
||||
using AesGcm aes = new(key, TagSize);
|
||||
aes.Decrypt(nonce, cipher, tag, plain);
|
||||
|
||||
return Encoding.UTF8.GetString(plain);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class CreateMessageDtoValidator : Validator<CreateMessageDto>
|
||||
RuleFor(x => x.Contenu)
|
||||
.NotEmpty()
|
||||
.WithMessage("Le message ne peux pas être vide")
|
||||
.MaximumLength(1000)
|
||||
.MaximumLength(2000)
|
||||
.WithMessage("Le message ne doit pas faire plus de 1000 caractères");
|
||||
|
||||
RuleFor(x => x.Date)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Knots")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+fff484d4ba20f7c49e5028f9d4c1ea30e818dffd")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+61f3d2889c45fcfb95557be37845198db85b3972")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Knots")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Knots")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
5d88c598527e99724c5b752512fbcecf629249d741e73169ee3813b5a417c501
|
||||
589797db71c3ee2db1d8c966ae2f187808629461cd5e264f494b12d939f74905
|
||||
|
||||
@@ -1 +1 @@
|
||||
eae1c606f235f5f51466aca07da485bdaa9b0072133d65d312d41bb94837718a
|
||||
8c008e0540a310c319081e4a6b8a7ce2f5c9b271d5260b8b45b33f49b9ad5228
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
17811343456901051
|
||||
17811676816949331
|
||||
@@ -1 +1 @@
|
||||
17811343564060541
|
||||
17811669515789890
|
||||
Reference in New Issue
Block a user