Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1e8a834b2 | |||
| 61f3d2889c |
@@ -7,6 +7,6 @@ public class GetMessageDetailsDto
|
|||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public Boolean Type { get; set; }
|
public Boolean Type { get; set; }
|
||||||
|
|
||||||
public int AuthorId { get; set; }
|
public int UserId { get; set; }
|
||||||
public string AuthorName { get; set; } = "";
|
public string AuthorName { get; set; } = "";
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,11 @@ using Knots.DTO.Discussion;
|
|||||||
using Knots.Models;
|
using Knots.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Knots.Services;
|
||||||
|
|
||||||
namespace Knots.Endpoints.Discussion;
|
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()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
@@ -44,6 +45,7 @@ public class CreateGroupDiscussionEndpoint(KnotsDbContext db) : Endpoint<CreateG
|
|||||||
Models.Discussion discussion = new()
|
Models.Discussion discussion = new()
|
||||||
{
|
{
|
||||||
IsGroup = true,
|
IsGroup = true,
|
||||||
|
Key = new Models.Key { EnKey = encryption.GenerateKey() },
|
||||||
UserDiscussions = targets
|
UserDiscussions = targets
|
||||||
.Select(t => new UserDiscussion { UserId = t.Id })
|
.Select(t => new UserDiscussion { UserId = t.Id })
|
||||||
.Append(new UserDiscussion { UserId = currentUserId })
|
.Append(new UserDiscussion { UserId = currentUserId })
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ using Knots.DTO.Discussion;
|
|||||||
using Knots.Models;
|
using Knots.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Knots.Services;
|
||||||
|
|
||||||
namespace Knots.Endpoints.Discussion;
|
namespace Knots.Endpoints.Discussion;
|
||||||
|
|
||||||
public class CreatePrivateDiscussionEndpoint(KnotsDbContext db)
|
public class CreatePrivateDiscussionEndpoint(KnotsDbContext db, EncryptionService encryption)
|
||||||
: Endpoint<CreatePrivateDiscussionRequest, GetDiscussionDto>
|
: Endpoint<CreatePrivateDiscussionRequest, GetDiscussionDto>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
@@ -57,6 +58,7 @@ public class CreatePrivateDiscussionEndpoint(KnotsDbContext db)
|
|||||||
Models.Discussion discussion = new()
|
Models.Discussion discussion = new()
|
||||||
{
|
{
|
||||||
IsGroup = false,
|
IsGroup = false,
|
||||||
|
Key = new Models.Key { EnKey = encryption.GenerateKey() },
|
||||||
UserDiscussions =
|
UserDiscussions =
|
||||||
[
|
[
|
||||||
new UserDiscussion { UserId = currentUserId },
|
new UserDiscussion { UserId = currentUserId },
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ using System.Security.Claims;
|
|||||||
using FastEndpoints;
|
using FastEndpoints;
|
||||||
using Knots.DTO.Message;
|
using Knots.DTO.Message;
|
||||||
using Knots.DTO.User;
|
using Knots.DTO.User;
|
||||||
|
using Knots.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Knots.Endpoints.Message;
|
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()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
@@ -31,19 +32,26 @@ public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
|
|||||||
return;
|
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)
|
.Where(m => m.DiscussionId == req.DiscussionId)
|
||||||
.OrderBy(m => m.Date)
|
.OrderBy(m => m.Date)
|
||||||
.Select(m => new GetMessageDetailsDto
|
.Select(m => new { m.Id, m.Contenu, m.Date, m.UserId, AuthorName = m.User.Username! })
|
||||||
{
|
|
||||||
Id = m.Id,
|
|
||||||
Contenu = m.Contenu!,
|
|
||||||
Date = m.Date,
|
|
||||||
AuthorId = m.UserId,
|
|
||||||
AuthorName = m.User.Username!
|
|
||||||
})
|
|
||||||
.ToListAsync(ct);
|
.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);
|
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.AspNetCore.SignalR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Knots.Hubs;
|
namespace Knots.Hubs;
|
||||||
|
|
||||||
[Authorize]
|
[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)
|
// Rejoindre une conversation (room)
|
||||||
public async Task JoinConversation(string discussionId)
|
public async Task JoinConversation(string discussionId)
|
||||||
@@ -21,19 +23,32 @@ public class ChatHub(KnotsDbContext db, AutoMapper.IMapper mapper) : Hub
|
|||||||
// Envoyer un message à une conversation
|
// Envoyer un message à une conversation
|
||||||
public async Task SendMessage(string discussionId, string content)
|
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
|
var message = new Models.Message
|
||||||
{
|
{
|
||||||
Contenu = content,
|
Contenu = encryption.Encrypt(content, discussion.Key!.EnKey!), // chiffré en base
|
||||||
Date = DateTime.UtcNow,
|
Date = DateTime.UtcNow,
|
||||||
Type = false,
|
Type = false,
|
||||||
UserId = int.Parse(Context.UserIdentifier!),
|
UserId = int.Parse(Context.UserIdentifier!),
|
||||||
DiscussionId = int.Parse(discussionId)
|
DiscussionId = id
|
||||||
};
|
};
|
||||||
|
|
||||||
db.Messages.Add(message);
|
db.Messages.Add(message);
|
||||||
await db.SaveChangesAsync();
|
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
|
// Notifier que l'utilisateur est en train d'écrire
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ public class Discussion
|
|||||||
public int? GroupId { get; set; }
|
public int? GroupId { get; set; }
|
||||||
public Group? Group { 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<Message> Messages { get; set; } = [];
|
||||||
public List<UserDiscussion> UserDiscussions { get; set; } = [];
|
public List<UserDiscussion> UserDiscussions { get; set; } = [];
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,4 @@ public class Key
|
|||||||
{
|
{
|
||||||
[Key] public int Id { get; set; }
|
[Key] public int Id { get; set; }
|
||||||
[Required, MaxLength(50)] public string? EnKey { 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 Discussion Discussion { get; set; } = null!;
|
||||||
|
|
||||||
public Group? Group { get; set; }
|
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.AddAutoMapper(cfg => { }, typeof(Program).Assembly);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<EncryptionService>();
|
||||||
|
|
||||||
// On construit l'application en lui donnant vie
|
// On construit l'application en lui donnant vie
|
||||||
WebApplication app = builder.Build();
|
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)
|
RuleFor(x => x.Contenu)
|
||||||
.NotEmpty()
|
.NotEmpty()
|
||||||
.WithMessage("Le message ne peux pas être vide")
|
.WithMessage("Le message ne peux pas être vide")
|
||||||
.MaximumLength(1000)
|
.MaximumLength(2000)
|
||||||
.WithMessage("Le message ne doit pas faire plus de 1000 caractères");
|
.WithMessage("Le message ne doit pas faire plus de 1000 caractères");
|
||||||
|
|
||||||
RuleFor(x => x.Date)
|
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.AssemblyCompanyAttribute("Knots")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[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.AssemblyProductAttribute("Knots")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Knots")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Knots")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[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