Browse Source

Backend Login Feature commit 21_05_2023

BISK2023-1
Benjamin Arhen 2 years ago
parent
commit
45ab3dee38
  1. 7
      Client/Pages/Auth/Login.razor.cs
  2. 10
      Server/BiskAcdbContext.cs
  3. 5
      Server/Biskilog Accounting.Server.csproj
  4. 20
      Server/BiskilogContext.cs
  5. 39
      Server/Controllers/AuthenticationController.cs
  6. 8
      Server/Program.cs
  7. 141
      Server/Services/AuthenticationService.cs
  8. 52
      Server/Services/ConnectionService.cs
  9. 5
      Server/appsettings.json
  10. 8
      Shared/Biskilog Accounting.Shared.csproj
  11. 3
      Shared/ClientContractModels/Databasemap.cs
  12. 17
      Shared/Enums/AuthEnums.cs
  13. 14
      Shared/Enums/ConnectionEnums.cs
  14. 12
      Shared/Interfaces/IAuthService.cs
  15. 24
      Shared/Interfaces/IConnectionService.cs
  16. 8
      Shared/Interfaces/ITokenService.cs
  17. 124
      Shared/ServiceRepo/TokenService.cs

7
Client/Pages/Auth/Login.razor.cs

@ -0,0 +1,7 @@
namespace Biskilog_Accounting.Client.Pages.Auth
{
public partial class Login
{
}
}

10
Server/DevBiskacdbContext.cs → Server/BiskAcdbContext.cs

