diff --git a/SharpRSS.API.Contracts/ApiResult.cs b/SharpRSS.API.Contracts/ApiResult.cs new file mode 100644 index 0000000..adf5af4 --- /dev/null +++ b/SharpRSS.API.Contracts/ApiResult.cs @@ -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 + } +} \ No newline at end of file diff --git a/SharpRSS.API.Contracts/DTO/UserDto.cs b/SharpRSS.API.Contracts/DTO/UserDto.cs new file mode 100644 index 0000000..bfb3483 --- /dev/null +++ b/SharpRSS.API.Contracts/DTO/UserDto.cs @@ -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; } + } +} \ No newline at end of file diff --git a/SharpRSS.API.Contracts/SharpRSS.API.Contracts.csproj b/SharpRSS.API.Contracts/SharpRSS.API.Contracts.csproj index ccefe79..5cf2042 100644 --- a/SharpRSS.API.Contracts/SharpRSS.API.Contracts.csproj +++ b/SharpRSS.API.Contracts/SharpRSS.API.Contracts.csproj @@ -13,7 +13,6 @@ - diff --git a/SharpRSS.API/Controllers/AuthController.cs b/SharpRSS.API/Controllers/AuthController.cs index d778a0b..82d4695 100644 --- a/SharpRSS.API/Controllers/AuthController.cs +++ b/SharpRSS.API/Controllers/AuthController.cs @@ -1,5 +1,10 @@ using System.Threading.Tasks; 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 { @@ -7,10 +12,20 @@ namespace SharpRSS.API.Controllers [Route("api/[controller]")] public class AuthController : ControllerBase { - [HttpGet("authenticate")] - public async Task> Authenticate() + public AuthController(AuthService authService) { - return "Authenticated!"; + _authService = authService; + } + + private readonly AuthService _authService; + + [HttpPost("create")] + public async Task> CreateUser(UserRequest user) + { + Result 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)); } } } \ No newline at end of file diff --git a/SharpRSS.API/Controllers/RssController.cs b/SharpRSS.API/Controllers/RssController.cs index 074e415..91da0df 100644 --- a/SharpRSS.API/Controllers/RssController.cs +++ b/SharpRSS.API/Controllers/RssController.cs @@ -1,11 +1,16 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace SharpRSS.API.Controllers { [ApiController] [Route("api/[controller]")] - public class RssController + public class RssController : ControllerBase { - + [HttpGet("guide")] + public async Task> GetGuide() + { + return Ok("Guide data"); + } } } \ No newline at end of file diff --git a/SharpRSS.API/Cryptography/Hasher.cs b/SharpRSS.API/Cryptography/Hasher.cs new file mode 100644 index 0000000..95831bc --- /dev/null +++ b/SharpRSS.API/Cryptography/Hasher.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/SharpRSS.API/Data/AuthService.cs b/SharpRSS.API/Data/AuthService.cs new file mode 100644 index 0000000..20f0ae2 --- /dev/null +++ b/SharpRSS.API/Data/AuthService.cs @@ -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> 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, 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, message: "Could not create user!"); + } + return new Result(user, result); + } + } +} \ No newline at end of file diff --git a/SharpRSS.API/Data/AuthenticationService.cs b/SharpRSS.API/Data/AuthenticationService.cs deleted file mode 100644 index 5ffd196..0000000 --- a/SharpRSS.API/Data/AuthenticationService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SharpRSS.API.Data -{ - public class AuthenticationService - { - - } -} \ No newline at end of file diff --git a/SharpRSS.API/Data/DbAccess.cs b/SharpRSS.API/Data/DbAccess.cs new file mode 100644 index 0000000..281a7c9 --- /dev/null +++ b/SharpRSS.API/Data/DbAccess.cs @@ -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 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().ToTable("srss_user"); + base.OnModelCreating(modelBuilder); + } + } +} \ No newline at end of file diff --git a/SharpRSS.API/Models/Auth/User.cs b/SharpRSS.API/Models/Auth/User.cs new file mode 100644 index 0000000..7e54e5f --- /dev/null +++ b/SharpRSS.API/Models/Auth/User.cs @@ -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; + } +} \ No newline at end of file diff --git a/SharpRSS.API/Models/Auth/UserRequest.cs b/SharpRSS.API/Models/Auth/UserRequest.cs new file mode 100644 index 0000000..9dbdb09 --- /dev/null +++ b/SharpRSS.API/Models/Auth/UserRequest.cs @@ -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; } + } +} \ No newline at end of file diff --git a/SharpRSS.API/Models/Result.cs b/SharpRSS.API/Models/Result.cs new file mode 100644 index 0000000..7db58e3 --- /dev/null +++ b/SharpRSS.API/Models/Result.cs @@ -0,0 +1,15 @@ +namespace SharpRSS.API.Models +{ + public class Result + { + 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; } + } +} \ No newline at end of file diff --git a/SharpRSS.API/Program.cs b/SharpRSS.API/Program.cs index 56522f5..58c955f 100644 --- a/SharpRSS.API/Program.cs +++ b/SharpRSS.API/Program.cs @@ -9,6 +9,7 @@ using SharpRSS.API.Data; using ToolQit; using ToolQit.Logging.Serilog; + SetupSerilog(); var builder = WebApplication.CreateBuilder(args); builder.Logging.AddSerilog(); @@ -18,7 +19,7 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); diff --git a/SharpRSS.API/SharpRSS.API.csproj b/SharpRSS.API/SharpRSS.API.csproj index f8bb911..77dbe9a 100644 --- a/SharpRSS.API/SharpRSS.API.csproj +++ b/SharpRSS.API/SharpRSS.API.csproj @@ -9,6 +9,8 @@ + + @@ -22,8 +24,4 @@ - - - - diff --git a/SharpRSS.API/appsettings.Development.json b/SharpRSS.API/appsettings.Development.json index 0c208ae..9cd5ba8 100644 --- a/SharpRSS.API/appsettings.Development.json +++ b/SharpRSS.API/appsettings.Development.json @@ -1,4 +1,8 @@ { + "DataBase": { + "Server": "MariaDB", + "Connection": "server=192.168.1.4;user id=api_dev;password=B4OqGO50qCxZXESD;database=sharprss_dev" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/SharpRSS.Blazor/Data/SrssAuthenticationStateProvider.cs b/SharpRSS.Blazor/Data/SrssAuthenticationStateProvider.cs new file mode 100644 index 0000000..8fa8383 --- /dev/null +++ b/SharpRSS.Blazor/Data/SrssAuthenticationStateProvider.cs @@ -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 GetAuthenticationStateAsync() + { + var identity = new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.Name, "srss") + }, "Custom Authentication"); + var user = new ClaimsPrincipal(identity); + return Task.FromResult(new AuthenticationState(user)); + } + } +} \ No newline at end of file diff --git a/SharpRSS.Blazor/Program.cs b/SharpRSS.Blazor/Program.cs index 40fda78..9215c85 100644 --- a/SharpRSS.Blazor/Program.cs +++ b/SharpRSS.Blazor/Program.cs @@ -1,11 +1,13 @@ using System; using System.IO; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MudBlazor.Services; using Serilog; using Serilog.Formatting.Compact; +using SharpRSS.Blazor.Data; using ToolQit; using ToolQit.Logging.Serilog; @@ -15,6 +17,7 @@ builder.Logging.AddSerilog(); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); +builder.Services.AddScoped(); builder.Services.AddMudServices(); var app = builder.Build(); diff --git a/SharpRSS.Blazor/SharpRSS.Blazor.csproj b/SharpRSS.Blazor/SharpRSS.Blazor.csproj index 6b88525..9ab30e8 100644 --- a/SharpRSS.Blazor/SharpRSS.Blazor.csproj +++ b/SharpRSS.Blazor/SharpRSS.Blazor.csproj @@ -8,6 +8,7 @@ + @@ -21,8 +22,4 @@ - - - -