mirror of
https://github.com/azaion/admin.git
synced 2026-04-22 06:56:32 +00:00
Init commit
add security encryption and hashing: WIP add endpoints: register user, get and save resources add db main operations, User entity
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Options">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.8\Microsoft.Extensions.Options.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
using Azaion.Common;
|
||||
using Azaion.Common.Configs;
|
||||
using Azaion.Common.Entities;
|
||||
using Azaion.Common.Requests;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Azaion.Services;
|
||||
|
||||
public interface IResourcesService
|
||||
{
|
||||
Task GetEncryptedResource(GetResourceRequest request, Stream outputStream, CancellationToken cancellationToken = default);
|
||||
Task SaveResource(UploadResourceRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class ResourcesService(IOptions<ResourcesConfig> resourcesConfig) : IResourcesService
|
||||
{
|
||||
public async Task GetEncryptedResource(GetResourceRequest request, Stream outputStream, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var fileStream = new FileStream(GetResourcePath(request.ResourceEnum), FileMode.Open, FileAccess.Read);
|
||||
var key = Security.MakeEncryptionKey(request.Username, request.Password);
|
||||
await fileStream.Encrypt(outputStream, key, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task SaveResource(UploadResourceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var fileStream = new FileStream(GetResourcePath(request.ResourceEnum), FileMode.OpenOrCreate, FileAccess.ReadWrite);
|
||||
await request.Data.CopyToAsync(fileStream, cancellationToken);
|
||||
}
|
||||
|
||||
private string GetResourcePath(ResourceEnum resourceEnum)
|
||||
{
|
||||
var resource = resourcesConfig.Value.Resources.GetValueOrDefault(resourceEnum.ToString());
|
||||
if (resource == null)
|
||||
throw new BusinessException(ExceptionEnum.WrongResourceType, "Wrong resource type!");
|
||||
return Path.Combine(resourcesConfig.Value.ResourcesFolder, resource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Azaion.Services;
|
||||
|
||||
public static class Security
|
||||
{
|
||||
private const int BUFFER_SIZE = 81920; // 80 KB buffer size
|
||||
|
||||
public static string ToHash(this string str) =>
|
||||
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
|
||||
|
||||
public static string MakeEncryptionKey(string username, string password) =>
|
||||
$"{username}-{password}---#%@AzaionKey@%#---";
|
||||
|
||||
public static async Task Encrypt(this Stream stream, Stream outputStream, string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (stream is { CanSeek: false }) throw new ArgumentNullException(nameof(stream));
|
||||
if (key is not { Length: > 0 }) throw new ArgumentNullException(nameof(key));
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
|
||||
aes.GenerateIV();
|
||||
|
||||
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
|
||||
await using var cs = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write);
|
||||
|
||||
// Prepend IV to the encrypted data
|
||||
await outputStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken);
|
||||
|
||||
var buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, cancellationToken)) > 0)
|
||||
await cs.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task Decrypt(this Stream encryptedStream, Stream outputStream, string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
|
||||
|
||||
// Read the IV from the start of the input stream
|
||||
var iv = new byte[aes.BlockSize / 8];
|
||||
_ = await encryptedStream.ReadAsync(iv, cancellationToken);
|
||||
aes.IV = iv;
|
||||
|
||||
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
await using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
|
||||
|
||||
// Read and write in chunks
|
||||
var buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await cryptoStream.ReadAsync(buffer, cancellationToken)) > 0)
|
||||
await outputStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Azaion.Common;
|
||||
using Azaion.Common.Database;
|
||||
using Azaion.Common.Entities;
|
||||
using Azaion.Common.Requests;
|
||||
using LinqToDB;
|
||||
|
||||
namespace Azaion.Services;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
Task RegisterUser(RegisterUserRequest request, CancellationToken cancellationToken = default);
|
||||
Task<User?> ValidateUser(GetResourceRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class UserService(IDbFactory dbFactory) : IUserService
|
||||
{
|
||||
public async Task RegisterUser(RegisterUserRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await dbFactory.Run(async db =>
|
||||
{
|
||||
var existingUser = await db.Users.FirstOrDefaultAsync(u => u.Username == request.Email, token: cancellationToken);
|
||||
if (existingUser != null)
|
||||
throw new BusinessException(ExceptionEnum.UserExists, "User already exists");
|
||||
|
||||
await db.InsertAsync(new User
|
||||
{
|
||||
Username = request.Email,
|
||||
PasswordHash = request.Password.ToHash(),
|
||||
Role = request.Role
|
||||
}, token: cancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<User?> ValidateUser(GetResourceRequest request, CancellationToken cancellationToken = default) =>
|
||||
await dbFactory.Run(async db =>
|
||||
{
|
||||
var user = await db.Users.FirstOrDefaultAsync(x => x.Username == request.Username, token: cancellationToken);
|
||||
if (user == null)
|
||||
throw new BusinessException(ExceptionEnum.NoUserFound, "No user found");
|
||||
|
||||
if (request.Password.ToHash() != user.PasswordHash)
|
||||
throw new BusinessException(ExceptionEnum.PasswordIncorrect, "Passwords do not match");
|
||||
|
||||
//If user's hardware Id is empty (usually on the first time login), then write down user
|
||||
if (string.IsNullOrEmpty(user.HardwareId))
|
||||
await db.Users.UpdateAsync(x => x.Username == request.Username, u => new User{HardwareId = request.HardwareId}, token: cancellationToken);
|
||||
else
|
||||
{
|
||||
//But if hardware Id exists, it should match with request
|
||||
if (user.HardwareId != request.HardwareId)
|
||||
throw new BusinessException(ExceptionEnum.HardwareIdMismatch, "Hardware id mismatch");
|
||||
}
|
||||
|
||||
return user;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user