@ -5,13 +5,13 @@ using Microsoft.EntityFrameworkCore;
namespace Biskilog_Accounting.Server.POSModels;
public partial class DevBiskacdbContext : DbContext
public partial class BiskAcdbContext : DbContext
{
public DevBiskacdbContext()
public BiskAcdbContext()
{
}
public DevBiskacdbContext(DbContextOptions<DevBiskacdbContext> options)
public BiskAcdbContext(DbContextOptions<BiskAcdbContext> options)
: base(options)
{
}
@ -76,10 +76,6 @@ public partial class DevBiskacdbContext : DbContext
public virtual DbSet<Unitofmeasure> Unitofmeasures { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseMySql("server=54.37.19.162;database=dev_biskacdb;user=biskilog;password=mefbuk-6niFsu-fytrew", Microsoft.EntityFrameworkCore.ServerVersion.Parse("10.3.38-mariadb"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder

5
Server/Biskilog Accounting.Server.csproj

@ -15,6 +15,7 @@
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -35,8 +36,4 @@
<ProjectReference Include="..\Shared\Biskilog Accounting.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>
</Project>

20
Server/BiskilogClientsContext.cs → Server/BiskilogContext.cs

@ -4,14 +4,15 @@ using Biskilog_Accounting.Shared.ClientContractModels;
using Microsoft.EntityFrameworkCore;
namespace Biskilog_Accounting.Server;
public partial class BiskilogClientsContext : DbContext
/// <summary>
/// This is the main EF DbContext for the Biskilog Accounting
/// </summary>
public partial class BiskilogContext : DbContext
{
public BiskilogClientsContext()
public BiskilogContext()
{
}
public BiskilogClientsContext(DbContextOptions<BiskilogClientsContext> options)
public BiskilogContext(DbContextOptions<BiskilogContext> options)
: base(options)
{
}
@ -178,6 +179,15 @@ public partial class BiskilogClientsContext : DbContext
.HasColumnName("db_name")
.UseCollation("utf8mb4_general_ci")
.HasCharSet("utf8mb4");
entity.Property(e => e.Domain)
.HasMaxLength(50)
.HasDefaultValueSql("''")
.HasColumnName("domain")
.UseCollation("utf8mb4_general_ci")
.HasCharSet("utf8mb4");
entity.Property(e => e.LastSyncDate)
.HasColumnType("datetime")
.HasColumnName("last_sync_date");
});
modelBuilder.Entity<Siteaccesspermission>(entity =>

39
Server/Controllers/AuthenticationController.cs

@ -0,0 +1,39 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.DotNet.Scaffolding.Shared.CodeModifier.CodeChange;
namespace Biskilog_Accounting.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthService m_authService;
private readonly ITokenService m_tokenService;
public AuthenticationController(IAuthService a_authService, ITokenService a_tokenService)
{
m_authService = a_authService;
m_tokenService = a_tokenService;
}
[HttpPost, Route("type-a")]
public async Task<IActionResult> AuthUser(Userauth a_userData)
{
if (a_userData != null && a_userData.Username != null && a_userData.Passsword != null)
{
var token = await m_authService.AuthenticateClient(a_userData.Username, a_userData.Passsword);
if (token == null)
return BadRequest("Invalid credentials");
return Ok(token.ToString());
}
else
{
return BadRequest();
}
}
}
}

8
Server/Program.cs

@ -6,6 +6,9 @@ using Microsoft.OpenApi.Models;
using System.Text.Json.Serialization;
using System.Text;
using Biskilog_Accounting.Server;
using Biskilog_Accounting.ServiceRepo;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Server.Services;
var builder = WebApplication.CreateBuilder(args);
@ -14,11 +17,12 @@ builder.Services.AddControllers().AddJsonOptions(x => x.JsonSerializerOptions.Re
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Services.AddEntityFrameworkMySql().AddDbContext<BiskilogClientsContext>(options =>
builder.Services.AddEntityFrameworkMySql().AddDbContext<BiskilogContext>(options =>
{
options.UseMySql(builder.Configuration.GetConnectionString("Connection"), new MariaDbServerVersion(new Version()));
});
//builder.Services.AddScoped<IAuthService, AuthRepo>();
builder.Services.AddScoped<IAuthService, AuthenticationService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddCors(options =>
{

141
Server/Services/AuthenticationService.cs

@ -0,0 +1,141 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Biskilog_Accounting.Server.Services
{
public class AuthenticationService : IAuthService
{
private readonly BiskilogContext m_context;
private readonly ITokenService m_tokenService;
public AuthenticationService(BiskilogContext a_context, ITokenService a_tokenService)
{
m_context = a_context;
m_tokenService = a_tokenService;
}
/// <summary>
/// Returns the status of a user account
/// </summary>
/// <returns>AuthEnums</returns>
public AuthEnums AccountStatus(int? a_id, string? a_username)
{
if (m_context.Userauths.Any(i => i.UserId == a_id || i.Username == a_username))
{
return AuthEnums.Found;
}
else
{
return AuthEnums.NotFound;
}
}
/// <summary>
/// Autenticates a user and returns a tokenized string
/// </summary>
/// <param name="a_username"></param>
/// <param name="a_password"></param>
/// <returns>strings</returns>
public async Task<string> AuthenticateClient(string a_username, string a_password)
{
var user = await GetUserAsync(a_username, a_password);
if (user == null)
{
return null;
}
user.LastLogin = DateTime.Now;
m_context.Userauths.Update(user);
m_context.SaveChanges();
Databasemap databasemap = GetClientDB(user.ClientId);
List<int> businessIds = GetSiteaccesspermission(user.ClientId, user.UserId).Select(t => t.BusinessId).ToList();
Contract? contract = GetContract(user.ClientId, businessIds);
if (contract == null)
return AuthEnums.Invalid.ToString();
return m_tokenService.GenerateToken(user, contract, databasemap);
}
/// <summary>
/// Creates a new user account
/// </summary>
/// returns AuthEnums.successful if the account was created successfully
public AuthEnums CreateUser(Userauth a_user)
{
throw new NotImplementedException();
//if (AccountStatus(null, a_user.Username) == AuthEnums.Found)
// return AuthEnums.Registered;
//a_user.Role = m_context.UserRoles.First(i => i.RoleId== a_user.RoleId);
//a_user.Password = BCrypt.Net.BCrypt.HashPassword(a_user.Password);
//m_context.Users.Add(a_user);
//var result = m_context.SaveChangesAsync();
//result.Wait();
//if(result.Result > 0)
//{
// return AuthEnums.Successful;
//}
//return AuthEnums.Error;
}
/// <summary>
/// Deletes a user account
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void DeleteUser(int a_id)
{
throw new NotImplementedException();
}
public List<Clientbusiness> GetClientbusiness(int a_clientId)
{
return m_context.Clientbusinesses.Where(i => i.ClientId == a_clientId).ToList();
}
public Databasemap GetClientDB(int a_clientId)
{
return m_context.Databasemaps.First(t => t.ClientId == a_clientId);
}
public Contract? GetContract(int a_clientId, List<int> a_businessId)
{
return m_context.Contracts.FirstOrDefault(c => c.ClientId == a_clientId && a_businessId.Contains(c.BusinessId!.Value) && c.EndDate >= DateTime.Now);
}
public List<Siteaccesspermission> GetSiteaccesspermission(int a_clientId, int a_userId)
{
return m_context.Siteaccesspermissions.Where(t => t.ClientId == a_clientId && t.UserId == a_userId).ToList();
}
private async Task<Userauth> GetUserAsync(string username, string password)
{
//Todo have complete implementation after means of creating user is done
//try
//{
// string pa = await m_context.Userauths.Where(u => u.Username == username).Select(u => u.Password).FirstAsync();
// bool verified = BCrypt.Net.BCrypt.Verify(password, pa);
// if (verified)
// {
//TODO have a complete implementation
return await m_context.Userauths.FirstAsync(u => u.Username == username && u.Passsword == password);
// }
// else
// {
// return null;
// }
//}catch(Exception ex)
//{
// //possible is user not found
// return null;
//}
}
}
}

52
Server/Services/ConnectionService.cs

@ -0,0 +1,52 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace Biskilog_Accounting.Server.Services
{
public class ConnectionService : IConnectionService
{
private readonly BiskilogContext m_context;
private readonly ITokenService m_tokenService;
private readonly IConfiguration m_configuration;
public ConnectionService(BiskilogContext a_context, ITokenService a_tokenService, IConfiguration configuration)
{
m_context = a_context;
m_tokenService = a_tokenService;
m_configuration = configuration;
}
/// <summary>
/// Prepares and returns the connection string for a client using the specified database id
/// </summary>
/// <param name="a_databaseId">Specified database id to use</param>
/// <returns></returns>
public string GetClientConnectionString(int a_databaseId)
{
Databasemap? dbMap = m_context.Databasemaps.Find(a_databaseId);
if (dbMap != null)
{
string rawConString = m_configuration.GetConnectionString("PrivateConnection")!.ToString();
return String.Format(rawConString, dbMap.Domain, dbMap.DbName);
}
return ConnectionEnums.ConnectionNotEstablished.ToString();
}
/// <summary>
/// Prepare the DB context from the specified connection string
/// </summary>
/// <param name="a_context"></param>
/// <param name="a_connectionString"></param>
/// <returns>A configured BiskAcdbContext</returns>
public object PrepareDBContext(string a_connectionString)
{
DbContextOptionsBuilder<BiskAcdbContext> acdbContext = new DbContextOptionsBuilder<BiskAcdbContext>();
acdbContext.UseMySql(a_connectionString, new MariaDbServerVersion(new Version()));
return acdbContext;
}
}
}

5
Server/appsettings.json

@ -6,11 +6,12 @@
}
},
"ConnectionStrings": {
"Connection": "server=54.37.19.162;database=dev_biskilogclients;user=biskilog;password=mefbuk-6niFsu-fytrew"
"Connection": "server=54.37.19.162;database=dev_biskilogclients;user=biskilog;password=mefbuk-6niFsu-fytrew",
"PrivateConnection": "server={0};database={1};user=biskilog;password=mefbuk-6niFsu-fytrew"
},
"AllowedHosts": "*",
"JWT": {
"Key": "@@BISKILOG2023DEV??//##$",
"Key": "@@BISKILOGACCOUNTING2023DEV??//##$",
"Issuer": "AUTH SERVER",
"Audience": "BISKILOG"
}

8
Shared/Biskilog Accounting.Shared.csproj

@ -8,13 +8,13 @@
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.30.1" />
</ItemGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<Folder Include="Enums\" />
</ItemGroup>
</Project>

3
Shared/ClientContractModels/Databasemap.cs

@ -10,4 +10,7 @@ public partial class Databasemap
public string DbName { get; set; } = null!;
public int ClientId { get; set; }
public string Domain { get; set; }
public DateTime LastSyncDate { get; set; }
}

17
Shared/Enums/AuthEnums.cs

@ -0,0 +1,17 @@
namespace Biskilog_Accounting.Shared.Enums
{
public enum AuthEnums
{
Registered,
AleadyLoggedin,
WrongPassword,
NotFound,
Found,
Expired,
Invalid,
Valid,
Successful,
Error
}
}

14
Shared/Enums/ConnectionEnums.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Enums
{
public enum ConnectionEnums
{
ConnectionEstablished,
ConnectionNotEstablished,
}
}

12
Shared/Interfaces/IAuthService.cs

@ -4,12 +4,6 @@ namespace Biskilog_Accounting.Shared.Interfaces
{
public interface IAuthService
{
/// <summary>
/// Prepares and returns the connection string for a client using the specified database id
/// </summary>
/// <param name="a_databaseId">Specified database id to use</param>
/// <returns></returns>
string GetClientConnectionString(int a_databaseId);
/// <summary>
/// Authenticates user or client
/// </summary>
@ -17,9 +11,9 @@ namespace Biskilog_Accounting.Shared.Interfaces
/// <param name="a_password"></param>
/// <returns>A tokenized string with relevant information on the authenticated user</returns>
Task<string> AuthenticateClient(string a_username, string a_password);
Contract GetContract(int a_clientId, int a_businessId);
Contract? GetContract(int a_clientId, List<int> a_businessId);
Databasemap GetClientDB(int a_clientId);
Siteaccesspermission GetSiteaccesspermission(int a_clientId);
Clientbusiness GetClientbusiness(int a_clientId);
List<Siteaccesspermission> GetSiteaccesspermission(int a_clientId, int a_userId);
List<Clientbusiness> GetClientbusiness(int a_clientId);
}
}

24
Shared/Interfaces/IConnectionService.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface IConnectionService
{
/// <summary>
/// Prepares and returns the connection string for a client using the specified database id
/// </summary>
/// <param name="a_databaseId">Specified database id to use</param>
/// <returns></returns>
string GetClientConnectionString(int a_databaseId);
/// <summary>
/// Prepare the DB context from the specified connection string
/// </summary>
/// <param name="a_connectionString"></param>
/// <returns>A configured BiskAcdbContext</returns>
object PrepareDBContext(string a_connectionString);
}
}

8
Shared/Interfaces/ITokenService.cs

@ -1,13 +1,13 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Enums;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface ITokenService
{
bool ValidateToken(string a_token);
string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database, Siteaccesspermission a_accessPermission);
int? GetRoleFromToken(string a_token);
AuthEnums ValidateToken(string a_token);
string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database);
int? GetDatabaseIdFromToken(string a_token);
int? GetUserIdFromToken(string a_token);
string? GetUserNameFromToken(string a_token);

124
Shared/ServiceRepo/TokenService.cs

@ -0,0 +1,124 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Biskilog_Accounting.ServiceRepo
{
public class TokenService : ITokenService
{
private IConfiguration m_configuration { get; }
public TokenService(IConfiguration a_configuration)
{
m_configuration = a_configuration;
}
/// <summary>
/// Validates a user access token
/// </summary>
/// <returns>AuthEnums.Valid if token is a valid and unexpired token</returns>
public AuthEnums ValidateToken(string a_token)
{
try
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
if (jwtToken.ValidFrom <= DateTime.Now && jwtToken.ValidTo > DateTime.Now)
return AuthEnums.Valid;
return AuthEnums.Expired;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return AuthEnums.Invalid;
}
}
/// <summary>
/// Generates an access token based on the user
/// </summary>
/// <returns>A tokenized string</returns>
public string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database)
{
try
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim("ContractStart",a_clientContract.StartDate !.Value.ToString()),
new Claim("ContractEnd",a_clientContract.EndDate!.Value.ToString()),
new Claim("UserId", a_user.UserId.ToString()),
new Claim("Username", a_user.Username.ToString()),
new Claim("DbId",a_database.DbNo.ToString()),
new Claim("ClientId", a_user.ClientId.ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(m_configuration["Jwt:Key"]!));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(m_configuration["Jwt:Issuer"], m_configuration["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(14), signingCredentials: signIn);
return $"{new JwtSecurityTokenHandler().WriteToken(token)}";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return AuthEnums.Error.ToString();
}
}
/// <summary>
///Deserializes the token string if valid to return the specified user role id in the token string
/// </summary>
/// <param name="a_token"></param>
/// <returns>RoleId</returns>
public int? GetDatabaseIdFromToken(string a_token)
{
if (ValidateToken(a_token) == AuthEnums.Valid)
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
return int.Parse(jwtToken.Claims.First(claim => claim.Type == "DbId").Value);
}
return null;
}
/// <summary>
///Deserializes the token string if valid to return the specified user id in the token string
/// </summary>
/// <param name="a_token"></param>
/// <returns>UserId</returns>
public int? GetUserIdFromToken(string a_token)
{
if (ValidateToken(a_token) == AuthEnums.Valid)
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
return int.Parse(jwtToken.Claims.First(claim => claim.Type == "UserId").Value);
}
return null;
}
/// <summary>
///Deserializes the token string if valid to return the specified username in the token string
/// </summary>
/// <param name="a_token"></param>
/// <returns>Username</returns>
public string? GetUserNameFromToken(string a_token)
{
if (ValidateToken(a_token) == AuthEnums.Valid)
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
return jwtToken.Claims.First(claim => claim.Type == "Username").Value;
}
return null;
}
}
}
Loading…
Cancel
Save