feat: Refactor authentication and token management

- Updated EchoHubConnection to use ApiClient for token retrieval.
- Introduced ValidationConstants for common validation patterns and limits.
- Enhanced AuthDtos with refresh token support and expiration details.
- Added new ChatDtos for channel creation and topic updates.
- Created CommonDtos for error and paginated responses.
- Implemented RefreshToken model for managing refresh tokens.
- Modified JwtTokenService to generate and hash refresh tokens.
- Updated AuthController to handle registration, login, token refresh, and logout with improved error handling.
- Enhanced ChannelsController with channel creation, topic updates, and pagination for channel retrieval.
- Added FilesController for file management with rate limiting.
- Improved UsersController for profile updates and avatar uploads with validation.
- Integrated rate limiting across controllers to manage request load.
- Introduced FileValidationHelper for validating uploaded image files.
- Updated database context to include RefreshToken and enforce unique constraints.
- Enhanced ChatHub for improved channel and message handling with validation.
- Updated Program.cs to configure rate limiting and CORS policies.
This commit is contained in:
HueByte
2026-02-19 03:25:01 +01:00
parent ed1bf13302
commit 287270b26d
18 changed files with 669 additions and 96 deletions
@@ -0,0 +1,68 @@
namespace EchoHub.Server.Services;
public static class FileValidationHelper
{
private static readonly byte[] JpegMagic = [0xFF, 0xD8, 0xFF];
private static readonly byte[] PngMagic = [0x89, 0x50, 0x4E, 0x47];
private static readonly byte[] GifMagic = [0x47, 0x49, 0x46];
private static readonly byte[] WebpRiff = [0x52, 0x49, 0x46, 0x46]; // "RIFF"
private static readonly byte[] WebpTag = [0x57, 0x45, 0x42, 0x50]; // "WEBP"
/// <summary>
/// Validates that a stream contains a recognized image format by checking magic bytes.
/// The stream position is reset to the beginning after validation.
/// </summary>
public static bool IsValidImage(Stream stream)
{
if (!stream.CanSeek)
return false;
var originalPosition = stream.Position;
try
{
var header = new byte[12];
var bytesRead = stream.Read(header, 0, header.Length);
if (bytesRead < 3)
return false;
// JPEG: FF D8 FF
if (StartsWith(header, bytesRead, JpegMagic))
return true;
// PNG: 89 50 4E 47
if (bytesRead >= 4 && StartsWith(header, bytesRead, PngMagic))
return true;
// GIF: 47 49 46 (GIF87a or GIF89a)
if (StartsWith(header, bytesRead, GifMagic))
return true;
// WebP: RIFF....WEBP
if (bytesRead >= 12 && StartsWith(header, bytesRead, WebpRiff)
&& header[8] == WebpTag[0] && header[9] == WebpTag[1]
&& header[10] == WebpTag[2] && header[11] == WebpTag[3])
return true;
return false;
}
finally
{
stream.Position = originalPosition;
}
}
private static bool StartsWith(byte[] buffer, int length, byte[] magic)
{
if (length < magic.Length)
return false;
for (int i = 0; i < magic.Length; i++)
{
if (buffer[i] != magic[i])
return false;
}
return true;
}
}