Working on users database & api dto's

This commit is contained in:
Max 2023-09-10 21:32:25 +02:00
parent 62244750de
commit 952e0d7e3e
18 changed files with 288 additions and 22 deletions

View File

@ -0,0 +1,21 @@
namespace SharpRSS.API.Contracts
{
public class ApiResult
{
public ApiResult(string message, ApiResults result)
{
Message = message;
Result = result;
}
public string Message { get; }
public ApiResults Result { get; }
}
public enum ApiResults
{
Ok,
Warning,
Error,
Invalid
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace SharpRSS.API.Contracts.DTO
{
public class UserDto
{
public UserDto(string id, string userName, string mail, string role, DateTime dateCreated)
{
Id = id;
UserName = userName;
Mail = mail;
Role = role;
DateCreated = dateCreated;
}
public string Id { get; }
public string UserName { get; }
public string Mail { get; }
public string Role { get; }
public DateTime DateCreated { get; }
}
}

View File

@ -13,7 +13,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Auth" /> <Folder Include="Auth" />
<Folder Include="DTO" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharpRSS.API.Contracts;
using SharpRSS.API.Contracts.DTO;
using SharpRSS.API.Data;
using SharpRSS.API.Models;
using SharpRSS.API.Models.Auth;
namespace SharpRSS.API.Controllers namespace SharpRSS.API.Controllers
{ {
@ -7,10 +12,20 @@ namespace SharpRSS.API.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class AuthController : ControllerBase public class AuthController : ControllerBase
{ {
[HttpGet("authenticate")] public AuthController(AuthService authService)
public async Task<ActionResult<string>> Authenticate()
{ {
return "Authenticated!"; _authService = authService;
}
private readonly AuthService _authService;
[HttpPost("create")]
public async Task<ActionResult<UserDto>> CreateUser(UserRequest user)
{
Result<User> result = await _authService.CreateUser(user);
if (result.Success)
return Ok(Models.Auth.User.ToDto(result.Value ?? new User()));
return BadRequest(new ApiResult(result.Message, ApiResults.Error));
} }
} }
} }

View File

@ -1,11 +1,16 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace SharpRSS.API.Controllers namespace SharpRSS.API.Controllers
{ {
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class RssController public class RssController : ControllerBase
{ {
[HttpGet("guide")]
public async Task<ActionResult<string>> GetGuide()
{
return Ok("Guide data");
}
} }
} }

View File

@ -0,0 +1,34 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace SharpRSS.API.Cryptography
{
public static class Hasher
{
private const int KeySize = 128;
private const int Iterations = 420069;
static readonly HashAlgorithmName Algorithm = HashAlgorithmName.SHA512;
public static byte[] HashPassword(string password, out byte[] salt)
{
salt = RandomNumberGenerator.GetBytes(KeySize);
return HashInternal(password, salt);
}
public static bool ComparePasswords(string password, byte[] hash, byte[] salt)
{
byte[] passwordHashed = HashInternal(password, salt);
if (hash.Length != passwordHashed.Length)
return false;
return !hash.Where((t, i) => t != passwordHashed[i]).Any();
}
private static byte[] HashInternal(string password, byte[] salt)
{
var hash = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, Iterations, Algorithm,KeySize);
return hash;
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using SharpRSS.API.Cryptography;
using SharpRSS.API.Models;
using SharpRSS.API.Models.Auth;
using ToolQit;
using ToolQit.Logging;
namespace SharpRSS.API.Data
{
public class AuthService
{
public AuthService(IConfiguration configuration)
{
_configuration = configuration;
_log = LogManager.CreateLogger(typeof(AuthService));
_log.Information("Setting up service...");
}
private readonly IConfiguration _configuration;
private readonly ILog _log;
public async Task<Result<User>> CreateUser(UserRequest userRequest)
{
bool result = false;
await using DbAccess access = new DbAccess(_configuration);
var user = access.Users.FirstOrDefault(u => u.UserName == userRequest.UserName);
if (user != null)
return new Result<User>(user, message:"User name already exists!");
byte[] hashedPwdBytes = Hasher.HashPassword(userRequest.Password, out byte[] salt);
user = new User()
{
UserName = userRequest.UserName,
Mail = userRequest.EMail,
Password = hashedPwdBytes,
Salt = salt
};
access.Users.Add(user);
try
{
int entries = await access.SaveChangesAsync();
result = entries > 0;
}
catch (Exception e)
{
_log.Error(e, "Error creating user: {UserName}", user.UserName);
return new Result<User>(user, message: "Could not create user!");
}
return new Result<User>(user, result);
}
}
}

View File

@ -1,7 +0,0 @@
namespace SharpRSS.API.Data
{
public class AuthenticationService
{
}
}

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using SharpRSS.API.Models.Auth;
using ToolQit;
using ToolQit.Logging;
namespace SharpRSS.API.Data
{
public sealed class DbAccess : DbContext
{
public DbAccess(IConfiguration configuration)
{
_log = LogManager.CreateLogger(typeof(DbAccess));
_configuration = configuration;
Database.EnsureCreated();
}
public DbSet<User> Users { get; set; }
private readonly IConfiguration _configuration;
private readonly ILog _log;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connection = _configuration["DataBase:Connection"] ?? throw new ArgumentNullException(nameof(_configuration), "No connection string in appsettings!");
string server = _configuration["DataBase:Server"] ?? "Unknown";
switch (server)
{
case "MariaDB":
var dbSrvVersion = ServerVersion.AutoDetect(connection);
_log.Information("Server {SrvType} detected, version: {SrvVersion}", dbSrvVersion.Type.ToString(), dbSrvVersion.Version);
optionsBuilder.UseMySql(connection, dbSrvVersion);
break;
default: // TODO: Add more database support.
_log.Warning("No valid db server: {Server}/nSupported db servers: 'MariaDB'", server);
throw new Exception("Database server not specified!");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("srss_user");
base.OnModelCreating(modelBuilder);
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using SharpRSS.API.Contracts.DTO;
namespace SharpRSS.API.Models.Auth
{
public class User
{
public static UserDto ToDto(User user) => new UserDto(user.Id, user.UserName, user.Mail, user.Role, user.DateCreated);
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
[Required]
public string UserName { get; set; }
[Required]
[EmailAddress]
public string Mail { get; set; }
[Required]
public byte[] Password { get; set; }
[Required]
public byte[] Salt { get; set; }
[Required]
public string Role { get; set; } = "User";
[Required]
public DateTime DateCreated { get; set; } = DateTime.Now;
}
}

View File

@ -0,0 +1,9 @@
namespace SharpRSS.API.Models.Auth
{
public class UserRequest
{
public string UserName { get; set; }
public string EMail { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace SharpRSS.API.Models
{
public class Result<TValue>
{
public Result(TValue value, bool success = false, string message = "")
{
Value = value;
Success = success;
Message = message;
}
public TValue? Value { get; }
public bool Success { get; }
public string Message { get; }
}
}

View File

@ -9,6 +9,7 @@ using SharpRSS.API.Data;
using ToolQit; using ToolQit;
using ToolQit.Logging.Serilog; using ToolQit.Logging.Serilog;
SetupSerilog(); SetupSerilog();
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddSerilog(); builder.Logging.AddSerilog();
@ -18,7 +19,7 @@ builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddScoped<AuthenticationService>(); builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<SharpRssService>(); builder.Services.AddScoped<SharpRssService>();
var app = builder.Build(); var app = builder.Build();

View File

@ -9,6 +9,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
@ -22,8 +24,4 @@
<ProjectReference Include="..\ToolQit\ToolQit\ToolQit.csproj" /> <ProjectReference Include="..\ToolQit\ToolQit\ToolQit.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Models" />
</ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,8 @@
{ {
"DataBase": {
"Server": "MariaDB",
"Connection": "server=192.168.1.4;user id=api_dev;password=B4OqGO50qCxZXESD;database=sharprss_dev"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",

View File

@ -0,0 +1,19 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
namespace SharpRSS.Blazor.Data
{
public class SrssAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "srss")
}, "Custom Authentication");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
}

View File

@ -1,11 +1,13 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MudBlazor.Services; using MudBlazor.Services;
using Serilog; using Serilog;
using Serilog.Formatting.Compact; using Serilog.Formatting.Compact;
using SharpRSS.Blazor.Data;
using ToolQit; using ToolQit;
using ToolQit.Logging.Serilog; using ToolQit.Logging.Serilog;
@ -15,6 +17,7 @@ builder.Logging.AddSerilog();
// Add services to the container. // Add services to the container.
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, SrssAuthenticationStateProvider>();
builder.Services.AddMudServices(); builder.Services.AddMudServices();
var app = builder.Build(); var app = builder.Build();

View File

@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.10" />
<PackageReference Include="MudBlazor" Version="6.9.0" /> <PackageReference Include="MudBlazor" Version="6.9.0" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
@ -21,8 +22,4 @@
<ProjectReference Include="..\ToolQit\ToolQit\ToolQit.csproj" /> <ProjectReference Include="..\ToolQit\ToolQit\ToolQit.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Data" />
</ItemGroup>
</Project> </Project>